From c092aa13730251bff6622fdccf48a32b30e626c5 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 12 Mar 2024 17:43:35 +0000 Subject: [PATCH 1/4] 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> --- qiskit/primitives/backend_sampler_v2.py | 2 +- qiskit/primitives/containers/shape.py | 2 +- .../quantum_info/operators/symplectic/clifford.py | 13 +++++++++++-- .../operators/symplectic/sparse_pauli_op.py | 7 ++++++- qiskit/visualization/array.py | 2 +- releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml | 5 +++++ requirements.txt | 4 ++-- 7 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py index 23861aa34f45..ad7546b302b9 100644 --- a/qiskit/primitives/backend_sampler_v2.py +++ b/qiskit/primitives/backend_sampler_v2.py @@ -213,7 +213,7 @@ def _prepare_memory(results: list[Result], num_bytes: int) -> NDArray[np.uint8]: # no measure in a circuit data = np.zeros((exp.shots, 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") diff --git a/qiskit/primitives/containers/shape.py b/qiskit/primitives/containers/shape.py index 952916cd67dc..6d893f46c13f 100644 --- a/qiskit/primitives/containers/shape.py +++ b/qiskit/primitives/containers/shape.py @@ -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]: diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index f0d30cf59f0c..815be0075d26 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -164,8 +164,17 @@ def __init__(self, data, validate=True, copy=True): # 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) diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index 0d29cd098dc3..beb78d51c310 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -142,7 +142,12 @@ def __init__( 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 diff --git a/qiskit/visualization/array.py b/qiskit/visualization/array.py index b076e38b174d..3a8ef2917156 100644 --- a/qiskit/visualization/array.py +++ b/qiskit/visualization/array.py @@ -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: diff --git a/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml b/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml new file mode 100644 index 000000000000..3595f2f936bd --- /dev/null +++ b/releasenotes/notes/numpy-2.0-2f3e35bd42c48518.yaml @@ -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. diff --git a/requirements.txt b/requirements.txt index 0a4f0f32c5a9..539f9587994d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ rustworkx>=0.14.0 -numpy>=1.17,<2 +numpy>=1.17,<3 scipy>=1.5 sympy>=1.3 dill>=0.3 python-dateutil>=2.8.0 stevedore>=3.0.0 typing-extensions -symengine>=0.11 \ No newline at end of file +symengine>=0.11 From 65adaca67ed258cf71b48481a7ce4d219e158851 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 15 Apr 2024 18:43:45 +0100 Subject: [PATCH 2/4] 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. --- qiskit/__init__.py | 1 + qiskit/_numpy_compat.py | 73 +++++++++++++++++++ qiskit/circuit/__init__.py | 7 +- qiskit/circuit/_utils.py | 23 ++++-- qiskit/circuit/delay.py | 6 +- .../library/generalized_gates/pauli.py | 4 +- .../library/generalized_gates/permutation.py | 5 +- .../library/generalized_gates/unitary.py | 7 +- qiskit/circuit/library/hamiltonian_gate.py | 11 ++- .../library/standard_gates/global_phase.py | 6 +- qiskit/circuit/library/standard_gates/p.py | 8 +- qiskit/circuit/library/standard_gates/r.py | 4 +- qiskit/circuit/library/standard_gates/rx.py | 8 +- qiskit/circuit/library/standard_gates/rxx.py | 4 +- qiskit/circuit/library/standard_gates/ry.py | 8 +- qiskit/circuit/library/standard_gates/ryy.py | 4 +- qiskit/circuit/library/standard_gates/rz.py | 8 +- qiskit/circuit/library/standard_gates/rzx.py | 4 +- qiskit/circuit/library/standard_gates/rzz.py | 4 +- qiskit/circuit/library/standard_gates/u.py | 14 ++-- qiskit/circuit/library/standard_gates/u1.py | 8 +- qiskit/circuit/library/standard_gates/u2.py | 6 +- qiskit/circuit/library/standard_gates/u3.py | 14 ++-- .../library/standard_gates/xx_minus_yy.py | 4 +- .../library/standard_gates/xx_plus_yy.py | 9 ++- .../containers/observables_array.py | 4 +- qiskit/quantum_info/operators/channel/chi.py | 12 +-- qiskit/quantum_info/operators/channel/choi.py | 16 ++-- qiskit/quantum_info/operators/channel/ptm.py | 12 +-- .../quantum_info/operators/channel/superop.py | 18 ++--- .../operators/dihedral/dihedral.py | 9 ++- qiskit/quantum_info/operators/operator.py | 24 +++--- qiskit/quantum_info/operators/scalar_op.py | 19 ++--- .../operators/symplectic/clifford.py | 9 ++- .../operators/symplectic/pauli.py | 9 ++- .../operators/symplectic/pauli_list.py | 7 +- .../operators/symplectic/sparse_pauli_op.py | 9 ++- qiskit/quantum_info/states/densitymatrix.py | 26 +++---- qiskit/quantum_info/states/statevector.py | 24 +++--- .../transpiler/test_dynamical_decoupling.py | 4 +- 40 files changed, 300 insertions(+), 152 deletions(-) create mode 100644 qiskit/_numpy_compat.py diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 9ef3487361c9..b57d924dd7e9 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -53,6 +53,7 @@ import qiskit._accelerate +import qiskit._numpy_compat # Globally define compiled submodules. The normal import mechanism will not find compiled submodules diff --git a/qiskit/_numpy_compat.py b/qiskit/_numpy_compat.py new file mode 100644 index 000000000000..a6c06671c986 --- /dev/null +++ b/qiskit/_numpy_compat.py @@ -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[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[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."""
diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py
index 2e8603af9f7d..928863572fca 100644
--- a/qiskit/circuit/__init__.py
+++ b/qiskit/circuit/__init__.py
@@ -833,7 +833,7 @@
 ``__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 @@ def power(self, exponent: float):
             # 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("cannot produce a matrix without calculation")
             cos = math.cos(0.5 * self.params[0])
             isin = 1j * math.sin(0.5 * self.params[0])
             return np.array([
@@ -1340,6 +1342,7 @@ def __array__(self, dtype=None):
 """
 
 from .exceptions import CircuitError
+from . import _utils
 from .quantumcircuit import QuantumCircuit
 from .classicalregister import ClassicalRegister, Clbit
 from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
diff --git a/qiskit/circuit/_utils.py b/qiskit/circuit/_utils.py
index cfde85bad8dd..86a058e88525 100644
--- a/qiskit/circuit/_utils.py
+++ b/qiskit/circuit/_utils.py
@@ -15,6 +15,8 @@
 
 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 matrix_for_control_state(state):
     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
             )
diff --git a/qiskit/circuit/delay.py b/qiskit/circuit/delay.py
index 16d84d15cbe5..a333125a5a2b 100644
--- a/qiskit/circuit/delay.py
+++ b/qiskit/circuit/delay.py
@@ -17,9 +17,11 @@
 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 @@ def duration(self, duration):
         """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.
diff --git a/qiskit/circuit/library/generalized_gates/pauli.py b/qiskit/circuit/library/generalized_gates/pauli.py
index e8b063a75e9e..01bbd09c1979 100644
--- a/qiskit/circuit/library/generalized_gates/pauli.py
+++ b/qiskit/circuit/library/generalized_gates/pauli.py
@@ -63,13 +63,13 @@ def inverse(self, annotated: bool = False):
         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):
diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py
index 8888344c78bc..edfe9cafac67 100644
--- a/qiskit/circuit/library/generalized_gates/permutation.py
+++ b/qiskit/circuit/library/generalized_gates/permutation.py
@@ -147,8 +147,11 @@ def __init__(
 
         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("cannot produce matrix without calculation")
+
         nq = len(self.pattern)
         mat = np.zeros((2**nq, 2**nq), dtype=dtype)
 
diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py
index 618041142227..1fd36e52e0c0 100644
--- a/qiskit/circuit/library/generalized_gates/unitary.py
+++ b/qiskit/circuit/library/generalized_gates/unitary.py
@@ -18,6 +18,7 @@
 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 @@ def __eq__(self, other):
             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."""
diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py
index a87504a97b36..35e23a0bbaea 100644
--- a/qiskit/circuit/library/hamiltonian_gate.py
+++ b/qiskit/circuit/library/hamiltonian_gate.py
@@ -21,6 +21,7 @@
 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 @@ def __eq__(self, other):
         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("cannot produce matrix without calculation")
         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."""
diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py
index 50576bf17ab4..7d81c9242837 100644
--- a/qiskit/circuit/library/standard_gates/global_phase.py
+++ b/qiskit/circuit/library/standard_gates/global_phase.py
@@ -69,10 +69,12 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         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):
diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py
index 1db0e7023f13..fcec63269f6d 100644
--- a/qiskit/circuit/library/standard_gates/p.py
+++ b/qiskit/circuit/library/standard_gates/p.py
@@ -140,8 +140,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         lam = float(self.params[0])
         return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype)
 
