diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py index 71b9fb7761a5..33b24115dee5 100644 --- a/qiskit/circuit/parameterexpression.py +++ b/qiskit/circuit/parameterexpression.py @@ -566,24 +566,18 @@ def __eq__(self, other): def is_real(self): """Return whether the expression is real""" - # workaround for symengine behavior that const * (0 + 1 * I) is not real - # see https://github.com/symengine/symengine.py/issues/414 - if _optionals.HAS_SYMENGINE and self._symbol_expr.is_real is None: - symbol_expr = self._symbol_expr.evalf() - else: - symbol_expr = self._symbol_expr - - if not symbol_expr.is_real and symbol_expr.is_real is not None: + if not self._symbol_expr.is_real and self._symbol_expr.is_real is not None: # Symengine returns false for is_real on the expression if - # there is a imaginary component (even if that component is 0), + # there is an imaginary component (even if that component is 0), # but the parameter will evaluate as real. Check that if the # expression's is_real attribute returns false that we have a # non-zero imaginary if _optionals.HAS_SYMENGINE: - if symbol_expr.imag == 0.0: + if numpy.isclose(float(self._symbol_expr.imag), 0): + self._symbol_expr = self._symbol_expr.real return True return False - return symbol_expr.is_real + return self._symbol_expr.is_real def sympify(self): """Return symbolic expression as a raw Sympy or Symengine object. diff --git a/requirements.txt b/requirements.txt index d41eee50ce95..0e809a463735 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,8 +7,13 @@ sympy>=1.3 dill>=0.3 python-dateutil>=2.8.0 stevedore>=3.0.0 -typing-extensions; python_version<'3.11' -# symengine pinning needed due lowered precision handling complex -# multiplication in version 0.10 wich breaks parameter assignment test -# (can be removed once issue is fix) -symengine>=0.9, <0.10; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64' +symengine>=0.10 ; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64' +shared-memory38;python_version<'3.8' +typing-extensions; python_version < '3.8' +singledispatchmethod; python_version < '3.8' + +# Hack around stevedore being broken by importlib_metadata 5.0; we need to pin +# the requirements rather than the constraints if we need to cut a release +# before stevedore is fixed. `importlib_metadata` is not (currently) a direct +# requirement of Terra. +importlib_metadata<5.0; python_version<'3.8' diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 9d14a4d766df..95e92ef40267 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -1453,31 +1453,30 @@ def test_raise_if_subbing_in_parameter_name_conflict(self): with self.assertRaisesRegex(CircuitError, "Name conflict"): expr.subs({x: y_second}) - def test_expressions_of_parameter_with_constant(self): + @data(2, 1.3, 0, -1, -1.0, numpy.pi, 1j) + def test_expressions_of_parameter_with_constant(self, const): """Verify operating on a Parameter with a constant.""" - good_constants = [2, 1.3, 0, -1, -1.0, numpy.pi, 1j] - x = Parameter("x") for op in self.supported_operations: - for const in good_constants: - expr = op(const, x) - bound_expr = expr.bind({x: 2.3}) - - self.assertEqual(complex(bound_expr), op(const, 2.3)) - - # Division by zero will raise. Tested elsewhere. - if const == 0 and op == truediv: - continue - - # Repeat above, swapping position of Parameter and constant. - expr = op(x, const) - bound_expr = expr.bind({x: 2.3}) - - res = complex(bound_expr) - expected = op(2.3, const) - self.assertTrue(cmath.isclose(res, expected), f"{res} != {expected}") + expr = op(const, x) + bound_expr = expr.bind({x: 2.3}) + res = complex(bound_expr) + expected = op(const, 2.3) + self.assertTrue(cmath.isclose(res, expected), f"{res} != {expected}") + + # Division by zero will raise. Tested elsewhere. + if const == 0 and op == truediv: + continue + + # Repeat above, swapping position of Parameter and constant. + expr = op(x, const) + bound_expr = expr.bind({x: 2.3}) + + res = complex(bound_expr) + expected = op(2.3, const) + self.assertTrue(cmath.isclose(res, expected), f"{res} != {expected}") def test_complex_parameter_bound_to_real(self): """Test a complex parameter expression can be real if bound correctly."""