Skip to content

Commit 2476188

Browse files
CryorisElePT
andauthored
fix the 0 complex part (Qiskit#13643)
Co-authored-by: Elena Peña Tapia <[email protected]>
1 parent b872e88 commit 2476188

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

qiskit/synthesis/evolution/product_formula.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,6 @@ def settings(self) -> dict[str, typing.Any]:
191191
"preserve_order": self.preserve_order,
192192
}
193193

194-
def _normalize_coefficients(
195-
self, paulis: list[str | list[int], float | complex | ParameterExpression]
196-
) -> list[str | list[int] | ParameterValueType]:
197-
"""Ensure the coefficients are real (or parameter expressions)."""
198-
return [[(op, qubits, real_or_fail(coeff)) for op, qubits, coeff in ops] for ops in paulis]
199-
200194
def _custom_evolution(self, num_qubits, pauli_rotations):
201195
"""Implement the evolution for the non-standard path.
202196

qiskit/synthesis/evolution/suzuki_trotter.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import typing
1919
from collections.abc import Callable
2020
from itertools import chain
21+
import numpy as np
2122

23+
from qiskit.circuit.parameterexpression import ParameterExpression
2224
from qiskit.circuit.quantumcircuit import QuantumCircuit
2325
from qiskit.quantum_info.operators import SparsePauliOp, Pauli
2426
from qiskit.utils.deprecation import deprecate_arg
@@ -157,7 +159,10 @@ def expand(
157159
time = evolution.time
158160

159161
def to_sparse_list(operator):
160-
paulis = (time * (2 / self.reps) * operator).to_sparse_list()
162+
paulis = [
163+
(pauli, indices, real_or_fail(coeff) * time * 2 / self.reps)
164+
for pauli, indices, coeff in operator.to_sparse_list()
165+
]
161166
if not self.preserve_order:
162167
return reorder_paulis(paulis)
163168

@@ -171,9 +176,6 @@ def to_sparse_list(operator):
171176
# here would be the location to do so.
172177
non_commuting = [[op] for op in to_sparse_list(operators)]
173178

174-
# normalize coefficients, i.e. ensure they are float or ParameterExpression
175-
non_commuting = self._normalize_coefficients(non_commuting)
176-
177179
# we're already done here since Lie Trotter does not do any operator repetition
178180
product_formula = self._recurse(self.order, non_commuting)
179181
flattened = self.reps * list(chain.from_iterable(product_formula))
@@ -213,3 +215,18 @@ def _recurse(order, grouped_paulis):
213215
],
214216
)
215217
return outer + inner + outer
218+
219+
220+
def real_or_fail(value, tol=100):
221+
"""Return real if close, otherwise fail. Unbound parameters are left unchanged.
222+
223+
Based on NumPy's ``real_if_close``, i.e. ``tol`` is in terms of machine precision for float.
224+
"""
225+
if isinstance(value, ParameterExpression):
226+
return value
227+
228+
abstol = tol * np.finfo(float).eps
229+
if abs(np.imag(value)) < abstol:
230+
return np.real(value)
231+
232+
raise ValueError(f"Encountered complex value {value}, but expected real.")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed an inconsistency in the circuit generated by a Pauli evolution synthesis
5+
with :class:`.SuzukiTrotter` or :class:`.LieTrotter` (the default) method.
6+
For parameterized evolution times, the resulting circuits contained parameters
7+
with a spurious, zero complex part, which affected the output of
8+
:meth:`.ParameterExpression.sympify`. The output now correctly is only real.
9+
Fixed `#13642 <https://github.com/Qiskit/qiskit/pull/13642>`__.

test/python/circuit/library/test_evolution_gate.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,20 @@ def atomic_evolution(pauli, time):
479479
decomposed = evo_gate.definition.decompose()
480480
self.assertEqual(decomposed.count_ops()["cx"], reps * 3 * 4)
481481

482+
def test_sympify_is_real(self):
483+
"""Test converting the parameters to sympy is real.
484+
485+
Regression test of #13642, where the parameters in the Pauli evolution had a spurious
486+
zero complex part. Even though this is not noticable upon binding or printing the parameter,
487+
it does affect the output of Parameter.sympify.
488+
"""
489+
time = Parameter("t")
490+
evo = PauliEvolutionGate(Z, time=time)
491+
492+
angle = evo.definition.data[0].operation.params[0]
493+
expected = (2.0 * time).sympify()
494+
self.assertEqual(expected, angle.sympify())
495+
482496

483497
def exact_atomic_evolution(circuit, pauli, time):
484498
"""An exact atomic evolution for Suzuki-Trotter.

0 commit comments

Comments
 (0)