@@ -280,8 +282,10 @@ def inverse(self, annotated: bool = False):
         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("cannot produce matrix without calculation")
         eith = exp(1j * float(self.params[0]))
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py
index a9b825a5484b..ba7c5501badc 100644
--- a/qiskit/circuit/library/standard_gates/r.py
+++ b/qiskit/circuit/library/standard_gates/r.py
@@ -93,8 +93,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         theta, phi = float(self.params[0]), float(self.params[1])
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py
index 6ef963fa6bdf..aac37f9948cd 100644
--- a/qiskit/circuit/library/standard_gates/rx.py
+++ b/qiskit/circuit/library/standard_gates/rx.py
@@ -120,8 +120,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         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)
@@ -264,8 +266,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         isin = 1j * math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py
index ddfe312291fd..d4fdd40618e2 100644
--- a/qiskit/circuit/library/standard_gates/rxx.py
+++ b/qiskit/circuit/library/standard_gates/rxx.py
@@ -122,8 +122,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         theta2 = float(self.params[0]) / 2
         cos = math.cos(theta2)
         isin = 1j * math.sin(theta2)
diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py
index 41e3b2598d15..a3212bdd8c3b 100644
--- a/qiskit/circuit/library/standard_gates/ry.py
+++ b/qiskit/circuit/library/standard_gates/ry.py
@@ -119,8 +119,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         cos = math.cos(self.params[0] / 2)
         sin = math.sin(self.params[0] / 2)
         return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)
