Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
)

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.
Expand Down
73 changes: 73 additions & 0 deletions qiskit/_numpy_compat.py
Original file line number Diff line number Diff line change
@@ -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."""
1 change: 1 addition & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@
"""

from .exceptions import CircuitError
from . import _utils
from .quantumcircuit import QuantumCircuit
from .classicalregister import ClassicalRegister, Clbit
from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit
Expand Down
23 changes: 16 additions & 7 deletions qiskit/circuit/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"""

import numpy

from qiskit import _numpy_compat
from qiskit.exceptions import QiskitError
from qiskit.circuit.exceptions import CircuitError
from .parametervector import ParameterVectorElement
Expand Down Expand Up @@ -116,8 +118,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__"):
Expand Down Expand Up @@ -148,15 +151,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
)
Expand Down
6 changes: 2 additions & 4 deletions qiskit/circuit/delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -49,10 +51,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.
Expand Down
4 changes: 2 additions & 2 deletions qiskit/circuit/library/generalized_gates/pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ def inverse(self):
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):
Expand Down
5 changes: 4 additions & 1 deletion qiskit/circuit/library/generalized_gates/permutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
7 changes: 4 additions & 3 deletions qiskit/circuit/library/generalized_gates/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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.quantumcircuit import QuantumCircuit
Expand Down Expand Up @@ -115,10 +116,10 @@ def __eq__(self, other):
# up to global phase?
return matrix_equal(self.params[0], other.params[0], ignore_phase=True)

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):
"""Return the adjoint of the unitary."""
Expand Down
11 changes: 8 additions & 3 deletions qiskit/circuit/library/hamiltonian_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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
Expand Down Expand Up @@ -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):
"""Return the adjoint of the unitary."""
Expand Down
6 changes: 4 additions & 2 deletions qiskit/circuit/library/standard_gates/global_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def inverse(self):
"""
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)
8 changes: 6 additions & 2 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,10 @@ def inverse(self):
r"""Return inverted Phase gate (:math:`Phase(\lambda)^{\dagger} = Phase(-\lambda)`)"""
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)

Expand Down Expand Up @@ -249,8 +251,10 @@ def inverse(self):
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(
Expand Down
4 changes: 3 additions & 1 deletion qiskit/circuit/library/standard_gates/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@ def inverse(self):
"""
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)
Expand Down
8 changes: 6 additions & 2 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ def inverse(self):
"""
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)
Expand Down Expand Up @@ -231,8 +233,10 @@ def inverse(self):
"""Return inverse CRX gate (i.e. with the negative rotation angle)."""
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)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/circuit/library/standard_gates/rxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ def inverse(self):
"""Return inverse RXX gate (i.e. with the negative rotation angle)."""
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)
Expand Down
8 changes: 6 additions & 2 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ def inverse(self):
"""
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)
Expand Down Expand Up @@ -226,8 +228,10 @@ def inverse(self):
"""Return inverse CRY gate (i.e. with the negative rotation angle)."""
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)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/circuit/library/standard_gates/ryy.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ def inverse(self):
"""Return inverse RYY gate (i.e. with the negative rotation angle)."""
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)
Expand Down
8 changes: 6 additions & 2 deletions qiskit/circuit/library/standard_gates/rz.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,12 @@ def inverse(self):
"""
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)

Expand Down Expand Up @@ -244,10 +246,12 @@ def inverse(self):
"""Return inverse CRZ gate (i.e. with the negative rotation angle)."""
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(
Expand Down
Loading