Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Numpy 2.0 #96

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
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>0.1.3'],
'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
Loading