Skip to content

Commit

Permalink
Merge pull request #96 from qutech/numpy-2.0
Browse files Browse the repository at this point in the history
Support Numpy 2.0
  • Loading branch information
thangleiter authored Nov 6, 2024
2 parents 37a0453 + 09e186c commit 10ae32a
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 27 deletions.
44 changes: 25 additions & 19 deletions filter_functions/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import numpy as np
import opt_einsum as oe
from numpy import linalg as nla
from numpy.core import ndarray
from scipy import linalg as sla
from sparse import COO

Expand All @@ -56,7 +55,7 @@
__all__ = ['Basis', 'expand', 'ggm_expand', 'normalize']


class Basis(ndarray):
class Basis(np.ndarray):
r"""
Class for operator bases. There are several ways to instantiate a
Basis object:
Expand Down Expand Up @@ -217,22 +216,26 @@ def __eq__(self, other: object) -> bool:
# Not ndarray
return np.equal(self, other)

return np.allclose(self.view(ndarray), other.view(ndarray),
return np.allclose(self.view(np.ndarray), other.view(np.ndarray),
atol=self._atol, rtol=self._rtol)

def __contains__(self, item: ndarray) -> bool:
def __contains__(self, item: np.ndarray) -> bool:
"""Implement 'in' operator."""
return any(np.isclose(item.view(ndarray), self.view(ndarray),
return any(np.isclose(item.view(np.ndarray), self.view(np.ndarray),
rtol=self._rtol, atol=self._atol).all(axis=(1, 2)))

def __array_wrap__(self, out_arr, context=None):
def __array_wrap__(self, arr, context=None, return_scalar=False):
"""
Fixes problem that ufuncs return 0-d arrays instead of scalars.
https://github.com/numpy/numpy/issues/5819#issue-72454838
"""
if out_arr.ndim:
return ndarray.__array_wrap__(self, out_arr, context)
try:
return super().__array_wrap__(arr, context, return_scalar=True)
except TypeError:
if arr.ndim:
# Numpy < 2
return np.ndarray.__array_wrap__(self, arr, context)

def _print_checks(self) -> None:
"""Print checks for debug purposes."""
Expand Down Expand Up @@ -265,7 +268,7 @@ def isorthonorm(self) -> bool:
actual = U.conj() @ U.T
target = np.identity(dim)
atol = self._eps*(self.d**2)**3
self._isorthonorm = np.allclose(actual.view(ndarray), target,
self._isorthonorm = np.allclose(actual.view(np.ndarray), target,
atol=atol, rtol=self._rtol)

return self._isorthonorm
Expand All @@ -278,13 +281,16 @@ def istraceless(self) -> bool:
if self._istraceless is None:
trace = np.einsum('...jj', self)
trace = util.remove_float_errors(trace, self.d**2)
nonzero = trace.nonzero()
nonzero = np.atleast_1d(trace).nonzero()
if nonzero[0].size == 0:
self._istraceless = True
elif nonzero[0].size == 1:
# Single element has nonzero trace, check if (proportional to)
# identity
elem = self[nonzero][0].view(ndarray) if self.ndim == 3 else self.view(ndarray)
if self.ndim == 3:
elem = self[nonzero][0].view(np.ndarray)
else:
elem = self.view(np.ndarray)
offdiag_nonzero = elem[~np.eye(self.d, dtype=bool)].nonzero()
diag_equal = np.diag(elem) == elem[0, 0]
if diag_equal.all() and not offdiag_nonzero[0].any():
Expand Down Expand Up @@ -597,7 +603,7 @@ def _full_from_partial(elems: Sequence, traceless: bool, labels: Sequence[str])
# sort Identity label to the front, default to first if not found
# (should not happen since traceless checks that it is present)
id_idx = next((i for i, elem in enumerate(elems)
if np.allclose(Id.view(ndarray), elem.view(ndarray),
if np.allclose(Id.view(np.ndarray), elem.view(np.ndarray),
rtol=elems._rtol, atol=elems._atol)), 0)
labels.insert(0, labels.pop(id_idx))

Expand All @@ -606,7 +612,7 @@ def _full_from_partial(elems: Sequence, traceless: bool, labels: Sequence[str])
return basis, labels


def _norm(b: Sequence) -> ndarray:
def _norm(b: Sequence) -> np.ndarray:
"""Frobenius norm with two singleton dimensions inserted at the end."""
b = np.asanyarray(b)
norm = nla.norm(b, axis=(-1, -2))
Expand All @@ -633,8 +639,8 @@ def normalize(b: Basis) -> Basis:
return (b/_norm(b)).squeeze().view(Basis)


def expand(M: Union[ndarray, Basis], basis: Union[ndarray, Basis],
normalized: bool = True, hermitian: bool = False, tidyup: bool = False) -> ndarray:
def expand(M: Union[np.ndarray, Basis], basis: Union[np.ndarray, Basis],
normalized: bool = True, hermitian: bool = False, tidyup: bool = False) -> np.ndarray:
r"""
Expand the array *M* in the basis given by *basis*.
Expand Down Expand Up @@ -684,8 +690,8 @@ def cast(arr):
return util.remove_float_errors(coefficients) if tidyup else coefficients


