Finalise support for Numpy 2.0 (#11999)

* Finalise support for Numpy 2.0

This commit brings the Qiskit test suite to a passing state (with all
optionals installed) with Numpy 2.0.0b1, building on previous commits
that handled much of the rest of the changing requirements:

- gh-10890
- gh-10891
- gh-10892
- gh-10897
- gh-11023

Notably, this commit did not actually require a rebuild of Qiskit,
despite us compiling against Numpy; it seems to happen that the C API
stuff we use via `rust-numpy` (which loads the Numpy C extensions
dynamically during module initialisation) hasn't changed.

The main changes are:

- adapting to the changed `copy=None` and `copy=False` semantics in
  `array` and `asarray`.
- making sure all our implementers of `__array__` accept both `dtype`
  and `copy` arguments.

Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com>

* Update `__array__` methods for Numpy 2.0 compatibility

As of Numpy 2.0, implementers of `__array__` are expected and required
to have a signature

    def __array__(self, dtype=None, copy=None): ...

In Numpys before 2.0, the `copy` argument will never be passed, and the
expected signature was

    def __array__(self, dtype=None): ...

Because of this, we have latitude to set `copy` in our implementations
to anything we like if we're running against Numpy 1.x, but we should
default to `copy=None` if we're running against Numpy 2.0.

The semantics of the `copy` argument to `np.array` changed in Numpy 2.0.
Now, `copy=False` means "raise a `ValueError` if a copy is required" and
`copy=None` means "copy only if required".  In Numpy 1.x, `copy=False`
meant "copy only if required".  In _both_ Numpy 1.x and 2.0,
`ndarray.astype` takes a `copy` argument, and in both, `copy=False`
means "copy only if required".  In Numpy 2.0 only, `np.asarray` gained a
`copy` argument with the same semantics as the `np.array` copy argument
from Numpy 2.0.

Further, the semantics of the `__array__` method changed in Numpy 2.0,
particularly around copying.  Now, Numpy will assume that it can pass
`copy=True` and the implementer will handle this.  If `copy=False` is
given and a copy or calculation is required, then the implementer is
required to raise `ValueError`.  We have a few places where the
`__array__` method may (or always does) calculate the array, so in all
these, we must forbid `copy=False`.

With all this in mind: this PR sets up all our implementers of
`__array__` to either default to `copy=None` if they will never actually
need to _use_ the `copy` argument within themselves (except perhaps to
test if it was set by Numpy 2.0 to `False`, as Numpy 1.x will never set
it), or to a compatibility shim `_numpy_compat.COPY_ONLY_IF_NEEDED` if
they do naturally want to use it with those semantics.  The pattern

    def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
        dtype = self._array.dtype if dtype is None else dtype
        return np.array(self._array, dtype=dtype, copy=copy)

using `array` instead of `asarray` lets us achieve all the desired
behaviour between the interactions of `dtype` and `copy` in a way that
is compatible with both Numpy 1.x and 2.x.

* fixing numerical issues on mac-arm

* Change error to match Numpy

---------

Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com>
Co-authored-by: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
This commit is contained in:
Jake Lishman 2024-04-25 20:27:23 +01:00 committed by GitHub
parent f04eaab9c7
commit 686ff139a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 338 additions and 166 deletions

View File

@ -53,6 +53,7 @@ if sys.version_info < (3, 9):
import qiskit._accelerate
import qiskit._numpy_compat
# Globally define compiled submodules. The normal import mechanism will not find compiled submodules
# in _accelerate because it relies on file paths, but PyO3 generates only one shared library file.

73
qiskit/_numpy_compat.py Normal file
View File

@ -0,0 +1,73 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Compatiblity helpers for the Numpy 1.x to 2.0 transition."""
import re
import typing
import warnings
import numpy as np
# This version pattern is taken from the pypa packaging project:
# https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254 which is dual licensed
# Apache 2.0 and BSD see the source for the original authors and other details.
_VERSION_PATTERN = r"""
v?
(?:
(?:(?P<epoch>[0-9]+)!)? # epoch
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?P<post> # post release
(?:-(?P<post_n1>[0-9]+))
|
(?:
[-_\.]?
(?P<post_l>post|rev|r)
[-_\.]?
(?P<post_n2>[0-9]+)?
)
)?
(?P<dev> # dev release
[-_\.]?
(?P<dev_l>dev)
[-_\.]?
(?P<dev_n>[0-9]+)?
)?
)
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
"""
VERSION = np.lib.NumpyVersion(np.__version__)
VERSION_PARTS: typing.Tuple[int, ...]
"""The numeric parts of the Numpy release version, e.g. ``(2, 0, 0)``. Does not include pre- or
post-release markers (e.g. ``rc1``)."""
if match := re.fullmatch(_VERSION_PATTERN, np.__version__, flags=re.VERBOSE | re.IGNORECASE):
# Assuming Numpy won't ever introduce epochs, and we don't care about pre/post markers.
VERSION_PARTS = tuple(int(x) for x in match["release"].split("."))
else:
# Just guess a version. We know all existing Numpys have good version strings, so the only way
# this should trigger is from a new or a dev version.
warnings.warn(
f"Unrecognized version string for Numpy: '{np.__version__}'. Assuming Numpy 2.0.",
RuntimeWarning,
)
VERSION_PARTS = (2, 0, 0)
COPY_ONLY_IF_NEEDED = None if VERSION_PARTS >= (2, 0, 0) else False
"""The sentinel value given to ``np.array`` and ``np.ndarray.astype`` (etc) to indicate that a copy
should be made only if required."""

View File

@ -833,7 +833,7 @@ method <https://numpy.org/devdocs/user/basics.interoperability.html#the-array-me
``__array__``. This is used by :meth:`Gate.to_matrix`, and has the signature:
.. currentmodule:: None
.. py:method:: __array__(dtype=None)
.. py:method:: __array__(dtype=None, copy=None)
Return a Numpy array representing the gate. This can use the gate's :attr:`~Instruction.params`
field, and may assume that these are numeric values (assuming the subclass expects that) and not
@ -875,7 +875,9 @@ are unitary, so should be a :class:`Gate`::
# Also we have an efficient representation of power.
return RXZGate(exponent * self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
cos = math.cos(0.5 * self.params[0])
isin = 1j * math.sin(0.5 * self.params[0])
return np.array([
@ -1340,6 +1342,7 @@ In both these cases, the matrix form of :class:`.CCXGate` in ``ctrl_state = 1``
"""
from .exceptions import CircuitError
from . import _utils
from .quantumcircuit import QuantumCircuit
from .classicalregister import ClassicalRegister, Clbit
from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit

