diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 2b9f94d280fd..52e04ec7a990 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -198,7 +198,7 @@ Tier 1 platforms are currently:
* Linux x86_64 (distributions compatible with the
`manylinux 2014 `__
packaging specification).
- * macOS x86_64 (10.9 or newer)
+ * macOS x86_64 (10.12 or newer)
* Windows 64 bit
Tier 2
@@ -211,10 +211,6 @@ functioning Python environment.
Tier 2 platforms are currently:
- * Linux i686 (distributions compatible with the
- `manylinux 2014 `__ packaging
- specification) for Python < 3.10
- * Windows 32 bit for Python < 3.10
* Linux aarch64 (distributions compatible with the
`manylinux 2014 `__ packaging
specification)
@@ -240,8 +236,8 @@ Tier 3 platforms are currently:
* macOS arm64 (10.15 or newer)
* Linux i686 (distributions compatible with the
`manylinux 2014 `__ packaging
- specification) for Python >= 3.10
- * Windows 32 bit for Python >= 3.10
+ specification)
+ * Windows 32 bit
Ready to get going?...
======================
diff --git a/pyproject.toml b/pyproject.toml
index 25ff0a5dade7..172c0625d25b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@ target-version = ['py38', 'py39', 'py310', 'py311']
manylinux-x86_64-image = "manylinux2014"
manylinux-i686-image = "manylinux2014"
skip = "pp* cp36-* cp37-* *musllinux*"
-test-skip = "cp310-win32 cp310-manylinux_i686 cp311-win32 cp311-manylinux_i686"
+test-skip = "*win32 *linux_i686"
test-command = "python {project}/examples/python/stochastic_swap.py"
# We need to use pre-built versions of Numpy and Scipy in the tests; they have a
# tendency to crash if they're installed from source by `pip install`, and since
@@ -25,6 +25,7 @@ environment = 'PATH="$PATH:$HOME/.cargo/bin" CARGO_NET_GIT_FETCH_WITH_CLI="true"
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} && pipx run abi3audit --strict --report {wheel}"
[tool.cibuildwheel.macos]
+environment = "MACOSX_DEPLOYMENT_TARGET=10.12"
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel}"
[tool.cibuildwheel.windows]
diff --git a/qiskit/circuit/parameter.py b/qiskit/circuit/parameter.py
index 3e347b0beac1..7f73a325fdc8 100644
--- a/qiskit/circuit/parameter.py
+++ b/qiskit/circuit/parameter.py
@@ -15,8 +15,9 @@
from uuid import uuid4
+import symengine
+
from qiskit.circuit.exceptions import CircuitError
-from qiskit.utils import optionals as _optionals
from .parameterexpression import ParameterExpression
@@ -80,14 +81,7 @@ def __init__(self, name: str):
be any unicode string, e.g. "ϕ".
"""
self._name = name
- if not _optionals.HAS_SYMENGINE:
- from sympy import Symbol
-
- symbol = Symbol(name)
- else:
- import symengine
-
- symbol = symengine.Symbol(name)
+ symbol = symengine.Symbol(name)
super().__init__(symbol_map={self: symbol}, expr=symbol)
def assign(self, parameter, value):
@@ -102,11 +96,7 @@ def assign(self, parameter, value):
return value
# This is the `super().bind` case, where we're required to return a `ParameterExpression`,
# so we need to lift the given value to a symbolic expression.
- if _optionals.HAS_SYMENGINE:
- from symengine import sympify
- else:
- from sympy import sympify
- return ParameterExpression({}, sympify(value))
+ return ParameterExpression({}, symengine.sympify(value))
def subs(self, parameter_map: dict, allow_unknown_parameters: bool = False):
"""Substitute self with the corresponding parameter in ``parameter_map``."""
@@ -152,12 +142,5 @@ def __getstate__(self):
def __setstate__(self, state):
self._name = state["name"]
- if not _optionals.HAS_SYMENGINE:
- from sympy import Symbol
-
- symbol = Symbol(self._name)
- else:
- import symengine
-
- symbol = symengine.Symbol(self._name)
+ symbol = symengine.Symbol(self._name)
super().__init__(symbol_map={self: symbol}, expr=symbol)
diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
index 71b9fb7761a5..23fc436015f3 100644
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -20,9 +20,9 @@
import operator
import numpy
+import symengine
from qiskit.circuit.exceptions import CircuitError
-from qiskit.utils import optionals as _optionals
# This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it
# can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it
@@ -66,14 +66,9 @@ def _names(self) -> dict:
def conjugate(self) -> "ParameterExpression":
"""Return the conjugate."""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- conjugated = ParameterExpression(
- self._parameter_symbols, symengine.conjugate(self._symbol_expr)
- )
- else:
- conjugated = ParameterExpression(self._parameter_symbols, self._symbol_expr.conjugate())
+ conjugated = ParameterExpression(
+ self._parameter_symbols, symengine.conjugate(self._symbol_expr)
+ )
return conjugated
def assign(self, parameter, value: ParameterValueType) -> "ParameterExpression":
@@ -183,15 +178,7 @@ def subs(
new_parameter_symbols = {
p: s for p, s in self._parameter_symbols.items() if p not in parameter_map
}
-
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- symbol_type = symengine.Symbol
- else:
- from sympy import Symbol
-
- symbol_type = Symbol
+ symbol_type = symengine.Symbol
# If new_param is an expr, we'll need to construct a matching sympy expr
# but with our sympy symbols instead of theirs.
@@ -304,15 +291,7 @@ def gradient(self, param) -> Union["ParameterExpression", complex]:
# Compute the gradient of the parameter expression w.r.t. param
key = self._parameter_symbols[param]
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- expr_grad = symengine.Derivative(self._symbol_expr, key)
- else:
- # TODO enable nth derivative
- from sympy import Derivative
-
- expr_grad = Derivative(self._symbol_expr, key).doit()
+ expr_grad = symengine.Derivative(self._symbol_expr, key)
# generate the new dictionary of symbols
# this needs to be done since in the derivative some symbols might disappear (e.g.
@@ -365,102 +344,39 @@ def _call(self, ufunc):
def sin(self):
"""Sine of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.sin)
- else:
- from sympy import sin as _sin
-
- return self._call(_sin)
+ return self._call(symengine.sin)
def cos(self):
"""Cosine of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.cos)
- else:
- from sympy import cos as _cos
-
- return self._call(_cos)
+ return self._call(symengine.cos)
def tan(self):
"""Tangent of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.tan)
- else:
- from sympy import tan as _tan
-
- return self._call(_tan)
+ return self._call(symengine.tan)
def arcsin(self):
"""Arcsin of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.asin)
- else:
- from sympy import asin as _asin
-
- return self._call(_asin)
+ return self._call(symengine.asin)
def arccos(self):
"""Arccos of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.acos)
- else:
- from sympy import acos as _acos
-
- return self._call(_acos)
+ return self._call(symengine.acos)
def arctan(self):
"""Arctan of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.atan)
- else:
- from sympy import atan as _atan
-
- return self._call(_atan)
+ return self._call(symengine.atan)
def exp(self):
"""Exponential of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.exp)
- else:
- from sympy import exp as _exp
-
- return self._call(_exp)
+ return self._call(symengine.exp)
def log(self):
"""Logarithm of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.log)
- else:
- from sympy import log as _log
-
- return self._call(_log)
+ return self._call(symengine.log)
def sign(self):
"""Sign of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.sign)
- else:
- from sympy import sign as _sign
-
- return self._call(_sign)
+ return self._call(symengine.sign)
def __repr__(self):
return f"{self.__class__.__name__}({str(self)})"
@@ -492,24 +408,21 @@ def __float__(self):
"ParameterExpression with unbound parameters ({}) "
"cannot be cast to a float.".format(self.parameters)
) from None
- try:
- # In symengine, if an expression was complex at any time, its type is likely to have
- # stayed "complex" even when the imaginary part symbolically (i.e. exactly)
- # cancelled out. Sympy tends to more aggressively recognise these as symbolically
- # real. This second attempt at a cast is a way of unifying the behaviour to the
- # more expected form for our users.
- cval = complex(self)
- if cval.imag == 0.0:
- return cval.real
- except TypeError:
- pass
+ # In symengine, if an expression was complex at any time, its type is likely to have
+ # stayed "complex" even when the imaginary part symbolically (i.e. exactly)
+ # cancelled out. Sympy tends to more aggressively recognise these as symbolically
+ # real. This second attempt at a cast is a way of unifying the behaviour to the
+ # more expected form for our users.
+ cval = complex(self)
+ if cval.imag == 0.0:
+ return cval.real
raise TypeError("could not cast expression to float") from exc
def __int__(self):
try:
return int(self._symbol_expr)
- # TypeError is for sympy, RuntimeError for symengine
- except (TypeError, RuntimeError) as exc:
+ # TypeError is for backwards compatibility, RuntimeError is raised by symengine
+ except RuntimeError as exc:
if self.parameters:
raise TypeError(
"ParameterExpression with unbound parameters ({}) "
@@ -528,14 +441,7 @@ def __deepcopy__(self, memo=None):
def __abs__(self):
"""Absolute of a ParameterExpression"""
- if _optionals.HAS_SYMENGINE:
- import symengine
-
- return self._call(symengine.Abs)
- else:
- from sympy import Abs as _abs
-
- return self._call(_abs)
+ return self._call(symengine.Abs)
def abs(self):
"""Absolute of a ParameterExpression"""
@@ -553,12 +459,9 @@ def __eq__(self, other):
if isinstance(other, ParameterExpression):
if self.parameters != other.parameters:
return False
- if _optionals.HAS_SYMENGINE:
- from sympy import sympify
+ from sympy import sympify
- return sympify(self._symbol_expr).equals(sympify(other._symbol_expr))
- else:
- return self._symbol_expr.equals(other._symbol_expr)
+ return sympify(self._symbol_expr).equals(sympify(other._symbol_expr))
elif isinstance(other, numbers.Number):
return len(self.parameters) == 0 and complex(self._symbol_expr) == other
return False
@@ -568,7 +471,7 @@ def is_real(self):
# 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:
+ if self._symbol_expr.is_real is None:
symbol_expr = self._symbol_expr.evalf()
else:
symbol_expr = self._symbol_expr
@@ -579,9 +482,8 @@ def is_real(self):
# 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:
- return True
+ if symbol_expr.imag == 0.0:
+ return True
return False
return symbol_expr.is_real
diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py
index 8c90777eae01..28d28fcd8edb 100644
--- a/qiskit/pulse/library/symbolic_pulses.py
+++ b/qiskit/pulse/library/symbolic_pulses.py
@@ -23,19 +23,14 @@
from copy import deepcopy
import numpy as np
+import symengine as sym
from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.library.pulse import Pulse
from qiskit.pulse.library.waveform import Waveform
-from qiskit.utils import optionals as _optional
from qiskit.utils.deprecation import deprecate_arg
-if _optional.HAS_SYMENGINE:
- import symengine as sym
-else:
- import sympy as sym
-
def _lifted_gaussian(
t: sym.Symbol,
@@ -183,34 +178,31 @@ def __set__(self, instance, value):
continue
params.append(p)
- if _optional.HAS_SYMENGINE:
- try:
- lamb = sym.lambdify(params, [value], real=False)
-
- def _wrapped_lamb(*args):
- if isinstance(args[0], np.ndarray):
- # When the args[0] is a vector ("t"), tile other arguments args[1:]
- # to prevent evaluation from looping over each element in t.
- t = args[0]
- args = np.hstack(
- (
- t.reshape(t.size, 1),
- np.tile(args[1:], t.size).reshape(t.size, len(args) - 1),
- )
+ try:
+ lamb = sym.lambdify(params, [value], real=False)
+
+ def _wrapped_lamb(*args):
+ if isinstance(args[0], np.ndarray):
+ # When the args[0] is a vector ("t"), tile other arguments args[1:]
+ # to prevent evaluation from looping over each element in t.
+ t = args[0]
+ args = np.hstack(
+ (
+ t.reshape(t.size, 1),
+ np.tile(args[1:], t.size).reshape(t.size, len(args) - 1),
)
- return lamb(args)
-
- func = _wrapped_lamb
- except RuntimeError:
- # Currently symengine doesn't support complex_double version for
- # several functions such as comparison operator and piecewise.
- # If expression contains these function, it fall back to sympy lambdify.
- # See https://github.com/symengine/symengine.py/issues/406 for details.
- import sympy
-
- func = sympy.lambdify(params, value)
- else:
- func = sym.lambdify(params, value)
+ )
+ return lamb(args)
+
+ func = _wrapped_lamb
+ except RuntimeError:
+ # Currently symengine doesn't support complex_double version for
+ # several functions such as comparison operator and piecewise.
+ # If expression contains these function, it fall back to sympy lambdify.
+ # See https://github.com/symengine/symengine.py/issues/406 for details.
+ import sympy
+
+ func = sympy.lambdify(params, value)
self.lambda_funcs[key] = func
diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py
index e4fd7363a4d4..98b617081ddb 100644
--- a/qiskit/qpy/binary_io/schedules.py
+++ b/qiskit/qpy/binary_io/schedules.py
@@ -19,6 +19,7 @@
from io import BytesIO
import numpy as np
+import symengine as sym
from qiskit.exceptions import QiskitError
from qiskit.pulse import library, channels, instructions
@@ -26,14 +27,8 @@
from qiskit.qpy import formats, common, type_keys
from qiskit.qpy.binary_io import value
from qiskit.qpy.exceptions import QpyError
-from qiskit.utils import optionals as _optional
from qiskit.pulse.configuration import Kernel, Discriminator
-if _optional.HAS_SYMENGINE:
- import symengine as sym
-else:
- import sympy as sym
-
def _read_channel(file_obj, version):
type_key = common.read_type_key(file_obj)
@@ -112,11 +107,7 @@ def _loads_symbolic_expr(expr_bytes):
expr_txt = zlib.decompress(expr_bytes).decode(common.ENCODE)
expr = parse_expr(expr_txt)
- if _optional.HAS_SYMENGINE:
- from symengine import sympify
-
- return sympify(expr)
- return expr
+ return sym.sympify(expr)
def _read_symbolic_pulse(file_obj, version):
diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py
index 2edac6c81b52..04828bf1dd86 100644
--- a/qiskit/qpy/binary_io/value.py
+++ b/qiskit/qpy/binary_io/value.py
@@ -19,6 +19,7 @@
import uuid
import numpy as np
+import symengine
from qiskit.circuit import CASE_DEFAULT, Clbit, ClassicalRegister
from qiskit.circuit.classical import expr, types
@@ -26,7 +27,6 @@
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
from qiskit.qpy import common, formats, exceptions, type_keys
-from qiskit.utils import optionals as _optional
def _write_parameter(file_obj, obj):
@@ -224,12 +224,7 @@ def _read_parameter_expression(file_obj):
)
from sympy.parsing.sympy_parser import parse_expr
- if _optional.HAS_SYMENGINE:
- import symengine
-
- expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
- else:
- expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))
+ expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
symbol_map = {}
for _ in range(data.map_elements):
elem_data = formats.PARAM_EXPR_MAP_ELEM(
@@ -265,12 +260,7 @@ def _read_parameter_expression_v3(file_obj, vectors):
)
from sympy.parsing.sympy_parser import parse_expr
- if _optional.HAS_SYMENGINE:
- import symengine
-
- expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
- else:
- expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))
+ expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
symbol_map = {}
for _ in range(data.map_elements):
elem_data = formats.PARAM_EXPR_MAP_ELEM_V3(
diff --git a/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml b/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml
new file mode 100644
index 000000000000..2f31d26035a4
--- /dev/null
+++ b/releasenotes/notes/platform-support-f7f693aaf5dec044.yaml
@@ -0,0 +1,28 @@
+---
+upgrade:
+ - |
+ The ``symengine`` library is now a hard requirement for all platforms.
+ Previously, ``symengine`` was required only on platforms that had
+ pre-compiled packages available and ``sympy`` would be used as a fallback
+ if it wasn't installed. This split requirements was resulting in increased
+ complexity as it was necessary to determine which libraries were installed
+ to debug an issue. By requiring symengine for all systems greatly decreases
+ the complexity and optimizes Qiskit for higher performance. However, for
+ users on i686 Linux, 32 bit Windows, and s390x Linux (the platforms without
+ precompiled packages on PyPI) will need to build symengine from source.
+ - |
+ Support for 32 bit platforms, i686 Linux and 32 bit Windows, on
+ Python < 3.10 has been downgraded from Tier 2 to Tier 3, as documented in
+ the :ref:`platform_support` page. This was required due to making
+ ``symengine`` required for all users and needing to install symengine from
+ source on these platforms.
+ - |
+ For macOS the minimum version of macOS is now 10.12. Previously, the
+ precompiled binary wheel packages for macOS x86_64 were published with
+ support for >=10.9. However, because of changes in the
+ `support policy `__
+ for the Rust programming language the minimum version needed to raised
+ to macOS 10.12. If you're using Qiskit on macOS 10.9 you can probably
+ build Qiskit from source while the Qiskit MSRV (minimum supported Rust
+ version) is < 1.74, but the precompiled binaries published to PyPI will
+ only be compatible with macOS >= 10.12.
diff --git a/requirements.txt b/requirements.txt
index d41eee50ce95..7620b00ee3ec 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,7 +8,4 @@ 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.9, <0.10