@@ -259,8 +261,10 @@ def inverse(self, annotated: bool = False):
         ."""
         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("cannot produce matrix without calculation")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         sin = math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py
index 059f5c117959..788fa6dfc0cb 100644
--- a/qiskit/circuit/library/standard_gates/ryy.py
+++ b/qiskit/circuit/library/standard_gates/ryy.py
@@ -122,8 +122,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         theta = float(self.params[0])
         cos = math.cos(theta / 2)
         isin = 1j * math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py
index fcd16c8240c2..40a4496c4c94 100644
--- a/qiskit/circuit/library/standard_gates/rz.py
+++ b/qiskit/circuit/library/standard_gates/rz.py
@@ -130,10 +130,12 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         ilam2 = 0.5j * float(self.params[0])
         return np.array([[exp(-ilam2), 0], [0, exp(ilam2)]], dtype=dtype)
 
@@ -277,10 +279,12 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         arg = 1j * float(self.params[0]) / 2
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py
index 42ba9158db0f..5090b9b307a0 100644
--- a/qiskit/circuit/library/standard_gates/rzx.py
+++ b/qiskit/circuit/library/standard_gates/rzx.py
@@ -166,10 +166,12 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         half_theta = float(self.params[0]) / 2
         cos = math.cos(half_theta)
         isin = 1j * math.sin(half_theta)
diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py
index 01cd1da199ea..462df650ad5f 100644
--- a/qiskit/circuit/library/standard_gates/rzz.py
+++ b/qiskit/circuit/library/standard_gates/rzz.py
@@ -130,10 +130,12 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         itheta2 = 1j * float(self.params[0]) / 2
         return numpy.array(
             [
diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py
index 81b48536f26b..67f5db92b602 100644
--- a/qiskit/circuit/library/standard_gates/u.py
+++ b/qiskit/circuit/library/standard_gates/u.py
@@ -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 @@ def control(
             )
         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("cannot produce matrix without calculation")
         theta, phi, lam = (float(param) for param in self.params)
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
@@ -146,7 +148,7 @@ def __array__(self, dtype=complex):
                 [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 @@ def inverse(self, annotated: bool = False):
             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("cannot produce matrix without calculation")
         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 @@ def __deepcopy__(self, memo=None):
         # 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
diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py
index b92bea51a26f..2eb7fbfb7b68 100644
--- a/qiskit/circuit/library/standard_gates/u1.py
+++ b/qiskit/circuit/library/standard_gates/u1.py
@@ -160,8 +160,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         lam = float(self.params[0])
         return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=dtype)
 
@@ -304,8 +306,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         eith = exp(1j * float(self.params[0]))
         if self.ctrl_state:
             return numpy.array(
diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py
index 021a38f4daeb..450587dd47ec 100644
--- a/qiskit/circuit/library/standard_gates/u2.py
+++ b/qiskit/circuit/library/standard_gates/u2.py
@@ -127,8 +127,10 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         isqrt2 = 1 / sqrt(2)
         phi, lam = self.params
         phi, lam = float(phi), float(lam)
@@ -137,5 +139,5 @@ def __array__(self, dtype=complex):
                 [isqrt2, -exp(1j * lam) * isqrt2],
                 [exp(1j * phi) * isqrt2, exp(1j * (phi + lam)) * isqrt2],
             ],
-            dtype=dtype,
+            dtype=dtype or complex,
         )
diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py
index c92a48ab52b6..9e9f79d6f9e1 100644
--- a/qiskit/circuit/library/standard_gates/u3.py
+++ b/qiskit/circuit/library/standard_gates/u3.py
@@ -149,8 +149,10 @@ def _define(self):
         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("cannot produce matrix without calculation")
         theta, phi, lam = self.params
         theta, phi, lam = float(theta), float(phi), float(lam)
         cos = math.cos(theta / 2)
@@ -160,7 +162,7 @@ def __array__(self, dtype=complex):
                 [cos, -exp(1j * lam) * sin],
                 [exp(1j * phi) * sin, exp(1j * (phi + lam)) * cos],
             ],
-            dtype=dtype,
+            dtype=dtype or complex,
         )
 
 
@@ -305,8 +307,10 @@ def inverse(self, annotated: bool = False):
             -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("cannot produce matrix without calculation")
         theta, phi, lam = self.params
         theta, phi, lam = float(theta), float(phi), float(lam)
         cos = math.cos(theta / 2)
@@ -319,7 +323,7 @@ def __array__(self, dtype=complex):
                     [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 @@ def __array__(self, dtype=complex):
                     [exp(1j * phi) * sin, 0, exp(1j * (phi + lam)) * cos, 0],
                     [0, 0, 0, 1],
                 ],
-                dtype=dtype,
+                dtype=dtype or complex,
             )
 
 
diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
index 2bdb32ad3c2b..b9c725932691 100644
--- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
@@ -169,8 +169,10 @@ def inverse(self, annotated: bool = False):
         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("cannot produce matrix without calculation")
         theta, beta = self.params
         cos = math.cos(theta / 2)
         sin = math.sin(theta / 2)
diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
index 5b53d9861ebb..1aaf7c8a1f7e 100644
--- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
@@ -15,6 +15,9 @@
 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 @@ def inverse(self, annotated: bool = False):
         """
         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("cannot produce matrix without calculation")
         half_theta = float(self.params[0]) / 2
         beta = float(self.params[1])
         cos = math.cos(half_theta)