View File

@ -15,6 +15,8 @@ This module contains utility functions for circuits.
import math
import numpy
from qiskit import _numpy_compat
from qiskit.exceptions import QiskitError
from qiskit.circuit.exceptions import CircuitError
from .parametervector import ParameterVectorElement
@ -117,8 +119,9 @@ def with_gate_array(base_array):
nonwritable = numpy.array(base_array, dtype=numpy.complex128)
nonwritable.setflags(write=False)
def __array__(_self, dtype=None):
return numpy.asarray(nonwritable, dtype=dtype)
def __array__(_self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = nonwritable.dtype if dtype is None else dtype
return numpy.array(nonwritable, dtype=dtype, copy=copy)
def decorator(cls):
if hasattr(cls, "__array__"):
@ -149,15 +152,21 @@ def with_controlled_gate_array(base_array, num_ctrl_qubits, cached_states=None):
if cached_states is None:
nonwritables = [matrix_for_control_state(state) for state in range(2**num_ctrl_qubits)]
def __array__(self, dtype=None):
return numpy.asarray(nonwritables[self.ctrl_state], dtype=dtype)
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
arr = nonwritables[self.ctrl_state]
dtype = arr.dtype if dtype is None else dtype
return numpy.array(arr, dtype=dtype, copy=copy)
else:
nonwritables = {state: matrix_for_control_state(state) for state in cached_states}
def __array__(self, dtype=None):
if (out := nonwritables.get(self.ctrl_state)) is not None:
return numpy.asarray(out, dtype=dtype)
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
if (arr := nonwritables.get(self.ctrl_state)) is not None:
dtype = arr.dtype if dtype is None else dtype
return numpy.array(arr, dtype=dtype, copy=copy)
if copy is False and copy is not _numpy_compat.COPY_ONLY_IF_NEEDED:
raise ValueError("could not produce matrix without calculation")
return numpy.asarray(
_compute_control_matrix(base, num_ctrl_qubits, self.ctrl_state), dtype=dtype
)

View File

@ -17,9 +17,11 @@ import numpy as np
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.gate import Gate
from qiskit.circuit import _utils
from qiskit.circuit.parameterexpression import ParameterExpression
@_utils.with_gate_array(np.eye(2, dtype=complex))
class Delay(Instruction):
"""Do nothing and just delay/wait/idle for a specified duration."""
@ -53,10 +55,6 @@ class Delay(Instruction):
"""Set the duration of this delay."""
self.params = [duration]
def __array__(self, dtype=None):
"""Return the identity matrix."""
return np.array([[1, 0], [0, 1]], dtype=dtype)
def to_matrix(self) -> np.ndarray:
"""Return a Numpy.array for the unitary matrix. This has been
added to enable simulation without making delay a full Gate type.

View File

@ -63,13 +63,13 @@ class PauliGate(Gate):
r"""Return inverted pauli gate (itself)."""
return PauliGate(self.params[0]) # self-inverse
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a Numpy.array for the pauli gate.
i.e. tensor product of the paulis"""
# pylint: disable=cyclic-import
from qiskit.quantum_info.operators import Pauli
return Pauli(self.params[0]).__array__(dtype=dtype)
return Pauli(self.params[0]).__array__(dtype=dtype, copy=copy)
def validate_parameter(self, parameter):
if isinstance(parameter, str):

View File

@ -147,8 +147,11 @@ class PermutationGate(Gate):
super().__init__(name="permutation", num_qubits=num_qubits, params=[pattern])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the Permutation gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
nq = len(self.pattern)
mat = np.zeros((2**nq, 2**nq), dtype=dtype)

View File

@ -18,6 +18,7 @@ import math
import typing
import numpy
from qiskit import _numpy_compat
from qiskit.circuit.gate import Gate
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.annotated_operation import AnnotatedOperation, ControlModifier
@ -118,10 +119,10 @@ class UnitaryGate(Gate):
return False
return matrix_equal(self.params[0], other.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
"""Return matrix for the unitary."""
# pylint: disable=unused-argument
return self.params[0]
dtype = self.params[0].dtype if dtype is None else dtype
return numpy.array(self.params[0], dtype=dtype, copy=copy)
def inverse(self, annotated: bool = False):
"""Return the adjoint of the unitary."""

View File

@ -21,6 +21,7 @@ import typing
from numbers import Number
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
@ -92,18 +93,22 @@ class HamiltonianGate(Gate):
times_eq = self.params[1] == other.params[1]
return operators_eq and times_eq
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return matrix for the unitary."""
# pylint: disable=unused-argument
import scipy.linalg
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
try:
return scipy.linalg.expm(-1j * self.params[0] * float(self.params[1]))
time = float(self.params[1])
except TypeError as ex:
raise TypeError(
"Unable to generate Unitary matrix for "
"unbound t parameter {}".format(self.params[1])
) from ex
arr = scipy.linalg.expm(-1j * self.params[0] * time)
dtype = complex if dtype is None else dtype
return np.array(arr, dtype=dtype, copy=_numpy_compat.COPY_ONLY_IF_NEEDED)
def inverse(self, annotated: bool = False):
"""Return the adjoint of the unitary."""

View File

@ -69,10 +69,12 @@ class GlobalPhaseGate(Gate):
"""
return GlobalPhaseGate(-self.params[0])
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the global_phase gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta = self.params[0]
return numpy.array([[numpy.exp(1j * theta)]], dtype=dtype)
return numpy.array([[numpy.exp(1j * theta)]], dtype=dtype or complex)
def __eq__(self, other):
if isinstance(other, GlobalPhaseGate):

View File

@ -140,8 +140,10 @@ class PhaseGate(Gate):
"""
return PhaseGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the Phase gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
lam = float(self.params[0])
return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype)
@ -279,8 +281,10 @@ class CPhaseGate(ControlledGate):
r"""Return inverted CPhase gate (:math:`CPhase(\lambda)^{\dagger} = CPhase(-\lambda)`)"""
return CPhaseGate(-self.params[0], ctrl_state=self.ctrl_state)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CPhase gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
eith = exp(1j * float(self.params[0]))
if self.ctrl_state:
return numpy.array(

View File

@ -93,8 +93,10 @@ class RGate(Gate):
"""
return RGate(-self.params[0], self.params[1])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the R gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, phi = float(self.params[0]), float(self.params[1])
cos = math.cos(theta / 2)
sin = math.sin(theta / 2)

View File

@ -120,8 +120,10 @@ class RXGate(Gate):
"""
return RXGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RX gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
cos = math.cos(self.params[0] / 2)
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=dtype)
@ -263,8 +265,10 @@ class CRXGate(ControlledGate):
"""
return CRXGate(-self.params[0], ctrl_state=self.ctrl_state)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CRX gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
half_theta = float(self.params[0]) / 2
cos = math.cos(half_theta)
isin = 1j * math.sin(half_theta)

View File

@ -122,8 +122,10 @@ class RXXGate(Gate):
"""
return RXXGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a Numpy.array for the RXX gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta2 = float(self.params[0]) / 2
cos = math.cos(theta2)
isin = 1j * math.sin(theta2)

View File

@ -119,8 +119,10 @@ class RYGate(Gate):
"""
return RYGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RY gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
cos = math.cos(self.params[0] / 2)
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)
@ -258,8 +260,10 @@ class CRYGate(ControlledGate):
."""
return CRYGate(-self.params[0], ctrl_state=self.ctrl_state)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CRY gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
half_theta = float(self.params[0]) / 2
cos = math.cos(half_theta)
sin = math.sin(half_theta)

View File

@ -122,8 +122,10 @@ class RYYGate(Gate):
"""
return RYYGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RYY gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta = float(self.params[0])
cos = math.cos(theta / 2)
isin = 1j * math.sin(theta / 2)

View File

@ -130,10 +130,12 @@ class RZGate(Gate):
"""
return RZGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RZ gate."""
import numpy as np
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
ilam2 = 0.5j * float(self.params[0])
return np.array([[exp(-ilam2), 0], [0, exp(ilam2)]], dtype=dtype)
@ -276,10 +278,12 @@ class CRZGate(ControlledGate):
"""
return CRZGate(-self.params[0], ctrl_state=self.ctrl_state)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CRZ gate."""
import numpy
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
arg = 1j * float(self.params[0]) / 2
if self.ctrl_state:
return numpy.array(

View File

@ -166,10 +166,12 @@ class RZXGate(Gate):
"""
return RZXGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RZX gate."""
import numpy
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
half_theta = float(self.params[0]) / 2
cos = math.cos(half_theta)
isin = 1j * math.sin(half_theta)

View File

@ -130,10 +130,12 @@ class RZZGate(Gate):
"""
return RZZGate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the RZZ gate."""
import numpy
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
itheta2 = 1j * float(self.params[0]) / 2
return numpy.array(
[

View File

@ -12,7 +12,7 @@
"""Two-pulse single-qubit gate."""
import cmath
import copy
import copy as _copy
import math
from cmath import exp
from typing import Optional, Union
@ -136,8 +136,10 @@ class UGate(Gate):
)
return gate
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the U gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, phi, lam = (float(param) for param in self.params)
cos = math.cos(theta / 2)
sin = math.sin(theta / 2)
@ -146,7 +148,7 @@ class UGate(Gate):
[cos, -exp(1j * lam) * sin],
[exp(1j * phi) * sin, exp(1j * (phi + lam)) * cos],
],
dtype=dtype,
dtype=dtype or complex,
)
def __eq__(self, other):
@ -337,8 +339,10 @@ class CUGate(ControlledGate):
ctrl_state=self.ctrl_state,
)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CU gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, phi, lam, gamma = (float(param) for param in self.params)
cos = math.cos(theta / 2)
sin = math.sin(theta / 2)
@ -372,5 +376,5 @@ class CUGate(ControlledGate):
# assuming that `params` will be a view onto the base gate's `_params`.
memo = memo if memo is not None else {}
out = super().__deepcopy__(memo)
out._params = copy.deepcopy(out._params, memo)
out._params = _copy.deepcopy(out._params, memo)
return out