def ggm_expand(M: Union[ndarray, Basis], traceless: bool = False,
hermitian: bool = False) -> ndarray:
def ggm_expand(M: Union[np.ndarray, Basis], traceless: bool = False,
hermitian: bool = False) -> np.ndarray:
r"""
Expand the matrix *M* in a Generalized Gell-Mann basis [Bert08]_.
This function makes use of the explicit construction prescription of
Expand Down Expand Up @@ -767,7 +773,7 @@ def cast(arr):
return coeffs.squeeze() if square else coeffs


def equivalent_pauli_basis_elements(idx: Union[Sequence[int], int], N: int) -> ndarray:
def equivalent_pauli_basis_elements(idx: Union[Sequence[int], int], N: int) -> np.ndarray:
"""
Get the indices of the equivalent (up to identities tensored to it)
basis elements of Pauli bases of qubits at position idx in the total
Expand All @@ -780,7 +786,7 @@ def equivalent_pauli_basis_elements(idx: Union[Sequence[int], int], N: int) -> n
return elem_idx


def remap_pauli_basis_elements(order: Sequence[int], N: int) -> ndarray:
def remap_pauli_basis_elements(order: Sequence[int], N: int) -> np.ndarray:
"""
For a N-qubit Pauli basis, transpose the order of the subsystems and
return the indices that permute the old basis to the new.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def extract_version(version_file):
'bloch_sphere_visualization': ['qutip', 'matplotlib'],
'fancy_progressbar': ['ipynbname', 'jupyter'],
'doc': ['jupyter', 'nbsphinx', 'numpydoc', 'sphinx', 'sphinx_rtd_theme',
'ipympl', 'qutip-qip', 'qutip-qtrl'],
'ipympl', 'qutip-qip', 'qutip-qtrl', 'numpy<2'],
'tests': ['pytest>=4.6', 'pytest-cov', 'codecov']}

extras_require['all'] = list({dep for deps in extras_require.values() for dep in deps})
Expand Down
23 changes: 17 additions & 6 deletions tests/test_basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@
from copy import copy
from itertools import product

import filter_functions as ff
import numpy as np
import pytest
from sparse import COO

import filter_functions as ff
from filter_functions import util
from sparse import COO
from tests import testutil
from tests.testutil import rng

Expand Down Expand Up @@ -152,9 +151,21 @@ def test_basis_properties(self):

base._print_checks()

basis = util.paulis[1].view(ff.Basis)
self.assertTrue(basis.isorthonorm)
self.assertArrayEqual(basis.T, basis.view(np.ndarray).T)
# single element always considered orthonormal
orthonorm = rng.normal(size=(d, d))
self.assertTrue(orthonorm.view(ff.Basis).isorthonorm)

herm = testutil.rand_herm(d).squeeze()
self.assertTrue(herm.view(ff.Basis).isherm)

herm[0, 1] += 1
self.assertFalse(herm.view(ff.Basis).isherm)

traceless = testutil.rand_herm_traceless(d).squeeze()
self.assertTrue(traceless.view(ff.Basis).istraceless)

traceless[0, 0] += 1
self.assertFalse(traceless.view(ff.Basis).istraceless)

def test_transpose(self):
arr = rng.normal(size=(2, 3, 3))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_gradient_calculation_random_pulse(self):
spectral_noise_density=gradient_testutil.one_over_f_noise,
c_id=[f'c{i}' for i in range(len(u_ctrl))], n_coeffs_deriv=None
)
self.assertArrayAlmostEqual(ana_grad, fin_diff_grad, rtol=1e-6, atol=1e-8)
self.assertArrayAlmostEqual(ana_grad, fin_diff_grad, rtol=1e-5, atol=1e-7)

def test_caching(self):
"""Make sure calculation works with or without cached intermediates."""
Expand Down

0 comments on commit 10ae32a

Please sign in to comment.