Repository: Qiskit/qiskit

Issue:
SparsePauliOp casting of coeffs causes errors for dtype=real
### Environment

- **Qiskit Terra version**: 0.22
- **Python version**: All
- **Operating system**: All

### What is happening?

The current init for `SparsePauliOp` has handling for storing coefficient arrays using their input dtype, but this is used to cast the coeffs to that type after moving the Pauli phase to the coeffs. This can cause errors if the dtype is not complex.

### How can we reproduce the issue?

Here is an example which gives an incorrect answer:
```python
# Initialize ops, with coeffs with dtype double
a = SparsePauliOp(['X'], np.array([1]))
b = SparsePauliOp(['Y'], np.array([1]))
a.dot(b)
```
This raises a warning
```
# Raises waring
.../sparse_pauli_op.py:131: ComplexWarning: Casting complex values to real discards the imaginary part
  self._coeffs = np.asarray((-1j) ** (phase - count_y) * coeffs, dtype=coeffs.dtype)
```
and returns the incorrect value
```
SparsePauliOp(['Z'],
              coeffs=[0])
```

### What should happen?

For the above example the correct answer should be
```
SparsePauliOp(['Z'],
              coeffs=[0.+1.j])
```

### Any suggestions?

Removing the explicit dype in the line from the warning should fix this, which would allow double coeffs to be converted to complex if required.

I suspect this support was added in the first place to handle object arrays for parameters, and I think that should still be supported if the dtype arg is not used since the asarray should default to an object array in that case.
SparsePauliOp casting of coeffs causes errors for dtype=real
### Environment

- **Qiskit Terra version**: 0.22
- **Python version**: All
- **Operating system**: All

### What is happening?

The current init for `SparsePauliOp` has handling for storing coefficient arrays using their input dtype, but this is used to cast the coeffs to that type after moving the Pauli phase to the coeffs. This can cause errors if the dtype is not complex.

### How can we reproduce the issue?

Here is an example which gives an incorrect answer:
```python
# Initialize ops, with coeffs with dtype double
a = SparsePauliOp(['X'], np.array([1]))
b = SparsePauliOp(['Y'], np.array([1]))
a.dot(b)
```
This raises a warning
```
# Raises waring
.../sparse_pauli_op.py:131: ComplexWarning: Casting complex values to real discards the imaginary part
  self._coeffs = np.asarray((-1j) ** (phase - count_y) * coeffs, dtype=coeffs.dtype)
```
and returns the incorrect value
```
SparsePauliOp(['Z'],
              coeffs=[0])
```

### What should happen?

For the above example the correct answer should be
```
SparsePauliOp(['Z'],
              coeffs=[0.+1.j])
```

### Any suggestions?

Removing the explicit dype in the line from the warning should fix this, which would allow double coeffs to be converted to complex if required.

I suspect this support was added in the first place to handle object arrays for parameters, and I think that should still be supported if the dtype arg is not used since the asarray should default to an object array in that case.


Discussion:
Yeah, seems like the allowed dtypes here should only be `np.complex128` and `object` - any other numeric dtype should perhaps be raised to complex to avoid this error.

@ikkoham: did you have more dtypes in mind when you wrote that part of the handling?
Ever wanted to use a float to save data? Well, no.
OK, I'll restrict it to complex and object.
Yeah, seems like the allowed dtypes here should only be `np.complex128` and `object` - any other numeric dtype should perhaps be raised to complex to avoid this error.

@ikkoham: did you have more dtypes in mind when you wrote that part of the handling?
Ever wanted to use a float to save data? Well, no.
OK, I'll restrict it to complex and object.

Resolving patch:
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -112,7 +112,7 @@ def __init__(self, data, coeffs=None, *, ignore_pauli_phase=False, copy=True):
 
         pauli_list = PauliList(data.copy() if copy and hasattr(data, "copy") else data)
 
-        dtype = coeffs.dtype if isinstance(coeffs, np.ndarray) else complex
+        dtype = object if isinstance(coeffs, np.ndarray) and coeffs.dtype == object else complex
 
         if coeffs is None:
             coeffs = np.ones(pauli_list.size, dtype=dtype)


Test patch:
diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
--- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
+++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
@@ -940,6 +940,13 @@ def commutes(left: Pauli, right: Pauli) -> bool:
                 )
             )
 
+    def test_dot_real(self):
+        """Test dot for real coefficiets."""
+        x = SparsePauliOp("X", np.array([1]))
+        y = SparsePauliOp("Y", np.array([1]))
+        iz = SparsePauliOp("Z", 1j)
+        self.assertEqual(x.dot(y), iz)
+
 
 if __name__ == "__main__":
     unittest.main()