View File

@ -160,8 +160,10 @@ class U1Gate(Gate):
"""
return U1Gate(-self.params[0])
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the U1 gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
lam = float(self.params[0])
return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=dtype)
@ -304,8 +306,10 @@ class CU1Gate(ControlledGate):
"""
return CU1Gate(-self.params[0], ctrl_state=self.ctrl_state)
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CU1 gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
eith = exp(1j * float(self.params[0]))
if self.ctrl_state:
return numpy.array(

View File

@ -127,8 +127,10 @@ class U2Gate(Gate):
"""
return U2Gate(-self.params[1] - pi, -self.params[0] + pi)
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a Numpy.array for the U2 gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
isqrt2 = 1 / sqrt(2)
phi, lam = self.params
phi, lam = float(phi), float(lam)
@ -137,5 +139,5 @@ class U2Gate(Gate):
[isqrt2, -exp(1j * lam) * isqrt2],
[exp(1j * phi) * isqrt2, exp(1j * (phi + lam)) * isqrt2],
],
dtype=dtype,
dtype=dtype or complex,
)

View File

@ -149,8 +149,10 @@ class U3Gate(Gate):
qc.u(self.params[0], self.params[1], self.params[2], 0)
self.definition = qc
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a Numpy.array for the U3 gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, phi, lam = self.params
theta, phi, lam = float(theta), float(phi), float(lam)
cos = math.cos(theta / 2)
@ -160,7 +162,7 @@ class U3Gate(Gate):
[cos, -exp(1j * lam) * sin],
[exp(1j * phi) * sin, exp(1j * (phi + lam)) * cos],
],
dtype=dtype,
dtype=dtype or complex,
)
@ -305,8 +307,10 @@ class CU3Gate(ControlledGate):
-self.params[0], -self.params[2], -self.params[1], ctrl_state=self.ctrl_state
)
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the CU3 gate."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, phi, lam = self.params
theta, phi, lam = float(theta), float(phi), float(lam)
cos = math.cos(theta / 2)
@ -319,7 +323,7 @@ class CU3Gate(ControlledGate):
[0, 0, 1, 0],
[0, exp(1j * phi) * sin, 0, exp(1j * (phi + lam)) * cos],
],
dtype=dtype,
dtype=dtype or complex,
)
else:
return numpy.array(
@ -329,7 +333,7 @@ class CU3Gate(ControlledGate):
[exp(1j * phi) * sin, 0, exp(1j * (phi + lam)) * cos, 0],
[0, 0, 0, 1],
],
dtype=dtype,
dtype=dtype or complex,
)

View File

@ -169,8 +169,10 @@ class XXMinusYYGate(Gate):
theta, beta = self.params
return XXMinusYYGate(-theta, beta)
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Gate matrix."""
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
theta, beta = self.params
cos = math.cos(theta / 2)
sin = math.sin(theta / 2)

View File

@ -15,6 +15,9 @@ import math
from cmath import exp
from math import pi
from typing import Optional
import numpy
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
@ -167,10 +170,10 @@ class XXPlusYYGate(Gate):
"""
return XXPlusYYGate(-self.params[0], self.params[1])
def __array__(self, dtype=complex):
def __array__(self, dtype=None, copy=None):
"""Return a numpy.array for the XX+YY gate."""
import numpy
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
half_theta = float(self.params[0]) / 2
beta = float(self.params[1])
cos = math.cos(half_theta)

View File

@ -268,7 +268,7 @@ def _memory_array(results: list[list[str]], num_bytes: int) -> NDArray[np.uint8]
# no measure in a circuit
data = np.zeros((len(memory), num_bytes), dtype=np.uint8)
lst.append(data)
ary = np.array(lst, copy=False)
ary = np.asarray(lst)
return np.unpackbits(ary, axis=-1, bitorder="big")

View File

@ -101,10 +101,10 @@ class ObservablesArray(ShapedMixin):
"""Convert to a nested list"""
return self._array.tolist()
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Convert to an Numpy.ndarray"""
if dtype is None or dtype == object:
return self._array
return self._array.copy() if copy else self._array
raise ValueError("Type must be 'None' or 'object'")
@overload

View File

@ -85,7 +85,7 @@ def array_coerce(arr: ArrayLike | Shaped) -> NDArray | Shaped:
"""
if isinstance(arr, Shaped):
return arr
return np.array(arr, copy=False)
return np.asarray(arr)
def _flatten_to_ints(arg: ShapeInput) -> Iterable[int]:

View File

@ -16,10 +16,11 @@ Chi-matrix representation of a Quantum Channel.
"""
from __future__ import annotations
import copy
import copy as _copy
import math
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
@ -131,10 +132,9 @@ class Chi(QuantumChannel):
raise QiskitError("Input is not an n-qubit Chi matrix.")
super().__init__(chi_mat, num_qubits=num_qubits)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype
return np.array(self.data, dtype=dtype, copy=copy)
@property
def _bipartite_shape(self):
@ -181,7 +181,7 @@ class Chi(QuantumChannel):
@classmethod
def _tensor(cls, a, b):
ret = copy.copy(a)
ret = _copy.copy(a)
ret._op_shape = a._op_shape.tensor(b._op_shape)
ret._data = np.kron(a._data, b.data)
return ret

View File

@ -16,10 +16,11 @@ Choi-matrix representation of a Quantum Channel.
"""
from __future__ import annotations
import copy
import copy as _copy
import math
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
@ -134,10 +135,9 @@ class Choi(QuantumChannel):
choi_mat = _to_choi(rep, data._data, input_dim, output_dim)
super().__init__(choi_mat, op_shape=op_shape)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
@property
def _bipartite_shape(self):
@ -152,12 +152,12 @@ class Choi(QuantumChannel):
# ---------------------------------------------------------------------
def conjugate(self):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.conj(self._data)
return ret
def transpose(self):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._op_shape = self._op_shape.transpose()
# Make bipartite matrix
d_in, d_out = self.dim
@ -206,7 +206,7 @@ class Choi(QuantumChannel):
@classmethod
def _tensor(cls, a, b):
ret = copy.copy(a)
ret = _copy.copy(a)
ret._op_shape = a._op_shape.tensor(b._op_shape)
ret._data = _bipartite_tensor(
a._data, b.data, shape1=a._bipartite_shape, shape2=b._bipartite_shape

View File

@ -16,10 +16,11 @@ Pauli Transfer Matrix (PTM) representation of a Quantum Channel.
"""
from __future__ import annotations
import copy
import copy as _copy
import math
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
@ -133,10 +134,9 @@ class PTM(QuantumChannel):
raise QiskitError("Input is not an n-qubit Pauli transfer matrix.")
super().__init__(ptm, num_qubits=num_qubits)
def __array__(self, dtype=None):
if dtype:
np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
@property
def _bipartite_shape(self):
@ -194,7 +194,7 @@ class PTM(QuantumChannel):
@classmethod
def _tensor(cls, a, b):
ret = copy.copy(a)
ret = _copy.copy(a)
ret._op_shape = a._op_shape.tensor(b._op_shape)
ret._data = np.kron(a._data, b.data)
return ret

View File

@ -15,12 +15,13 @@ Superoperator representation of a Quantum Channel."""
from __future__ import annotations
import copy
import copy as _copy
import math
from typing import TYPE_CHECKING
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.exceptions import QiskitError
@ -127,10 +128,9 @@ class SuperOp(QuantumChannel):
# Initialize QuantumChannel
super().__init__(super_mat, op_shape=op_shape)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
@property
def _tensor_shape(self):
@ -149,18 +149,18 @@ class SuperOp(QuantumChannel):
# ---------------------------------------------------------------------
def conjugate(self):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.conj(self._data)
return ret
def transpose(self):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.transpose(self._data)
ret._op_shape = self._op_shape.transpose()
return ret
def adjoint(self):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.conj(np.transpose(self._data))
ret._op_shape = self._op_shape.transpose()
return ret
@ -177,7 +177,7 @@ class SuperOp(QuantumChannel):
@classmethod
def _tensor(cls, a, b):
ret = copy.copy(a)
ret = _copy.copy(a)
ret._op_shape = a._op_shape.tensor(b._op_shape)
ret._data = _bipartite_tensor(
a._data, b.data, shape1=a._bipartite_shape, shape2=b._bipartite_shape

View File

@ -357,10 +357,11 @@ class CNOTDihedral(BaseOperator, AdjointMixin):
_append_circuit(elem, circuit)
return elem
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.to_matrix(), dtype=dtype)
return self.to_matrix()
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
arr = self.to_matrix()
return arr if dtype is None else arr.astype(dtype, copy=False)
def to_matrix(self):
"""Convert operator to Numpy matrix."""

View File

@ -16,13 +16,14 @@ Matrix Operator class.
from __future__ import annotations
import copy
import copy as _copy
import re
from numbers import Number
from typing import TYPE_CHECKING
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.library.standard_gates import HGate, IGate, SGate, TGate, XGate, YGate, ZGate
from qiskit.circuit.operation import Operation
@ -117,10 +118,9 @@ class Operator(LinearOp):
shape=self._data.shape,
)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
def __repr__(self):
prefix = "Operator("
@ -447,13 +447,13 @@ class Operator(LinearOp):
def conjugate(self):
# Make a shallow copy and update array
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.conj(self._data)
return ret
def transpose(self):
# Make a shallow copy and update array
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.transpose(self._data)
ret._op_shape = self._op_shape.transpose()
return ret
@ -523,7 +523,7 @@ class Operator(LinearOp):
"""
if self.input_dims() != self.output_dims():
raise QiskitError("Can only power with input_dims = output_dims.")
ret = copy.copy(self)
ret = _copy.copy(self)
if isinstance(n, int):
ret._data = np.linalg.matrix_power(self.data, n)
else:
@ -550,7 +550,7 @@ class Operator(LinearOp):
@classmethod
def _tensor(cls, a, b):
ret = copy.copy(a)
ret = _copy.copy(a)
ret._op_shape = a._op_shape.tensor(b._op_shape)
ret._data = np.kron(a.data, b.data)
return ret
@ -585,7 +585,7 @@ class Operator(LinearOp):
self._op_shape._validate_add(other._op_shape, qargs)
other = ScalarOp._pad_with_identity(self, other, qargs)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = self.data + other.data
return ret
@ -603,7 +603,7 @@ class Operator(LinearOp):
"""
if not isinstance(other, Number):
raise QiskitError("other is not a number")
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = other * self._data
return ret
@ -643,7 +643,7 @@ class Operator(LinearOp):
Returns:
Operator: the operator with reversed subsystem order.
"""
ret = copy.copy(self)
ret = _copy.copy(self)
axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
axes = axes + tuple(len(axes) + i for i in axes)
ret._data = np.reshape(

View File

@ -15,7 +15,7 @@ ScalarOp class
"""
from __future__ import annotations
import copy
import copy as _copy
from numbers import Number
import numpy as np
@ -52,10 +52,11 @@ class ScalarOp(LinearOp):
self._coeff = coeff
super().__init__(input_dims=dims, output_dims=dims)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.to_matrix(), dtype=dtype)
return self.to_matrix()
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("could not produce matrix without calculation")
arr = self.to_matrix()
return arr if dtype is None else arr.astype(dtype, copy=False)
def __repr__(self):
return f"ScalarOp({self.input_dims()}, coeff={self.coeff})"
@ -104,7 +105,7 @@ class ScalarOp(LinearOp):
# If other is also an ScalarOp we only need to
# update the coefficient and dimensions
if isinstance(other, ScalarOp):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._coeff = self.coeff * other.coeff
ret._op_shape = new_shape
return ret
@ -112,7 +113,7 @@ class ScalarOp(LinearOp):
# If we are composing on the full system we return the
# other operator with reshaped dimensions
if qargs is None:
ret = copy.copy(other)
ret = _copy.copy(other)
ret._op_shape = new_shape
# Other operator might not support scalar multiplication
# so we treat the identity as a special case to avoid a
@ -148,7 +149,7 @@ class ScalarOp(LinearOp):
other = Operator(other)
if isinstance(other, ScalarOp):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._coeff = self.coeff * other.coeff
ret._op_shape = self._op_shape.tensor(other._op_shape)
return ret
@ -160,7 +161,7 @@ class ScalarOp(LinearOp):
other = Operator(other)
if isinstance(other, ScalarOp):
ret = copy.copy(self)
ret = _copy.copy(self)
ret._coeff = self.coeff * other.coeff
ret._op_shape = self._op_shape.expand(other._op_shape)
return ret

View File

@ -122,10 +122,11 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
_COMPOSE_PHASE_LOOKUP = None
_COMPOSE_1Q_LOOKUP = None
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.to_matrix(), dtype=dtype)
return self.to_matrix()
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
arr = self.to_matrix()
return arr if dtype is None else arr.astype(dtype, copy=False)
def __init__(self, data, validate=True, copy=True):
"""Initialize an operator object."""
@ -164,8 +165,17 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
# Initialize StabilizerTable directly from the data
else:
if isinstance(data, (list, np.ndarray)) and np.asarray(data, dtype=bool).ndim == 2:
data = np.array(data, dtype=bool, copy=copy)
if (
isinstance(data, (list, np.ndarray))
and (data_asarray := np.asarray(data, dtype=bool)).ndim == 2
):
# This little dance is to avoid Numpy 1/2 incompatiblities between the availability
# and meaning of the 'copy' argument in 'array' and 'asarray', when the input needs
# its dtype converting. 'asarray' prefers to return 'self' if possible in both.
if copy and np.may_share_memory(data, data_asarray):
data = data_asarray.copy()
else:
data = data_asarray
if data.shape[0] == data.shape[1]:
self.tableau = self._stack_table_phase(
data, np.zeros(data.shape[0], dtype=bool)

View File

@ -222,10 +222,11 @@ class Pauli(BasePauli):
return front + "..."
return self.to_label()
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.to_matrix(), dtype=dtype)
return self.to_matrix()
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
arr = self.to_matrix()
return arr if dtype is None else arr.astype(dtype, copy=False)
@classmethod
def set_truncation(cls, val: int):

View File

@ -148,14 +148,15 @@ class PauliList(BasePauli, LinearMixin, GroupMixin):
"""Return settings."""
return {"data": self.to_labels()}
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""Convert to numpy array"""
# pylint: disable=unused-argument
if copy is False:
raise ValueError("cannot provide a matrix without calculation")
shape = (len(self),) + 2 * (2**self.num_qubits,)
ret = np.zeros(shape, dtype=complex)
for i, mat in enumerate(self.matrix_iter()):
ret[i] = mat
return ret
return ret if dtype is None else ret.astype(dtype, copy=False)
@staticmethod
def _from_paulis(data):

View File

@ -142,7 +142,12 @@ class SparsePauliOp(LinearOp):
if coeffs is None:
coeffs = np.ones(pauli_list.size, dtype=complex)
else:
coeffs = np.array(coeffs, copy=copy, dtype=dtype)
coeffs_asarray = np.asarray(coeffs, dtype=dtype)
coeffs = (
coeffs_asarray.copy()
if copy and np.may_share_memory(coeffs, coeffs_asarray)
else coeffs_asarray
)
if ignore_pauli_phase:
# Fast path used in copy operations, where the phase of the PauliList is already known
@ -166,10 +171,11 @@ class SparsePauliOp(LinearOp):
# Initialize LinearOp
super().__init__(num_qubits=self._pauli_list.num_qubits)
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.to_matrix(), dtype=dtype)
return self.to_matrix()
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
arr = self.to_matrix()
return arr if dtype is None else arr.astype(dtype, copy=False)
def __repr__(self):
prefix = "SparsePauliOp("

View File

@ -15,10 +15,11 @@ DensityMatrix quantum state class.
"""
from __future__ import annotations
import copy
import copy as _copy
from numbers import Number
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
@ -110,10 +111,9 @@ class DensityMatrix(QuantumState, TolerancesMixin):
raise QiskitError("Invalid DensityMatrix input: not a square matrix.")
super().__init__(op_shape=OpShape.auto(shape=self._data.shape, dims_l=dims, dims_r=dims))
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
def __eq__(self, other):
return super().__eq__(other) and np.allclose(
@ -241,7 +241,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
"""
if not isinstance(other, DensityMatrix):
other = DensityMatrix(other)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.kron(self._data, other._data)
ret._op_shape = self._op_shape.tensor(other._op_shape)
return ret
@ -260,7 +260,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
"""
if not isinstance(other, DensityMatrix):
other = DensityMatrix(other)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = np.kron(other._data, self._data)
ret._op_shape = self._op_shape.expand(other._op_shape)
return ret
@ -281,7 +281,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
if not isinstance(other, DensityMatrix):
other = DensityMatrix(other)
self._op_shape._validate_add(other._op_shape)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = self.data + other.data
return ret
@ -299,7 +299,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
"""
if not isinstance(other, Number):
raise QiskitError("other is not a number")
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = other * self.data
return ret
@ -356,7 +356,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
Returns:
DensityMatrix: the state with reversed subsystem order.
"""
ret = copy.copy(self)
ret = _copy.copy(self)
axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
axes = axes + tuple(len(axes) + i for i in axes)
ret._data = np.reshape(
@ -523,7 +523,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
"""
if qargs is None:
# Resetting all qubits does not require sampling or RNG
ret = copy.copy(self)
ret = _copy.copy(self)
state = np.zeros(self._op_shape.shape, dtype=complex)
state[0, 0] = 1
ret._data = state
@ -715,7 +715,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
new_shape._dims_r = new_shape._dims_l
new_shape._num_qargs_r = new_shape._num_qargs_l
ret = copy.copy(self)
ret = _copy.copy(self)
if qargs is None:
# Evolution on full matrix
op_mat = other.data
@ -792,7 +792,7 @@ class DensityMatrix(QuantumState, TolerancesMixin):
"""Return a new statevector by applying an instruction."""
if isinstance(obj, QuantumCircuit):
obj = obj.to_instruction()
vec = copy.copy(self)
vec = _copy.copy(self)
vec._append_instruction(obj, qargs=qargs)
return vec

View File

@ -14,13 +14,14 @@
Statevector quantum state class.
"""
from __future__ import annotations
import copy
import copy as _copy
import math
import re
from numbers import Number
import numpy as np
from qiskit import _numpy_compat
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
@ -104,10 +105,9 @@ class Statevector(QuantumState, TolerancesMixin):
raise QiskitError("Invalid input: not a vector or column-vector.")
super().__init__(op_shape=OpShape.auto(shape=shape, dims_l=dims, num_qubits_r=0))
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __array__(self, dtype=None, copy=_numpy_compat.COPY_ONLY_IF_NEEDED):
dtype = self.data.dtype if dtype is None else dtype
return np.array(self.data, dtype=dtype, copy=copy)
def __eq__(self, other):
return super().__eq__(other) and np.allclose(
@ -277,7 +277,7 @@ class Statevector(QuantumState, TolerancesMixin):
"""
if not isinstance(other, Statevector):
other = Statevector(other)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._op_shape = self._op_shape.tensor(other._op_shape)
ret._data = np.kron(self._data, other._data)
return ret
@ -318,7 +318,7 @@ class Statevector(QuantumState, TolerancesMixin):
"""
if not isinstance(other, Statevector):
other = Statevector(other)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._op_shape = self._op_shape.expand(other._op_shape)
ret._data = np.kron(other._data, self._data)
return ret
@ -339,7 +339,7 @@ class Statevector(QuantumState, TolerancesMixin):
if not isinstance(other, Statevector):
other = Statevector(other)
self._op_shape._validate_add(other._op_shape)
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = self.data + other.data
return ret
@ -357,7 +357,7 @@ class Statevector(QuantumState, TolerancesMixin):
"""
if not isinstance(other, Number):
raise QiskitError("other is not a number")
ret = copy.copy(self)
ret = _copy.copy(self)
ret._data = other * self.data
return ret
@ -382,7 +382,7 @@ class Statevector(QuantumState, TolerancesMixin):
qargs = getattr(other, "qargs", None)
# Get return vector
ret = copy.copy(self)
ret = _copy.copy(self)
# Evolution by a circuit or instruction
if isinstance(other, QuantumCircuit):
@ -448,7 +448,7 @@ class Statevector(QuantumState, TolerancesMixin):
Returns:
Statevector: the Statevector with reversed subsystem order.
"""
ret = copy.copy(self)
ret = _copy.copy(self)
axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
ret._data = np.reshape(
np.transpose(np.reshape(self.data, self._op_shape.tensor_shape), axes),
@ -619,7 +619,7 @@ class Statevector(QuantumState, TolerancesMixin):
"""
if qargs is None:
# Resetting all qubits does not require sampling or RNG
ret = copy.copy(self)
ret = _copy.copy(self)
state = np.zeros(self._op_shape.shape, dtype=complex)
state[0] = 1
ret._data = state

View File

@ -33,7 +33,7 @@ def _num_to_latex(raw_value, decimals=15, first_term=True, coefficient=False):
"""
import sympy # runtime import
raw_value = np.around(raw_value, decimals=decimals)
raw_value = np.around(raw_value, decimals=decimals).item()
value = sympy.nsimplify(raw_value, rational=False)
if isinstance(value, sympy.core.numbers.Rational) and value.denominator > 50:

View File

@ -0,0 +1,5 @@
---
features:
- |
This release of Qiskit finalizes support for NumPy 2.0. Qiskit will continue to support both
Numpy 1.x and 2.x for the foreseeable future.

View File

@ -1,5 +1,5 @@
rustworkx>=0.14.0
numpy>=1.17,<2
numpy>=1.17,<3
scipy>=1.5
sympy>=1.3
dill>=0.3

View File

@ -19,6 +19,7 @@ from test import combine
from ddt import ddt, data
from numpy.testing import assert_array_max_ulp
from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.circuit.library.standard_gates import (
CXGate,
@ -66,9 +67,11 @@ class TestBackendV2(QiskitTestCase):
def test_qubit_properties(self):
"""Test that qubit properties are returned as expected."""
props = self.backend.qubit_properties([1, 0])
self.assertEqual([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
self.assertEqual([0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props])
self.assertEqual([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
assert_array_max_ulp([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
assert_array_max_ulp(
[0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props]
)
assert_array_max_ulp([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
def test_legacy_qubit_properties(self):
"""Test that qubit props work for backends not using properties in target."""
@ -82,9 +85,11 @@ class TestBackendV2(QiskitTestCase):
return [self.target.qubit_properties[i] for i in qubit]
props = FakeBackendV2LegacyQubitProps(num_qubits=2, seed=42).qubit_properties([1, 0])
self.assertEqual([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
self.assertEqual([0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props])
self.assertEqual([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
assert_array_max_ulp([0.0001697368029059364, 0.00017739560485559633], [x.t1 for x in props])
assert_array_max_ulp(
[0.00010941773478876496, 0.00014388784397520525], [x.t2 for x in props]
)
assert_array_max_ulp([5487811175.818378, 5429298959.955691], [x.frequency for x in props])
def test_no_qubit_properties_raises(self):
"""Ensure that if no qubit properties are defined we raise correctly."""

View File

@ -447,7 +447,9 @@ class TestPadDynamicalDecoupling(QiskitTestCase):
representation to satisfy PadDynamicalDecoupling's check.
"""
def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("cannot produce matrix without calculation")
return np.eye(2, dtype=dtype)
# A gate with one unbound and one bound parameter to leave in the final