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

Feature: migrate to numpy v2.0.0 #596

Merged
merged 12 commits into from
Aug 9, 2024
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ sphinx:
# Declare the Python requirements required to build your docs
python:
install:
- requirements: requirements-dev.txt
- requirements: requirements-doc.txt
- method: pip
path: .
2 changes: 1 addition & 1 deletion environment-dev-arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ channels:
dependencies:
- python>=3.6.4
- pip
- numpy>=1.21.0,<2.0.0
- numpy>=1.21.0
- scipy>=1.11.0
- pytorch>=1.2.0
- cpuonly
Expand Down
2 changes: 1 addition & 1 deletion environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ channels:
dependencies:
- python>=3.6.4
- pip
- numpy>=1.21.0,<2.0.0
- numpy>=1.21.0
- scipy>=1.11.0
- pytorch>=1.2.0
- cpuonly
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ channels:
- defaults
dependencies:
- python>=3.6.4
- numpy>=1.21.0,<2.0.0
- numpy>=1.21.0
- scipy>=1.14.0
19 changes: 15 additions & 4 deletions pylops/basicoperators/restriction.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
__all__ = ["Restriction"]

import logging

from typing import Sequence, Union

import numpy as np
import numpy.ma as np_ma
from numpy.core.multiarray import normalize_axis_index

# need to check numpy version since normalize_axis_index will be
# soon moved from numpy.core.multiarray to from numpy.lib.array_utils
np_version = np.__version__.split(".")
if int(np_version[0]) < 2:
from numpy.core.multiarray import normalize_axis_index
else:
from numpy.lib.array_utils import normalize_axis_index

from pylops import LinearOperator
from pylops.utils._internal import _value_or_sized_to_tuple
Expand Down Expand Up @@ -128,8 +134,13 @@ def __init__(
)
forceflat = None

super().__init__(dtype=np.dtype(dtype), dims=dims, dimsd=dimsd,
forceflat=forceflat, name=name)
super().__init__(
dtype=np.dtype(dtype),
dims=dims,
dimsd=dimsd,
forceflat=forceflat,
name=name,
)

iavareshape = np.ones(len(self.dims), dtype=int)
iavareshape[axis] = len(iava)
Expand Down
27 changes: 13 additions & 14 deletions pylops/linearoperator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,23 +1242,14 @@ def _get_dtype(
) -> DTypeLike:
if dtypes is None:
dtypes = []
opdtypes = []
for obj in operators:
if obj is not None and hasattr(obj, "dtype"):
opdtypes.append(obj.dtype)
return np.find_common_type(opdtypes, dtypes)
dtypes.append(obj.dtype)
return np.result_type(*dtypes)


class _ScaledLinearOperator(LinearOperator):
"""
Sum Linear Operator

Modified version of scipy _ScaledLinearOperator which uses a modified
_get_dtype where the scalar and operator types are passed separately to
np.find_common_type. Passing them together does lead to problems when using
np.float32 operators which are cast to np.float64

"""
"""Scaled Linear Operator"""