diff --git a/qiskit/primitives/containers/observables_array.py b/qiskit/primitives/containers/observables_array.py
index 0d0322dc6a3c..21c415d75899 100644
--- a/qiskit/primitives/containers/observables_array.py
+++ b/qiskit/primitives/containers/observables_array.py
@@ -101,10 +101,10 @@ def tolist(self) -> list:
         """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
diff --git a/qiskit/quantum_info/operators/channel/chi.py b/qiskit/quantum_info/operators/channel/chi.py
index 7cd5fce5258f..ee0ddaa45385 100644
--- a/qiskit/quantum_info/operators/channel/chi.py
+++ b/qiskit/quantum_info/operators/channel/chi.py
@@ -16,10 +16,11 @@
 """
 
 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 @@ def __init__(
             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 @@ def expand(self, other: Chi) -> Chi:
 
     @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
diff --git a/qiskit/quantum_info/operators/channel/choi.py b/qiskit/quantum_info/operators/channel/choi.py
index afd8e4fca6f9..1c9579e45602 100644
--- a/qiskit/quantum_info/operators/channel/choi.py
+++ b/qiskit/quantum_info/operators/channel/choi.py
@@ -16,10 +16,11 @@
 """
 
 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 @@ def __init__(
             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 @@ def _evolve(self, state, qargs=None):
     # ---------------------------------------------------------------------
 
     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 @@ def expand(self, other: Choi) -> Choi:
 
     @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
diff --git a/qiskit/quantum_info/operators/channel/ptm.py b/qiskit/quantum_info/operators/channel/ptm.py
index 1bdf1b5ef235..84db071121f3 100644
--- a/qiskit/quantum_info/operators/channel/ptm.py
+++ b/qiskit/quantum_info/operators/channel/ptm.py
@@ -16,10 +16,11 @@
 """
 
 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 @@ def __init__(
             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 @@ def expand(self, other: PTM) -> PTM:
 
     @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
diff --git a/qiskit/quantum_info/operators/channel/superop.py b/qiskit/quantum_info/operators/channel/superop.py
index 0d7116ef5069..19867696ec6a 100644
--- a/qiskit/quantum_info/operators/channel/superop.py
+++ b/qiskit/quantum_info/operators/channel/superop.py
@@ -15,12 +15,13 @@
 
 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 @@ def __init__(
         # 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 @@ def _bipartite_shape(self):
     # ---------------------------------------------------------------------
 
     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 @@ def expand(self, other: SuperOp) -> SuperOp:
 
     @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
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py
index af15e0ed3ae5..9223fecf0499 100644
--- a/qiskit/quantum_info/operators/dihedral/dihedral.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral.py
@@ -357,10 +357,11 @@ def _from_circuit(self, circuit):
         _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("cannot produce matrix without calculation")
+        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."""
diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
index 90d40b93cc50..d119a3812493 100644
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -16,13 +16,14 @@
 
 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 @@ def __init__(
             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 @@ def to_instruction(self):
 
     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 @@ def power(self, n: float) -> Operator:
         """
         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 @@ def expand(self, other: Operator) -> Operator:
 
     @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 @@ def _add(self, other, qargs=None):
         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 @@ def _multiply(self, other):
         """
         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 @@ def reverse_qargs(self) -> Operator:
         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(
diff --git a/qiskit/quantum_info/operators/scalar_op.py b/qiskit/quantum_info/operators/scalar_op.py
index d856a39c2a20..38f36193739b 100644
--- a/qiskit/quantum_info/operators/scalar_op.py
+++ b/qiskit/quantum_info/operators/scalar_op.py
@@ -15,7 +15,7 @@
 """
 
 from __future__ import annotations
-import copy
+import copy as _copy
 from numbers import Number
 import numpy as np
 
@@ -52,10 +52,11 @@ def __init__(self, dims: int | tuple | None = None, coeff: Number = 1):
         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 @@ def compose(self, other: ScalarOp, qargs: list | None = None, front: bool = Fals
         # 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 @@ def compose(self, other: ScalarOp, qargs: list | None = None, front: bool = Fals
         # 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 @@ def tensor(self, other: ScalarOp) -> ScalarOp:
             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 @@ def expand(self, other: ScalarOp) -> ScalarOp:
             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
diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py
index 815be0075d26..a4fcdcfc2f83 100644
--- a/qiskit/quantum_info/operators/symplectic/clifford.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford.py
@@ -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("cannot produce matrix without calculation")
+        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."""
diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index 1bdff0cf8fea..4087acfb4a32 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli.py
@@ -222,10 +222,11 @@ def __str__(self):
             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("cannot produce matrix without calculation")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     @classmethod
     def set_truncation(cls, val: int):
diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py
index 1bb9ae3d1ada..3d348d236387 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli_list.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py
@@ -148,14 +148,15 @@ def settings(self):
         """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):
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index beb78d51c310..2223d8f4a42d 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -171,10 +171,11 @@ def __init__(
         # 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("cannot produce matrix without calculation")
+        arr = self.to_matrix()
+        return arr if dtype is None else arr.astype(dtype, copy=False)
 
     def __repr__(self):
         prefix = "SparsePauliOp("
diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py
index 07cc65685745..1c66d8bcf5cf 100644
--- a/qiskit/quantum_info/states/densitymatrix.py
+++ b/qiskit/quantum_info/states/densitymatrix.py
@@ -15,10 +15,11 @@
 """
 
 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 @@ def __init__(
             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 @@ def tensor(self, other: DensityMatrix) -> DensityMatrix:
         """
         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 @@ def expand(self, other: DensityMatrix) -> DensityMatrix:
         """
         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 @@ def _add(self, other):
         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 @@ def _multiply(self, other):
         """
         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 @@ def reverse_qargs(self) -> DensityMatrix:
         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 @@ def reset(self, qargs: list[int] | None = None) -> DensityMatrix:
         """
         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 @@ def _evolve_operator(self, other, qargs=None):
         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 @@ def _evolve_instruction(self, obj, qargs=None):
         """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
 
diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py
index b13ccde21743..df39ba42f915 100644
--- a/qiskit/quantum_info/states/statevector.py
+++ b/qiskit/quantum_info/states/statevector.py
@@ -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 @@ def __init__(
                 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 @@ def tensor(self, other: Statevector) -> Statevector:
         """
         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 @@ def expand(self, other: Statevector) -> Statevector:
         """
         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 @@ def _add(self, other):
         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 @@ def _multiply(self, other):
         """
         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 @@ def evolve(
             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 @@ def reverse_qargs(self) -> Statevector:
         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 @@ def reset(self, qargs: list[int] | None = None) -> Statevector:
         """
         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
diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py
index 6460847b2e81..5f11ede3a3e8 100644
--- a/test/python/transpiler/test_dynamical_decoupling.py
+++ b/test/python/transpiler/test_dynamical_decoupling.py
@@ -447,7 +447,9 @@ class Echo(Gate):
             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

From 00617e16c7eda04324be877fb83b1095193ba836 Mon Sep 17 00:00:00 2001
From: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com>
Date: Wed, 24 Apr 2024 17:11:11 +0200
Subject: [PATCH 3/4] fixing numerical issues on mac-arm

---
 test/python/providers/test_backend_v2.py | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py
index 6b974df902cd..70330085b1ab 100644
--- a/test/python/providers/test_backend_v2.py
+++ b/test/python/providers/test_backend_v2.py
@@ -19,6 +19,7 @@
 
 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 @@ def assertMatchesTargetConstraints(self, tqc, target):
     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 @@ def qubit_properties(self, qubit):
                 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."""

From c4103b77447a16310242669ff80199d8ecaebe58 Mon Sep 17 00:00:00 2001
From: Jake Lishman 
Date: Thu, 25 Apr 2024 18:19:59 +0100
Subject: [PATCH 4/4] Change error to match Numpy

---
 qiskit/circuit/__init__.py                                  | 2 +-
 qiskit/circuit/library/generalized_gates/permutation.py     | 2 +-
 qiskit/circuit/library/hamiltonian_gate.py                  | 2 +-
 qiskit/circuit/library/standard_gates/global_phase.py       | 2 +-
 qiskit/circuit/library/standard_gates/p.py                  | 4 ++--
 qiskit/circuit/library/standard_gates/r.py                  | 2 +-
 qiskit/circuit/library/standard_gates/rx.py                 | 4 ++--
 qiskit/circuit/library/standard_gates/rxx.py                | 2 +-
 qiskit/circuit/library/standard_gates/ry.py                 | 4 ++--
 qiskit/circuit/library/standard_gates/ryy.py                | 2 +-
 qiskit/circuit/library/standard_gates/rz.py                 | 4 ++--
 qiskit/circuit/library/standard_gates/rzx.py                | 2 +-
 qiskit/circuit/library/standard_gates/rzz.py                | 2 +-
 qiskit/circuit/library/standard_gates/u.py                  | 4 ++--
 qiskit/circuit/library/standard_gates/u1.py                 | 4 ++--
 qiskit/circuit/library/standard_gates/u2.py                 | 2 +-
 qiskit/circuit/library/standard_gates/u3.py                 | 4 ++--
 qiskit/circuit/library/standard_gates/xx_minus_yy.py        | 2 +-
 qiskit/circuit/library/standard_gates/xx_plus_yy.py         | 2 +-
 qiskit/quantum_info/operators/dihedral/dihedral.py          | 2 +-
 qiskit/quantum_info/operators/symplectic/clifford.py        | 2 +-
 qiskit/quantum_info/operators/symplectic/pauli.py           | 2 +-
 qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py | 2 +-
 23 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py
index 928863572fca..9fbefb4c5d9f 100644
--- a/qiskit/circuit/__init__.py
+++ b/qiskit/circuit/__init__.py
@@ -877,7 +877,7 @@ def power(self, exponent: float):
 
         def __array__(self, dtype=None, copy=None):
             if copy is False:
-                raise ValueError("cannot produce a matrix without calculation")
+                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([
diff --git a/qiskit/circuit/library/generalized_gates/permutation.py b/qiskit/circuit/library/generalized_gates/permutation.py
index edfe9cafac67..776c69d94f01 100644
--- a/qiskit/circuit/library/generalized_gates/permutation.py
+++ b/qiskit/circuit/library/generalized_gates/permutation.py
@@ -150,7 +150,7 @@ def __init__(
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the Permutation gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/hamiltonian_gate.py b/qiskit/circuit/library/hamiltonian_gate.py
index 35e23a0bbaea..2997d01ed487 100644
--- a/qiskit/circuit/library/hamiltonian_gate.py
+++ b/qiskit/circuit/library/hamiltonian_gate.py
@@ -98,7 +98,7 @@ def __array__(self, dtype=None, copy=None):
         import scipy.linalg
 
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            raise ValueError("unable to avoid copy while creating an array as requested")
         try:
             time = float(self.params[1])
         except TypeError as ex:
diff --git a/qiskit/circuit/library/standard_gates/global_phase.py b/qiskit/circuit/library/standard_gates/global_phase.py
index 7d81c9242837..ccd758e47241 100644
--- a/qiskit/circuit/library/standard_gates/global_phase.py
+++ b/qiskit/circuit/library/standard_gates/global_phase.py
@@ -72,7 +72,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the global_phase gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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 or complex)
 
diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py
index fcec63269f6d..605be5b9ea41 100644
--- a/qiskit/circuit/library/standard_gates/p.py
+++ b/qiskit/circuit/library/standard_gates/p.py
@@ -143,7 +143,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the Phase gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
@@ -285,7 +285,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CPhase gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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(
diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py
index ba7c5501badc..10fdd4242ec2 100644
--- a/qiskit/circuit/library/standard_gates/r.py
+++ b/qiskit/circuit/library/standard_gates/r.py
@@ -96,7 +96,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the R gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py
index aac37f9948cd..7d0ea9e051bf 100644
--- a/qiskit/circuit/library/standard_gates/rx.py
+++ b/qiskit/circuit/library/standard_gates/rx.py
@@ -123,7 +123,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RX gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
@@ -269,7 +269,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CRX gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py
index d4fdd40618e2..8270620a6262 100644
--- a/qiskit/circuit/library/standard_gates/rxx.py
+++ b/qiskit/circuit/library/standard_gates/rxx.py
@@ -125,7 +125,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the RXX gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py
index a3212bdd8c3b..4c60056c3d8d 100644
--- a/qiskit/circuit/library/standard_gates/ry.py
+++ b/qiskit/circuit/library/standard_gates/ry.py
@@ -122,7 +122,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RY gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
@@ -264,7 +264,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CRY gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py
index 788fa6dfc0cb..6de6e9cd42d2 100644
--- a/qiskit/circuit/library/standard_gates/ryy.py
+++ b/qiskit/circuit/library/standard_gates/ryy.py
@@ -125,7 +125,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the RYY gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py
index 40a4496c4c94..9f4c836c0f3e 100644
--- a/qiskit/circuit/library/standard_gates/rz.py
+++ b/qiskit/circuit/library/standard_gates/rz.py
@@ -135,7 +135,7 @@ def __array__(self, dtype=None, copy=None):
         import numpy as np
 
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
@@ -284,7 +284,7 @@ def __array__(self, dtype=None, copy=None):
         import numpy
 
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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(
diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py
index 5090b9b307a0..951b2e3449f1 100644
--- a/qiskit/circuit/library/standard_gates/rzx.py
+++ b/qiskit/circuit/library/standard_gates/rzx.py
@@ -171,7 +171,7 @@ def __array__(self, dtype=None, copy=None):
         import numpy
 
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py
index 462df650ad5f..8eb8b814d48f 100644
--- a/qiskit/circuit/library/standard_gates/rzz.py
+++ b/qiskit/circuit/library/standard_gates/rzz.py
@@ -135,7 +135,7 @@ def __array__(self, dtype=None, copy=None):
         import numpy
 
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            raise ValueError("unable to avoid copy while creating an array as requested")
         itheta2 = 1j * float(self.params[0]) / 2
         return numpy.array(
             [
diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py
index 67f5db92b602..3d631898850a 100644
--- a/qiskit/circuit/library/standard_gates/u.py
+++ b/qiskit/circuit/library/standard_gates/u.py
@@ -139,7 +139,7 @@ def control(
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the U gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
@@ -342,7 +342,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py
index 2eb7fbfb7b68..1d59cabae1f6 100644
--- a/qiskit/circuit/library/standard_gates/u1.py
+++ b/qiskit/circuit/library/standard_gates/u1.py
@@ -163,7 +163,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the U1 gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
@@ -309,7 +309,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU1 gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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(
diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py
index 450587dd47ec..c8e4de96efec 100644
--- a/qiskit/circuit/library/standard_gates/u2.py
+++ b/qiskit/circuit/library/standard_gates/u2.py
@@ -130,7 +130,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the U2 gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py
index 9e9f79d6f9e1..62c1e33b9628 100644
--- a/qiskit/circuit/library/standard_gates/u3.py
+++ b/qiskit/circuit/library/standard_gates/u3.py
@@ -152,7 +152,7 @@ def _define(self):
     def __array__(self, dtype=None, copy=None):
         """Return a Numpy.array for the U3 gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
@@ -310,7 +310,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the CU3 gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
index b9c725932691..12a7dfe907ab 100644
--- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
@@ -172,7 +172,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Gate matrix."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
index 1aaf7c8a1f7e..489c51e264a5 100644
--- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py
+++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py
@@ -173,7 +173,7 @@ def inverse(self, annotated: bool = False):
     def __array__(self, dtype=None, copy=None):
         """Return a numpy.array for the XX+YY gate."""
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py
index 9223fecf0499..4f49879063ec 100644
--- a/qiskit/quantum_info/operators/dihedral/dihedral.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral.py
@@ -359,7 +359,7 @@ def _from_circuit(self, circuit):
 
     def __array__(self, dtype=None, copy=None):
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py
index a4fcdcfc2f83..47ef8b629dd6 100644
--- a/qiskit/quantum_info/operators/symplectic/clifford.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford.py
@@ -124,7 +124,7 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
 
     def __array__(self, dtype=None, copy=None):
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py
index 4087acfb4a32..e1bcfa29ebcb 100644
--- a/qiskit/quantum_info/operators/symplectic/pauli.py
+++ b/qiskit/quantum_info/operators/symplectic/pauli.py
@@ -224,7 +224,7 @@ def __str__(self):
 
     def __array__(self, dtype=None, copy=None):
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)
 
diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
index 2223d8f4a42d..dc445509b0e3 100644
--- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
+++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
@@ -173,7 +173,7 @@ def __init__(
 
     def __array__(self, dtype=None, copy=None):
         if copy is False:
-            raise ValueError("cannot produce matrix without calculation")
+            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)