def __init__(
self,
Expand All @@ -1269,7 +1260,15 @@ def __init__(
raise ValueError("LinearOperator expected as A")
if not np.isscalar(alpha):
raise ValueError("scalar expected as alpha")
dtype = _get_dtype([A], [type(alpha)])
if isinstance(alpha, complex) and not np.iscomplexobj(
np.ones(1, dtype=A.dtype)
):
# if the scalar is of complex type but not the operator, find out type
dtype = _get_dtype([A], [type(alpha)])
else:
# if both the scalar and operator are of real or complex type, use type
# of the operator
dtype = A.dtype
super(_ScaledLinearOperator, self).__init__(dtype=dtype, shape=A.shape)
self.args = (A, alpha)

Expand Down Expand Up @@ -1465,7 +1464,7 @@ def __init__(self, A: LinearOperator, p: int) -> None:
if not isintlike(p) or p < 0:
raise ValueError("non-negative integer expected as p")

super(_PowerLinearOperator, self).__init__(dtype=_get_dtype([A]), shape=A.shape)
super(_PowerLinearOperator, self).__init__(dtype=A.dtype, shape=A.shape)
self.args = (A, p)

def _power(self, fun: Callable, x: NDArray) -> NDArray:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Mathematics",
]
dependencies = [
"numpy >= 1.21.0 , < 2.0.0",
"numpy >= 1.21.0",
"scipy >= 1.11.0",
]
dynamic = ["version"]
Expand Down
12 changes: 12 additions & 0 deletions pytests/test_dtcwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

from pylops.signalprocessing import DTCWT

# currently test only if numpy<2.0.0 is installed...
np_version = np.__version__.split(".")

par1 = {"ny": 10, "nx": 10, "dtype": "float64"}
par2 = {"ny": 50, "nx": 50, "dtype": "float64"}

Expand All @@ -17,6 +20,8 @@ def sequential_array(shape):
@pytest.mark.parametrize("par", [(par1), (par2)])
def test_dtcwt1D_input1D(par):
"""Test for DTCWT with 1D input"""
if int(np_version[0]) >= 2:
return

t = sequential_array((par["ny"],))

Expand All @@ -31,6 +36,8 @@ def test_dtcwt1D_input1D(par):
@pytest.mark.parametrize("par", [(par1), (par2)])
def test_dtcwt1D_input2D(par):
"""Test for DTCWT with 2D input (forward-inverse pair)"""
if int(np_version[0]) >= 2:
return

t = sequential_array(
(
Expand All @@ -50,6 +57,8 @@ def test_dtcwt1D_input2D(par):
@pytest.mark.parametrize("par", [(par1), (par2)])
def test_dtcwt1D_input3D(par):
"""Test for DTCWT with 3D input (forward-inverse pair)"""
if int(np_version[0]) >= 2:
return

t = sequential_array((par["ny"], par["ny"], par["ny"]))

Expand All @@ -64,6 +73,9 @@ def test_dtcwt1D_input3D(par):
@pytest.mark.parametrize("par", [(par1), (par2)])
def test_dtcwt1D_birot(par):
"""Test for DTCWT birot (forward-inverse pair)"""
if int(np_version[0]) >= 2:
return

birots = ["antonini", "legall", "near_sym_a", "near_sym_b"]

t = sequential_array(
Expand Down
8 changes: 7 additions & 1 deletion pytests/test_sparsity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from pylops.basicoperators import FirstDerivative, Identity, MatrixMult
from pylops.optimization.sparsity import fista, irls, ista, omp, spgl1, splitbregman

# currently test spgl1 only if numpy<2.0.0 is installed...
np_version = np.__version__.split(".")

par1 = {
"ny": 11,
"nx": 11,
Expand Down Expand Up @@ -359,6 +362,9 @@ def test_ISTA_FISTA_multiplerhs(par):
)
def test_SPGL1(par):
"""Invert problem with SPGL1"""
if int(np_version[0]) >= 2:
return

np.random.seed(42)
Aop = MatrixMult(np.random.randn(par["ny"], par["nx"]))

Expand Down Expand Up @@ -412,6 +418,6 @@ def test_SplitBregman(par):
x0=x0 if par["x0"] else None,
restart=False,
show=False,
**dict(iter_lim=5, damp=1e-3)
**dict(iter_lim=5, damp=1e-3),
)
assert (np.linalg.norm(x - xinv) / np.linalg.norm(x)) < 1e-1
17 changes: 17 additions & 0 deletions pytests/test_torchoperator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import platform

import numpy as np
import pytest
import torch
Expand All @@ -17,6 +19,11 @@ def test_TorchOperator(par):
must equal the adjoint of operator applied to the same vector, the two
results are also checked to be the same.
"""
# temporarily, skip tests on mac as torch seems not to recognized
# numpy when v2 is installed
if platform.system() == "Darwin":
return

Dop = MatrixMult(np.random.normal(0.0, 1.0, (par["ny"], par["nx"])))
Top = TorchOperator(Dop, batch=False)

Expand All @@ -40,6 +47,11 @@ def test_TorchOperator(par):
@pytest.mark.parametrize("par", [(par1)])
def test_TorchOperator_batch(par):
"""Apply forward for input with multiple samples (= batch) and flattened arrays"""
# temporarily, skip tests on mac as torch seems not to recognized
# numpy when v2 is installed
if platform.system() == "Darwin":
return

Dop = MatrixMult(np.random.normal(0.0, 1.0, (par["ny"], par["nx"])))
Top = TorchOperator(Dop, batch=True)

Expand All @@ -56,6 +68,11 @@ def test_TorchOperator_batch(par):
@pytest.mark.parametrize("par", [(par1)])
def test_TorchOperator_batch_nd(par):
"""Apply forward for input with multiple samples (= batch) and nd-arrays"""
# temporarily, skip tests on mac as torch seems not to recognized
# numpy when v2 is installed
if platform.system() == "Darwin":
return

Dop = MatrixMult(np.random.normal(0.0, 1.0, (par["ny"], par["nx"])), otherdims=(2,))
Top = TorchOperator(Dop, batch=True, flatten=False)

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
numpy>=1.21.0,<2.0.0
numpy>=1.21.0
scipy>=1.11.0
--extra-index-url https://download.pytorch.org/whl/cpu
torch>=1.2.0
Expand Down
32 changes: 32 additions & 0 deletions requirements-doc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Currently we force rdt to use numpy<2.0.0 to build the documentation
# since the dtcwt and spgl1 are not yet compatible with numpy=2.0.0
numpy>=1.21.0,<2.0.0
scipy>=1.11.0
--extra-index-url https://download.pytorch.org/whl/cpu
cako marked this conversation as resolved.
Show resolved Hide resolved
torch>=1.2.0
numba
pyfftw
PyWavelets
spgl1
scikit-fmm
sympy
devito
dtcwt
matplotlib
ipython
pytest
pytest-runner
setuptools_scm
docutils<0.18
Sphinx
pydata-sphinx-theme
sphinx-gallery
numpydoc
nbsphinx
image
pre-commit
autopep8
isort
black
flake8
mypy