diff --git a/.github/workflows/codacy-coverage-reporter.yaml b/.github/workflows/codacy-coverage-reporter.yaml index eb5c7b3..579c725 100644 --- a/.github/workflows/codacy-coverage-reporter.yaml +++ b/.github/workflows/codacy-coverage-reporter.yaml @@ -5,7 +5,7 @@ name: PyProximal-coverage on: pull_request: push: - branches: [main] + branches: [dev] jobs: build: diff --git a/Makefile b/Makefile index 2a476a8..b319e76 100755 --- a/Makefile +++ b/Makefile @@ -97,11 +97,12 @@ typeannot_uv: $(UV) run mypy pyproximal/ coverage: - coverage run -m pytest && coverage xml && coverage html && $(PYTHON) -m http.server --directory htmlcov/ + coverage run --source=pyproximal -m pytest && \ + coverage xml && coverage html && $(PYTHON) -m http.server --directory htmlcov/ coverage_uv: make uvcheck - $(UV) run coverage run -m pytest &&\ + $(UV) run coverage run --source=pyproximal -m pytest &&\ $(UV) run coverage xml &&\ $(UV) run coverage html &&\ $(UV) run python -m http.server --directory htmlcov/ diff --git a/docs/source/conf.py b/docs/source/conf.py index c6da920..d6e3ff1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- -import sys -import os import datetime +import os +import sys from sphinx_gallery.sorting import ExampleTitleSortKey @@ -93,7 +92,7 @@ # General information about the project year = datetime.date.today().year project = "PyProximal" -copyright = "{}, PyLops Development Team".format(year) +copyright = f"{year}, PyLops Development Team" # Version version = __version__ @@ -101,9 +100,9 @@ version = "dev" # These enable substitutions using |variable| in the rst files -rst_epilog = """ +rst_epilog = f""" .. |year| replace:: {year} -""".format(year=year) +""" html_static_path = ["_static"] html_last_updated_fmt = "%b %d, %Y" @@ -146,7 +145,11 @@ "doc_path": "docs/source", "galleries": sphinx_gallery_conf["gallery_dirs"], "gallery_dir": dict( - zip(sphinx_gallery_conf["gallery_dirs"], sphinx_gallery_conf["examples_dirs"]) + zip( + sphinx_gallery_conf["gallery_dirs"], + sphinx_gallery_conf["examples_dirs"], + strict=False, + ) ), "github_project": "PyLops", "github_repo": "pyproximal", diff --git a/pyproject.toml b/pyproject.toml index d4cbedf..fc6022b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,19 +82,39 @@ python_files = ["pytests/*.py"] [tool.ruff] src = ["pyproximal"] +exclude = ["pyproximal/version.py", ] line-length = 88 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "W", # pycodestyle warnings + "B", # flake8-bugbear + "EM", # flake8-errmsg + "UP", # pyupgrade + "I" # isort +] +ignore = [ + "E501", # line too long (for comments and strings) + "UP031", # use of old '%' formatting instead of f-strings +] [tool.ruff.lint.per-file-ignores] "__init__.py" = [ "F401", # imported but unused (re-exports) "F403", # star import used for public API "F405", # name may be undefined from star import + "I001", # allow usorted imports ] "tutorials/*.py" = [ - "E402", # module level import not at top of file "E731", # do not assign a lambda expression, use def ] +[tool.ruff.lint.isort] +known-first-party = ["pyproximal"] + [tool.mypy] files = ["pyproximal", ] python_version = "3.11" @@ -102,10 +122,10 @@ mypy_path = "pyproximal" strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] disable_error_code = ["untyped-decorator"] -warn_unreachable = true allow_redefinition = true disallow_untyped_defs = true disallow_incomplete_defs = true +warn_unreachable = false [[tool.mypy.overrides]] module = ["cupy.*", "numpy.*", "numba.*", "pylops.*", "scipy.*", "scooby.*", "pyproximal.*"] diff --git a/pyproximal/ProxOperator.py b/pyproximal/ProxOperator.py index 436952d..f32d07c 100644 --- a/pyproximal/ProxOperator.py +++ b/pyproximal/ProxOperator.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional, Union import numpy as np from pylops.utils.typing import NDArray @@ -19,13 +20,14 @@ def _check_tau(func: Callable[..., NDArray]) -> Callable[..., NDArray]: def wrapper(*args: Any, **kwargs: Any) -> Any: if np.any(args[2] <= 0): - raise ValueError("tau must be positive") + msg = "tau must be positive" + raise ValueError(msg) return func(*args, **kwargs) return wrapper -class ProxOperator(object): +class ProxOperator: r"""Common interface for proximal operators of a function. This class defines the overarching structure of any proximal operator. It @@ -87,10 +89,11 @@ def __call__(self, x: NDArray) -> bool | float | int: Subclasses should implement this. Returns the value of the function. """ - raise NotImplementedError( + msg = ( "This ProxOperator's __call__ method " "must be implemented by subclasses to return a float." ) + raise NotImplementedError(msg) @_check_tau def _prox_moreau(self, x: NDArray, tau: float, **kwargs: Any) -> NDArray: @@ -207,7 +210,8 @@ def affine_addition(self, v: NDArray) -> "ProxOperator": if isinstance(v, (np.ndarray, cp_dtype)): return _SumOperator(self, v) else: - raise NotImplementedError("v must be a numpy.ndarray or cupy.ndarray") + msg = "v must be a numpy.ndarray or cupy.ndarray" + raise NotImplementedError(msg) def postcomposition(self, sigma: float) -> "ProxOperator": r"""Postcomposition @@ -235,7 +239,8 @@ def postcomposition(self, sigma: float) -> "ProxOperator": if isinstance(sigma, float): return _PostcompositionOperator(self, sigma) else: - raise NotImplementedError("sigma must be of type float") + msg = "sigma must be of type float" + raise NotImplementedError(msg) def precomposition(self, a: float, b: float | NDArray) -> "ProxOperator": r"""Precomposition @@ -264,11 +269,8 @@ def precomposition(self, a: float, b: float | NDArray) -> "ProxOperator": if isinstance(a, float) and isinstance(b, (float, np.ndarray, cp_dtype)): # type: ignore[redundant-expr] return _PrecompositionOperator(self, a, b) else: - raise NotImplementedError( - "a must be of type float and b " - "must be of type float or " - "numpy.ndarray" - ) + msg = "a must be of type float and b must be of type float or numpy.ndarray" + raise NotImplementedError(msg) def chain(self, g: "ProxOperator") -> "ProxOperator": r"""Chain @@ -336,7 +338,8 @@ def __init__(self, f: ProxOperator, v: NDArray) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(v, (np.ndarray, cp_dtype)): - raise ValueError("Second input must be a numpy.ndarray or cupy.ndarray") + msg = "Second input must be a numpy.ndarray or cupy.ndarray" + raise ValueError(msg) self.f, self.v = f, v super().__init__(None, f.hasgrad) @@ -375,7 +378,8 @@ def __init__(self, f: ProxOperator, sigma: float) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(sigma, float): - raise ValueError("Second input must be a float") + msg = "Second input must be a float" + raise ValueError(msg) self.f, self.sigma = f, sigma super().__init__(None, f.hasgrad) @@ -395,11 +399,11 @@ def __init__(self, f: ProxOperator, a: float, b: float | NDArray) -> None: # if not isinstance(f, ProxOperator): # raise ValueError('First input must be a ProxOperator') if not isinstance(a, float): - raise ValueError("Second input must be a float") + msg = "Second input must be a float" + raise ValueError(msg) if not isinstance(b, (float, np.ndarray, cp_dtype)): - raise ValueError( - "Third input must be a float, numpy.ndarray, or cupy.ndarray" - ) + msg = "Third input must be a float, numpy.ndarray, or cupy.ndarray" + raise ValueError(msg) self.f, self.a, self.b = f, a, b super().__init__(None, f.hasgrad) diff --git a/pyproximal/optimization/bregman.py b/pyproximal/optimization/bregman.py index cad51de..851eeb4 100644 --- a/pyproximal/optimization/bregman.py +++ b/pyproximal/optimization/bregman.py @@ -1,6 +1,7 @@ import time +from collections.abc import Callable from copy import deepcopy -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional +from typing import TYPE_CHECKING, Any, Optional import numpy as np from pylops.utils.typing import NDArray @@ -22,9 +23,9 @@ def Bregman( warm: bool = False, tolx: float = 1e-10, tolf: float = 1e-10, - bregcallback: Optional[Callable[[NDArray], None]] = None, + bregcallback: Callable[[NDArray], None] | None = None, show: bool = False, - **kwargs_solver: Dict[str, Any], + **kwargs_solver: dict[str, Any], ) -> NDArray: r"""Bregman iterations with Proximal Solver diff --git a/pyproximal/optimization/palm.py b/pyproximal/optimization/palm.py index b15e75c..a8bca32 100644 --- a/pyproximal/optimization/palm.py +++ b/pyproximal/optimization/palm.py @@ -1,5 +1,5 @@ import time -from typing import Callable, List, Optional, Tuple +from collections.abc import Callable import numpy as np from pylops.utils.typing import NDArray @@ -9,14 +9,14 @@ def _backtracking( - x: List[NDArray], + x: list[NDArray], tau: float, H: BilinearOperator, - proxf: Optional[ProxOperator], + proxf: ProxOperator | None, ix: int, beta: float = 0.5, niterback: int = 10, -) -> Tuple[NDArray, float]: +) -> tuple[NDArray, float]: r"""Backtracking Line-search algorithm for finding step sizes in PALM algorithms when @@ -27,7 +27,7 @@ def _backtracking( def ftilde( x: NDArray, - y: List[NDArray], + y: list[NDArray], f: BilinearOperator, g: NDArray, tau: float, @@ -59,18 +59,18 @@ def ftilde( def PALM( H: BilinearOperator, - proxf: Optional[ProxOperator], - proxg: Optional[ProxOperator], + proxf: ProxOperator | None, + proxg: ProxOperator | None, x0: NDArray, y0: NDArray, - gammaf: Optional[float] = 1.0, - gammag: Optional[float] = 1.0, + gammaf: float | None = 1.0, + gammag: float | None = 1.0, beta: float = 0.5, niter: int = 10, niterback: int = 100, - callback: Optional[Callable[[NDArray, NDArray], None]] = None, + callback: Callable[[NDArray, NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Proximal Alternating Linearized Minimization Solves the following minimization problem using the Proximal Alternating @@ -220,19 +220,19 @@ def PALM( def iPALM( H: BilinearOperator, - proxf: Optional[ProxOperator], - proxg: Optional[ProxOperator], + proxf: ProxOperator | None, + proxg: ProxOperator | None, x0: NDArray, y0: NDArray, - gammaf: Optional[float] = 1.0, - gammag: Optional[float] = 1.0, - a: List[float] = [1.0, 1.0], + gammaf: float | None = 1.0, + gammag: float | None = 1.0, + a: tuple[float, float] = (1.0, 1.0), beta: float = 0.5, niter: int = 10, niterback: int = 100, - callback: Optional[Callable[[NDArray, NDArray], None]] = None, + callback: Callable[[NDArray, NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Inertial Proximal Alternating Linearized Minimization Solves the following minimization problem using the Inertial Proximal diff --git a/pyproximal/optimization/pnp.py b/pyproximal/optimization/pnp.py index bf658f9..d40bf26 100644 --- a/pyproximal/optimization/pnp.py +++ b/pyproximal/optimization/pnp.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Tuple, Union +from collections.abc import Callable +from typing import Any from pylops.utils.typing import NDArray, ShapeLike @@ -47,8 +48,8 @@ def PlugAndPlay( dims: ShapeLike, x0: NDArray, solver: Callable[..., NDArray] = ADMM, - **kwargs_solver: Dict[str, Any], -) -> Union[NDArray, Tuple[NDArray, ...]]: + **kwargs_solver: dict[str, Any], +) -> NDArray | tuple[NDArray, ...]: r"""Plug-and-Play Priors with any proximal algorithm of choice Solves the following minimization problem using any proximal a diff --git a/pyproximal/optimization/primal.py b/pyproximal/optimization/primal.py index 423707a..f04d988 100644 --- a/pyproximal/optimization/primal.py +++ b/pyproximal/optimization/primal.py @@ -1,7 +1,8 @@ import time import warnings +from collections.abc import Callable from math import sqrt -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Optional import numpy as np from pylops.optimization.leastsquares import regularized_inversion @@ -24,7 +25,7 @@ def _backtracking( epsg: float, beta: float = 0.5, niterback: int = 10, -) -> Tuple[NDArray, float]: +) -> tuple[NDArray, float]: r"""Backtracking Line-search algorithm for finding step sizes in proximal algorithms when @@ -54,9 +55,9 @@ def _x0z0_init( x0: NDArray | None, z0: NDArray | None, Op: Optional["LinearOperator"] = None, - z0name: Optional[str] = "z0", - Opname: Optional[str] = "Op", -) -> Tuple[NDArray, NDArray]: + z0name: str | None = "z0", + Opname: str | None = "Op", +) -> tuple[NDArray, NDArray]: r"""Initialize x0 and z0 Initialize x0 and z0 using the following convention. @@ -85,9 +86,8 @@ def _x0z0_init( """ if x0 is None and z0 is None: - raise ValueError( - f"Both x0 or {z0name} are None, provide either of them or both" - ) + msg = f"Both x0 or {z0name} are None, provide either of them or both" + raise ValueError(msg) if Op is None: if x0 is None: @@ -96,7 +96,8 @@ def _x0z0_init( z0 = x0.copy() else: if x0 is None: - raise ValueError(f"x0 must be provided when {Opname} is also provided") + msg = f"x0 must be provided when {Opname} is also provided" + raise ValueError(msg) elif z0 is None: z0 = Op @ x0 return x0, z0 @@ -107,8 +108,8 @@ def ProximalPoint( x0: NDArray, tau: float, niter: int = 10, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal point algorithm @@ -209,16 +210,16 @@ def ProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - epsg: Union[float, NDArray] = 1.0, - tau: Optional[float] = None, + epsg: float | NDArray = 1.0, + tau: float | None = None, backtracking: bool = False, beta: float = 0.5, eta: float = 1.0, niter: int = 10, niterback: int = 100, - acceleration: Optional[str] = None, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + acceleration: str | None = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal gradient (optionally accelerated) @@ -329,9 +330,8 @@ def ProximalGradient( epsg_print = "Multi" if acceleration not in [None, "None", "vandenberghe", "fista"]: - raise NotImplementedError( - "Acceleration should be None, vandenberghe " "or fista" - ) + msg = "Acceleration should be None, vandenberghe or fista" + raise NotImplementedError(msg) if show: tstart = time.time() print( @@ -453,14 +453,14 @@ def AcceleratedProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - tau: Optional[float] = None, + tau: float | None = None, beta: float = 0.5, epsg: float = 1.0, niter: int = 10, niterback: int = 100, acceleration: str = "vandenberghe", - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Accelerated Proximal gradient @@ -476,6 +476,7 @@ def AcceleratedProximalGradient( "appropriate acceleration parameter as this behaviour will become default in " "version v1.0.0 and AcceleratedProximalGradient will be removed.", FutureWarning, + stacklevel=2, ) return ProximalGradient( proxf, @@ -497,14 +498,14 @@ def AndersonProximalGradient( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - epsg: Union[float, NDArray] = 1.0, - tau: Union[float, NDArray] = 1.0, + epsg: float | NDArray = 1.0, + tau: float | NDArray = 1.0, niter: int = 10, nhistory: int = 10, epsr: float = 1e-10, safeguard: bool = False, - tol: Optional[float] = None, - callback: Optional[Callable[[NDArray], None]] = None, + tol: float | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Proximal gradient with Anderson acceleration @@ -623,18 +624,20 @@ def AndersonProximalGradient( x = proxg.prox(y, epsg[0] * tau) g = y.copy() r = g - x0 - R, G = [ - g, - ], [ - r, - ] + R, G = ( + [ + g, + ], + [ + r, + ], + ) pf = proxf(x) pfg = np.inf tolbreak = False # iterate for iiter in range(niter): - # update fix point g = x - tau * proxf.grad(x) r = g - y @@ -720,16 +723,16 @@ def AndersonProximalGradient( def GeneralizedProximalGradient( - proxfs: List[ProxOperator], - proxgs: List[ProxOperator], + proxfs: list[ProxOperator], + proxgs: list[ProxOperator], x0: NDArray, - tau: Optional[float], - epsg: Union[float, NDArray] = 1.0, - weights: Optional[NDArray] = None, + tau: float | None, + epsg: float | NDArray = 1.0, + weights: NDArray | None = None, eta: float = 1.0, niter: int = 10, - acceleration: Optional[str] = None, - callback: Optional[Callable[[NDArray], None]] = None, + acceleration: str | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, ) -> NDArray: r"""Generalized Proximal gradient @@ -803,9 +806,8 @@ def GeneralizedProximalGradient( if weights is None: weights = np.ones(len(proxgs)) / len(proxgs) if len(weights) != len(proxgs) or np.sum(weights) != 1.0: - raise ValueError( - f"omega={weights} must be an array of size {len(proxgs)} " f"summing to 1" - ) + msg = f"omega={weights} must be an array of size {len(proxgs)} summing to 1" + raise ValueError(msg) # check if epgs is a vector epsg = np.asarray(epsg, dtype=float) @@ -816,9 +818,8 @@ def GeneralizedProximalGradient( epsg_print = "Multi" if acceleration not in [None, "None", "vandenberghe", "fista"]: - raise NotImplementedError( - "Acceleration should be None, vandenberghe " "or fista" - ) + msg = "Acceleration should be None, vandenberghe or fista" + raise NotImplementedError(msg) if show: tstart = time.time() print( @@ -853,7 +854,7 @@ def GeneralizedProximalGradient( # gradient grad = np.zeros_like(x) - for i, proxf in enumerate(proxfs): + for _, proxf in enumerate(proxfs): grad += proxf.grad(x) # proximal step @@ -882,7 +883,9 @@ def GeneralizedProximalGradient( if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf: float = np.sum([proxf(x) for proxf in proxfs]) - pg: float = np.sum([eg * proxg(x) for proxg, eg in zip(proxgs, epsg)]) + pg: float = np.sum( + [eg * proxg(x) for proxg, eg in zip(proxgs, epsg, strict=True)] + ) msg = "%6g %12.5e %10.3e %10.3e %10.3e" % ( iiter + 1, x[0] if x.ndim == 1 else x[0, 0], @@ -901,14 +904,14 @@ def HQS( proxf: ProxOperator, proxg: ProxOperator, x0: NDArray, - tau: Union[float, NDArray], + tau: float | NDArray, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbackz: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Half Quadratic splitting Solves the following minimization problem using Half Quadratic splitting @@ -1054,12 +1057,12 @@ def ADMM( x0: NDArray, tau: float, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = False, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbackz: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Alternating Direction Method of Multipliers Solves the following minimization problem using Alternating Direction @@ -1213,12 +1216,12 @@ def ADMML2( x0: NDArray, tau: float, niter: int = 10, - z0: Optional[NDArray] = None, + z0: NDArray | None = None, gfirst: bool = False, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, - **kwargs_solver: Dict[str, Any], -) -> Tuple[NDArray, NDArray]: + **kwargs_solver: dict[str, Any], +) -> tuple[NDArray, NDArray]: r"""Alternating Direction Method of Multipliers for L2 misfit term Solves the following minimization problem using Alternating Direction @@ -1381,10 +1384,10 @@ def LinearizedADMM( tau: float, mu: float, niter: int = 10, - z0: Optional[NDArray] = None, - callback: Optional[Callable[[NDArray], None]] = None, + z0: NDArray | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Linearized Alternating Direction Method of Multipliers Solves the following minimization problem using Linearized Alternating @@ -1513,14 +1516,14 @@ def TwIST( A: "LinearOperator", b: NDArray, x0: NDArray, - alpha: Optional[float] = None, - beta: Optional[float] = None, - eigs: Optional[Tuple[float, float]] = None, + alpha: float | None = None, + beta: float | None = None, + eigs: tuple[float, float] | None = None, niter: int = 10, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, returncost: bool = False, -) -> Union[NDArray, Tuple[NDArray, NDArray]]: +) -> NDArray | tuple[NDArray, NDArray]: r"""Two-step Iterative Shrinkage/Threshold Solves the following minimization problem using Two-step Iterative @@ -1685,10 +1688,10 @@ def DouglasRachfordSplitting( eta: float = 1.0, niter: int = 10, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbacky: bool = False, show: bool = False, -) -> Tuple[NDArray, NDArray]: +) -> tuple[NDArray, NDArray]: r"""Douglas-Rachford Splitting Solves the following minimization problem using Douglas-Rachford Splitting @@ -1770,7 +1773,6 @@ def DouglasRachfordSplitting( x = x0.copy() for iiter in range(niter): - if gfirst: y = proxg.prox(x, tau) x = x + eta * (proxf.prox(2 * y - x, tau) - y) @@ -1799,14 +1801,14 @@ def DouglasRachfordSplitting( def PPXA( # pylint: disable=invalid-name - proxfs: List[ProxOperator], - x0: NDArray | List[NDArray], + proxfs: list[ProxOperator], + x0: NDArray | list[NDArray], tau: float, eta: float = 1.0, - weights: NDArray | List[float] | None = None, + weights: NDArray | list[float] | None = None, niter: int = 1000, - tol: Optional[float] = 1e-7, - callback: Optional[Callable[..., None]] = None, + tol: float | None = 1e-7, + callback: Callable[..., None] | None = None, show: bool = False, ) -> NDArray: r"""Parallel Proximal Algorithm (PPXA) @@ -1932,7 +1934,6 @@ def PPXA( # pylint: disable=invalid-name # iterate for iiter in range(niter): - p = ncp.stack([proxfs[i].prox(y[i], tau / w[i]) for i in range(m)]) pn = ncp.sum(w[:, None] * p, axis=0) y = y + eta * (2 * pn - x - p) @@ -1946,9 +1947,7 @@ def PPXA( # pylint: disable=invalid-name if show: if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf = ncp.sum([proxfs[i](x) for i in range(m)]) - print( - f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} " f"{pf:10.3e}" - ) + print(f"{iiter + 1:6d} {ncp.real(to_numpy(x[0])):12.5e} {pf:10.3e}") # break if tolerance condition is met if ncp.abs(x - x_old).max() < tol: @@ -1964,12 +1963,12 @@ def PPXA( # pylint: disable=invalid-name def ConsensusADMM( # pylint: disable=invalid-name - proxfs: List[ProxOperator], + proxfs: list[ProxOperator], x0: NDArray, tau: float, niter: int = 1000, - tol: Optional[float] = 1e-7, - callback: Optional[Callable[..., None]] = None, + tol: float | None = 1e-7, + callback: Callable[..., None] | None = None, show: bool = False, ) -> NDArray: r"""Consensus ADMM @@ -2050,8 +2049,7 @@ def ConsensusADMM( # pylint: disable=invalid-name if show: tstart = time.time() print( - "Consensus ADMM\n" - "---------------------------------------------------------" + "Consensus ADMM\n---------------------------------------------------------" ) for i, proxf in enumerate(proxfs): print(f"Proximal operator (f{i}): {type(proxf)}") @@ -2069,7 +2067,6 @@ def ConsensusADMM( # pylint: disable=invalid-name # iterate for iiter in range(niter): - x = ncp.stack([proxfs[i].prox(x_bar - y[i], tau) for i in range(m)]) x_bar = ncp.mean(x, axis=0) y = y + x - x_bar @@ -2083,8 +2080,7 @@ def ConsensusADMM( # pylint: disable=invalid-name if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0: pf = ncp.sum([proxfs[i](x_bar) for i in range(m)]) print( - f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} " - f"{pf:10.3e}" + f"{iiter + 1:6d} {ncp.real(to_numpy(x_bar[0])):12.5e} {pf:10.3e}" ) # break if tolerance condition is met diff --git a/pyproximal/optimization/primaldual.py b/pyproximal/optimization/primaldual.py index 4191147..28c1637 100644 --- a/pyproximal/optimization/primaldual.py +++ b/pyproximal/optimization/primaldual.py @@ -1,5 +1,6 @@ import time -from typing import TYPE_CHECKING, Callable, Optional, Tuple, Union +from collections.abc import Callable +from typing import TYPE_CHECKING import numpy as np from pylops.utils.backend import get_array_module, to_numpy @@ -16,18 +17,18 @@ def PrimalDual( proxg: ProxOperator, A: "LinearOperator", x0: NDArray, - tau: Union[float, NDArray], - mu: Union[float, NDArray], - y0: Optional[NDArray] = None, - z: Optional[NDArray] = None, + tau: float | NDArray, + mu: float | NDArray, + y0: NDArray | None = None, + z: NDArray | None = None, theta: float = 1.0, niter: int = 10, gfirst: bool = True, - callback: Optional[Callable[..., None]] = None, + callback: Callable[..., None] | None = None, callbacky: bool = False, returny: bool = False, show: bool = False, -) -> Union[NDArray, Tuple[NDArray, NDArray]]: +) -> NDArray | tuple[NDArray, NDArray]: r"""Primal-dual algorithm Solves the following (possibly) nonlinear minimization problem using @@ -231,12 +232,12 @@ def AdaptivePrimalDual( eta: float = 0.95, s: float = 1.0, delta: float = 1.5, - z: Optional[NDArray] = None, + z: NDArray | None = None, niter: int = 10, tol: float = 1e-10, - callback: Optional[Callable[[NDArray], None]] = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, -) -> Tuple[NDArray, Tuple[NDArray, NDArray, NDArray]]: +) -> tuple[NDArray, tuple[NDArray, NDArray, NDArray]]: r"""Adaptive Primal-dual algorithm Solves the minimization problem in @@ -351,7 +352,6 @@ def AdaptivePrimalDual( iiter = 0 while iiter < niter and p > tol and d > tol: - # store old values xold = x.copy() yold = y.copy() diff --git a/pyproximal/optimization/segmentation.py b/pyproximal/optimization/segmentation.py index 2e2ef5b..51bf084 100644 --- a/pyproximal/optimization/segmentation.py +++ b/pyproximal/optimization/segmentation.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Optional, Tuple +from collections.abc import Callable +from typing import Any import numpy as np from pylops import BlockDiag, Gradient @@ -13,14 +14,14 @@ def Segment( cl: NDArray, sigma: float, alpha: float, - clsigmas: Optional[NDArray] = None, - z: Optional[NDArray] = None, + clsigmas: NDArray | None = None, + z: NDArray | None = None, niter: int = 10, - x0: Optional[NDArray] = None, - callback: Optional[Callable[[NDArray], None]] = None, + x0: NDArray | None = None, + callback: Callable[[NDArray], None] | None = None, show: bool = False, - kwargs_simplex: Optional[Dict[str, Any]] = None, -) -> Tuple[NDArray, NDArray]: + kwargs_simplex: dict[str, Any] | None = None, +) -> tuple[NDArray, NDArray]: r"""Primal-dual algorithm for image segmentation Perform image segmentation over :math:`N_{cl}` classes using the diff --git a/pyproximal/optimization/sr3.py b/pyproximal/optimization/sr3.py index 25e4925..cf97bfd 100644 --- a/pyproximal/optimization/sr3.py +++ b/pyproximal/optimization/sr3.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np import pylops @@ -98,7 +98,7 @@ def SR3( data: NDArray, kappa: float, eps: float, - x0: Optional[NDArray] = None, + x0: NDArray | None = None, adaptive: bool = True, iter_lim_outer: int = 100, iter_lim_inner: int = 100, diff --git a/pyproximal/projection/Box.py b/pyproximal/projection/Box.py index 6534c1e..38e0f01 100644 --- a/pyproximal/projection/Box.py +++ b/pyproximal/projection/Box.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray from scipy.optimize import bisect @@ -41,8 +39,8 @@ class BoxProj: def __init__( self, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, ) -> None: self.lower = lower self.upper = upper @@ -107,8 +105,8 @@ def __init__( self, coeffs: NDArray, scalar: float, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, maxiter: int = 100, xtol: float = 1e-5, ) -> None: diff --git a/pyproximal/projection/Euclidean.py b/pyproximal/projection/Euclidean.py index 6161999..001a9b7 100644 --- a/pyproximal/projection/Euclidean.py +++ b/pyproximal/projection/Euclidean.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -34,7 +32,7 @@ class EuclideanBallProj: """ - def __init__(self, center: Union[NDArray, float], radius: float): + def __init__(self, center: NDArray | float, radius: float): self.center = center self.radius = radius diff --git a/pyproximal/projection/GenericIntersection.py b/pyproximal/projection/GenericIntersection.py index 8d4cd27..623cb7e 100644 --- a/pyproximal/projection/GenericIntersection.py +++ b/pyproximal/projection/GenericIntersection.py @@ -1,4 +1,4 @@ -from typing import Callable, List +from collections.abc import Callable from pylops.utils.typing import NDArray @@ -100,12 +100,11 @@ class GenericIntersectionProj: def __init__( self, - projections: List[Callable[[NDArray], NDArray]], + projections: list[Callable[[NDArray], NDArray]], niter: int = 1000, tol: float = 1e-6, use_parallel: bool = False, ) -> None: - self.projections = projections self.niter = niter self.tol = tol @@ -125,14 +124,16 @@ def __call__(self, x: NDArray) -> NDArray: def _single_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(\mathbf{x})` for :math:`m=1`.""" if len(self.projections) != 1: - raise ValueError("len(projections) should be 1") + msg = "len(projections) should be 1" + raise ValueError(msg) return self.projections[0](x0) def _two_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(\mathbf{x})` for :math:`m=2`.""" if len(self.projections) != 2: - raise ValueError("len(projections) should be 2") + msg = "len(projections) should be 2" + raise ValueError(msg) step1, step2 = self.projections @@ -147,7 +148,8 @@ def _two_proj(self, x0: NDArray) -> NDArray: def _more_proj(self, x0: NDArray) -> NDArray: r"""Compute projection :math:`P_C(x)` for :math:`m \ge 2`.""" if len(self.projections) < 2: - raise ValueError("len(projections) should be 2 or larger") + msg = "len(projections) should be 2 or larger" + raise ValueError(msg) return parallel_dykstra_projection( x0, diff --git a/pyproximal/projection/Intersection.py b/pyproximal/projection/Intersection.py index 9a886e8..30160d3 100644 --- a/pyproximal/projection/Intersection.py +++ b/pyproximal/projection/Intersection.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -41,7 +39,7 @@ def __init__( self, k: int, n: int, - sigma: Union[float, NDArray], + sigma: float | NDArray, niter: int = 100, tol: float = 1e-5, ) -> None: @@ -56,7 +54,7 @@ def __init__( def __call__(self, x: NDArray) -> NDArray: x = x.reshape(self.k, self.n) x12 = np.zeros((self.k, self.k, self.n)) - for iiter in range(self.niter): + for _ in range(self.niter): xold = x.copy() for i1 in range(self.k - 1): for i2 in range(i1 + 1, self.k): diff --git a/pyproximal/projection/L0.py b/pyproximal/projection/L0.py index a78a206..e470d28 100644 --- a/pyproximal/projection/L0.py +++ b/pyproximal/projection/L0.py @@ -77,10 +77,14 @@ def __call__(self, x: NDArray) -> NDArray: class L01BallProj(L10BallProj): def __init__(self, radius: int) -> None: - warnings.warn( + msg = ( "The L01BallProj class has been renamed L10BallProj due " "to a mistake in the original choice of the name. As such " - "L01BallProj will be deprecated in v1.0.0.", + "L01BallProj will be deprecated in v1.0.0." + ) + warnings.warn( + msg, FutureWarning, + stacklevel=2, ) super().__init__(radius) diff --git a/pyproximal/proximal/Box.py b/pyproximal/proximal/Box.py index 7adc82d..ae56271 100644 --- a/pyproximal/proximal/Box.py +++ b/pyproximal/proximal/Box.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -29,8 +27,8 @@ class Box(ProxOperator): def __init__( self, - lower: Union[float, NDArray] = -np.inf, - upper: Union[float, NDArray] = np.inf, + lower: float | NDArray = -np.inf, + upper: float | NDArray = np.inf, ) -> None: super().__init__(None, False) self.lower = lower diff --git a/pyproximal/proximal/ETP.py b/pyproximal/proximal/ETP.py index 0b9be0a..8643114 100644 --- a/pyproximal/proximal/ETP.py +++ b/pyproximal/proximal/ETP.py @@ -51,9 +51,11 @@ class ETP(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.0) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma <= 0: - raise ValueError('Variable "gamma" must be strictly positive.') + msg = 'Variable "gamma" must be strictly positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma diff --git a/pyproximal/proximal/Euclidean.py b/pyproximal/proximal/Euclidean.py index fd319bb..c068601 100644 --- a/pyproximal/proximal/Euclidean.py +++ b/pyproximal/proximal/Euclidean.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -85,7 +83,7 @@ class EuclideanBall(ProxOperator): """ - def __init__(self, center: Union[NDArray, float], radius: float) -> None: + def __init__(self, center: NDArray | float, radius: float) -> None: super().__init__(None, False) self.center = center self.radius = radius diff --git a/pyproximal/proximal/Geman.py b/pyproximal/proximal/Geman.py index aeac30e..371591d 100644 --- a/pyproximal/proximal/Geman.py +++ b/pyproximal/proximal/Geman.py @@ -1,5 +1,3 @@ -from typing import Tuple - import numpy as np from pylops.utils.typing import NDArray @@ -50,9 +48,11 @@ class Geman(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.3) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma <= 0: - raise ValueError('Variable "gamma" must be strictly positive.') + msg = 'Variable "gamma" must be strictly positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma @@ -80,7 +80,7 @@ def prox(self, x: NDArray, tau: float) -> NDArray: @staticmethod def _find_local_minima( b: NDArray, c: NDArray, d: NDArray - ) -> Tuple[NDArray, NDArray]: + ) -> tuple[NDArray, NDArray]: f = -((c - b**2.0 / 3.0) ** 3.0) / 27.0 g = (2.0 * b**3.0 - 9.0 * b * c + 27.0 * d) / 27.0 idx = g**2.0 / 4.0 - f <= 0 diff --git a/pyproximal/proximal/GenericIntersection.py b/pyproximal/proximal/GenericIntersection.py index 7973aa7..dff8884 100644 --- a/pyproximal/proximal/GenericIntersection.py +++ b/pyproximal/proximal/GenericIntersection.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List +from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray @@ -38,7 +39,7 @@ class GenericIntersectionProx(ProxOperator): def __init__( self, - projections: List[Callable[[NDArray], NDArray]], + projections: list[Callable[[NDArray], NDArray]], niter: int = 1000, tol: float = 1e-6, use_parallel: bool = False, diff --git a/pyproximal/proximal/Intersection.py b/pyproximal/proximal/Intersection.py index d417035..4bf74a1 100644 --- a/pyproximal/proximal/Intersection.py +++ b/pyproximal/proximal/Intersection.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.utils.typing import NDArray @@ -38,7 +36,7 @@ def __init__( self, k: int, n: int, - sigma: Union[float, NDArray], + sigma: float | NDArray, niter: int = 100, tol: float = 1e-5, call: bool = True, diff --git a/pyproximal/proximal/L0.py b/pyproximal/proximal/L0.py index 8a94f39..00612a5 100644 --- a/pyproximal/proximal/L0.py +++ b/pyproximal/proximal/L0.py @@ -1,5 +1,6 @@ import warnings -from typing import Any, Callable, Union +from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray @@ -42,7 +43,7 @@ def _hardthreshold(x: NDArray, thresh: float) -> NDArray: def _current_radius( radius: IntCallableLike, count: int, -) -> Union[int, NDArray]: +) -> int | NDArray: if not callable(radius): return radius else: @@ -233,5 +234,6 @@ def __init__(self, ndim: int, radius: IntCallableLike) -> None: "to a mistake in the original choice of the name. As such " "L01Ball will be deprecated in v1.0.0.", FutureWarning, + stacklevel=2, ) super().__init__(ndim, radius) diff --git a/pyproximal/proximal/L1.py b/pyproximal/proximal/L1.py index 6fbf6bc..5af3bfa 100644 --- a/pyproximal/proximal/L1.py +++ b/pyproximal/proximal/L1.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray @@ -40,7 +41,7 @@ def _softthreshold(x: NDArray, thresh: float) -> NDArray: def _current_sigma( sigma: FloatCallableLike, count: int, -) -> Union[float, NDArray]: +) -> float | NDArray: if not callable(sigma): return sigma else: @@ -106,7 +107,7 @@ class L1(ProxOperator): def __init__( self, sigma: FloatCallableLike = 1.0, - g: Optional[NDArray] = None, + g: NDArray | None = None, ) -> None: super().__init__(None, False) self.sigma = sigma diff --git a/pyproximal/proximal/L2.py b/pyproximal/proximal/L2.py index 8084bc7..2fb271b 100644 --- a/pyproximal/proximal/L2.py +++ b/pyproximal/proximal/L2.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Optional import numpy as np from pylops import Identity, MatrixMult @@ -132,17 +133,17 @@ class L2(ProxOperator): def __init__( self, Op: Optional["LinearOperator"] = None, - b: Optional[NDArray] = None, - q: Optional[NDArray] = None, + b: NDArray | None = None, + q: NDArray | None = None, sigma: float = 1.0, alpha: float = 1.0, qgrad: bool = True, - niter: Union[int, Callable[[int], int]] = 10, - x0: Optional[NDArray] = None, + niter: int | Callable[[int], int] = 10, + x0: NDArray | None = None, warm: bool = True, - solver: Optional[str] = "legacy", - densesolver: Optional[str] = None, - kwargs_solver: Optional[Dict[str, Any]] = None, + solver: str | None = "legacy", + densesolver: str | None = None, + kwargs_solver: dict[str, Any] | None = None, ) -> None: super().__init__(Op, True) self.b = b @@ -165,10 +166,11 @@ def __init__( elif self.solver == "cgls": self.normaleqs = False else: - raise ValueError( + msg = ( f"Provided solver={self.solver}. " "Available options are: 'legacy', 'cg', 'cgls'." ) + raise ValueError(msg) # when using factorize, store the first tau*sigma=0 so that the # first time it will be recomputed (as tau cannot be 0) if self.densesolver == "factorize": @@ -342,8 +344,8 @@ def __init__( b: NDArray, nfft: int = 2**10, sigma: float = 1.0, - dims: Optional[ShapeLike] = None, - dir: Optional[int] = None, + dims: ShapeLike | None = None, + dir: int | None = None, ) -> None: super().__init__(None, True) self.nfft = nfft diff --git a/pyproximal/proximal/Log.py b/pyproximal/proximal/Log.py index 8f38640..5a9d10b 100644 --- a/pyproximal/proximal/Log.py +++ b/pyproximal/proximal/Log.py @@ -73,9 +73,11 @@ class Log(ProxOperator): def __init__(self, sigma: float, gamma: float = 1.3) -> None: super().__init__(None, False) if sigma < 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if gamma < 0: - raise ValueError('Variable "gamma" must be positive.') + msg = 'Variable "gamma" must be positive.' + raise ValueError(msg) self.sigma = sigma self.gamma = gamma @@ -146,7 +148,8 @@ class Log1(ProxOperator): def __init__(self, sigma: float, delta: float = 1e-10) -> None: super().__init__(None, False) if delta < 0: - raise ValueError('Variable "delta" must be positive.') + msg = 'Variable "delta" must be positive.' + raise ValueError(msg) self.sigma = sigma self.delta = delta diff --git a/pyproximal/proximal/Nonlinear.py b/pyproximal/proximal/Nonlinear.py index 82ac4e8..eef13cd 100644 --- a/pyproximal/proximal/Nonlinear.py +++ b/pyproximal/proximal/Nonlinear.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Tuple from pylops.utils.typing import NDArray @@ -84,13 +83,13 @@ def _funprox(self, x: NDArray, tau: float) -> float: def _gradprox(self, x: NDArray, tau: float) -> NDArray: return self.grad(x) + 1.0 / tau * (x - self.y) - def _fungradprox(self, x: NDArray, tau: float) -> Tuple[float, NDArray]: + def _fungradprox(self, x: NDArray, tau: float) -> tuple[float, NDArray]: f, g = self.fungrad(x) f = f + 1.0 / (2 * tau) * ((x - self.y) ** 2).sum() g = g + 1.0 / tau * (x - self.y) return f, g - def fungrad(self, x: NDArray) -> Tuple[float, NDArray]: + def fungrad(self, x: NDArray) -> tuple[float, NDArray]: f = self.fun(x) g = self.grad(x) return f, g diff --git a/pyproximal/proximal/Nuclear.py b/pyproximal/proximal/Nuclear.py index a4227ed..149c5c0 100644 --- a/pyproximal/proximal/Nuclear.py +++ b/pyproximal/proximal/Nuclear.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.optimization.cls_sparsity import _softthreshold from pylops.utils.typing import NDArray, ShapeLike @@ -57,7 +55,7 @@ class Nuclear(ProxOperator): """ - def __init__(self, dim: ShapeLike, sigma: Union[float, NDArray] = 1.0) -> None: + def __init__(self, dim: ShapeLike, sigma: float | NDArray = 1.0) -> None: super().__init__(None, False) self.dim = dim self.sigma = sigma diff --git a/pyproximal/proximal/Orthogonal.py b/pyproximal/proximal/Orthogonal.py index c9b2a40..479860b 100644 --- a/pyproximal/proximal/Orthogonal.py +++ b/pyproximal/proximal/Orthogonal.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pylops.utils.typing import NDArray @@ -65,7 +65,7 @@ def __init__( f: ProxOperator, Q: "LinearOperator", partial: bool = False, - b: Optional[NDArray] = None, + b: NDArray | None = None, alpha: float = 1.0, ) -> None: super().__init__(None, False) diff --git a/pyproximal/proximal/Quadratic.py b/pyproximal/proximal/Quadratic.py index 3631886..8a6ebfd 100644 --- a/pyproximal/proximal/Quadratic.py +++ b/pyproximal/proximal/Quadratic.py @@ -72,15 +72,16 @@ class Quadratic(ProxOperator): def __init__( self, Op: Optional["LinearOperator"] = None, - b: Optional[NDArray] = None, + b: NDArray | None = None, c: float = 0.0, niter: int = 10, - x0: Optional[NDArray] = None, + x0: NDArray | None = None, warm: bool = True, ) -> None: if Op is not None: if Op.shape[0] != Op.shape[1]: - raise ValueError("Op must be square") + msg = "Op must be square" + raise ValueError(msg) super().__init__(Op, True) self.b = b if self.Op is not None and self.b is None: diff --git a/pyproximal/proximal/QuadraticEnvelope.py b/pyproximal/proximal/QuadraticEnvelope.py index 5463942..a07ee16 100644 --- a/pyproximal/proximal/QuadraticEnvelope.py +++ b/pyproximal/proximal/QuadraticEnvelope.py @@ -1,5 +1,3 @@ -from typing import Union - import numpy as np from pylops.optimization.cls_sparsity import _hardthreshold from pylops.utils.typing import IntNDArray, NDArray, ShapeLike @@ -165,7 +163,7 @@ class QuadraticEnvelopeCardIndicator(ProxOperator): """ - def __init__(self, r0: Union[int, IntNDArray]) -> None: + def __init__(self, r0: int | IntNDArray) -> None: super().__init__(None, False) self.r0 = r0 @@ -311,7 +309,6 @@ def prox(self, x: NDArray, tau: float) -> NDArray: zk[self.r0 :] = (1 + rho) * yk[self.r0 :] for k, ii in enumerate(ind): - if ii < self.r0: a = a + (rho + 1) / rho b = b + (rho + 1) / rho * yk[ii] diff --git a/pyproximal/proximal/RelaxedMS.py b/pyproximal/proximal/RelaxedMS.py index 588a81f..92ac1de 100644 --- a/pyproximal/proximal/RelaxedMS.py +++ b/pyproximal/proximal/RelaxedMS.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Union +from collections.abc import Callable +from typing import Any import numpy as np from pylops.utils.typing import NDArray @@ -34,7 +35,7 @@ def _l2(x: NDArray, alpha: float) -> NDArray: def _current_kappa( kappa: FloatCallableLike, count: int, -) -> Union[float, NDArray]: +) -> float | NDArray: if not callable(kappa): return kappa else: diff --git a/pyproximal/proximal/SCAD.py b/pyproximal/proximal/SCAD.py index acef8b2..9e7ef77 100644 --- a/pyproximal/proximal/SCAD.py +++ b/pyproximal/proximal/SCAD.py @@ -53,9 +53,11 @@ def __init__(self, sigma: float, a: float = 3.7) -> None: super().__init__(None, False) self.sigma = sigma if sigma <= 0: - raise ValueError('Variable "sigma" must be positive.') + msg = 'Variable "sigma" must be positive.' + raise ValueError(msg) if a <= 2: - raise ValueError('Variable "a" must be larger than two.') + msg = 'Variable "a" must be larger than two.' + raise ValueError(msg) self.a = a def __call__(self, x: NDArray) -> float: @@ -68,7 +70,7 @@ def elementwise(self, x: NDArray) -> NDArray: f[ind] = self.sigma * absx[ind] ind = np.logical_and(self.sigma < absx, absx <= self.a * self.sigma) f[ind] = ( - -x[ind] ** 2 + 2 * self.a * self.sigma * absx[ind] - self.sigma**2 + -(x[ind] ** 2) + 2 * self.a * self.sigma * absx[ind] - self.sigma**2 ) / (2 * (self.a - 1)) ind = absx > self.a * self.sigma f[ind] = (self.a + 1) * self.sigma**2 / 2 diff --git a/pyproximal/proximal/Simplex.py b/pyproximal/proximal/Simplex.py index 60809cd..a6c40dd 100644 --- a/pyproximal/proximal/Simplex.py +++ b/pyproximal/proximal/Simplex.py @@ -1,5 +1,4 @@ import logging -from typing import Optional, Union import numpy as np from pylops.utils.backend import get_array_module, to_cupy_conditional @@ -30,7 +29,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, xtol: float = 1e-8, @@ -38,7 +37,8 @@ def __init__( ) -> None: super().__init__(None, False) if dims is not None and len(dims) != 2: - raise ValueError("provide only 2 dimensions, or None") + msg = "provide only 2 dimensions, or None" + raise ValueError(msg) self.n = n self.radius = radius self.dims = dims @@ -96,7 +96,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -163,7 +163,7 @@ def __init__( self, n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -208,7 +208,7 @@ def prox(self, x: NDArray, tau: float) -> NDArray: def Simplex( n: int, radius: float, - dims: Optional[ShapeLike] = None, + dims: ShapeLike | None = None, axis: int = -1, maxiter: int = 100, ftol: float = 1e-8, @@ -265,9 +265,10 @@ def Simplex( """ if engine not in ["numpy", "numba", "cuda"]: - raise KeyError("engine must be numpy or numba or cuda") + msg = "engine must be numpy or numba or cuda" + raise KeyError(msg) - s: Union[_Simplex, _Simplex_numba, _Simplex_cuda] + s: _Simplex | _Simplex_numba | _Simplex_cuda if engine == "numba" and jit is not None: s = _Simplex_numba( n, diff --git a/pyproximal/proximal/Sum.py b/pyproximal/proximal/Sum.py index d78850c..e20e13d 100644 --- a/pyproximal/proximal/Sum.py +++ b/pyproximal/proximal/Sum.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List +from collections.abc import Callable +from typing import Any from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray @@ -109,8 +110,8 @@ class Sum(ProxOperator): def __init__( self, - ops: List[ProxOperator], - weights: NDArray | List[float] | None = None, + ops: list[ProxOperator], + weights: NDArray | list[float] | None = None, niter: int = 1000, tol: float = 1e-7, use_parallel: bool = False, @@ -180,14 +181,16 @@ def prox(self, x: NDArray, tau: float, **kwargs: Any) -> NDArray: def _single_prox(self, x0: NDArray, tau: float) -> NDArray: r"""Compute :math:`\prox_{\tau \ f}(\mathbf{x})` for :math:`m = 1`.""" if len(self.ops) != 1: - raise ValueError("len(ops) should be 1") + msg = "len(ops) should be 1" + raise ValueError(msg) return self.ops[0].prox(x0, tau) def _two_prox(self, x0: NDArray, tau: float) -> NDArray: r"""Compute :math:`\prox_{\tau \ f + g}(\mathbf{x})` for :math:`m = 2`.""" if len(self.ops) != 2: - raise ValueError("len(ops) should be 2") + msg = "len(ops) should be 2" + raise ValueError(msg) def bind_tau( prox: Callable[[NDArray, float], NDArray], @@ -210,7 +213,7 @@ def _more_prox(self, x0: NDArray, tau: float) -> NDArray: for :math:`m \ge 2`. """ - def tau_policy(tau: float, w: NDArray | List[float]) -> List[float]: + def tau_policy(tau: float, w: NDArray | list[float]) -> list[float]: if self.use_original_tau: # legacy: all prox_i use the same tau return [tau] * len(w) @@ -218,7 +221,8 @@ def tau_policy(tau: float, w: NDArray | List[float]) -> List[float]: return [tau / wi for wi in w] if len(self.ops) < 2: - raise ValueError("len(ops) should be 2 or larger") + msg = "len(ops) should be 2 or larger" + raise ValueError(msg) return parallel_dykstra_prox( x0, diff --git a/pyproximal/proximal/TV.py b/pyproximal/proximal/TV.py index a3dc3ef..bf32148 100644 --- a/pyproximal/proximal/TV.py +++ b/pyproximal/proximal/TV.py @@ -1,5 +1,6 @@ +from collections.abc import Callable from copy import deepcopy -from typing import Any, Callable, Union +from typing import Any import numpy as np from pylops import FirstDerivative, Gradient @@ -42,7 +43,7 @@ def __init__( self, dims: ShapeLike, sigma: float = 1.0, - niter: Union[int, Callable[[int], int]] = 10, + niter: int | Callable[[int], int] = 10, rtol: float = 1e-4, **kwargs: Any, ) -> None: @@ -185,7 +186,8 @@ def prox(self, x: NDArray, tau: float) -> NDArray: while iter <= niter: # Current Solution if self.ndim == 0: - raise ValueError("Need to input at least one value") + msg = "Need to input at least one value" + raise ValueError(msg) if self.ndim >= 1: div = np.concatenate( diff --git a/pyproximal/proximal/VStack.py b/pyproximal/proximal/VStack.py index 4157d84..8c3cc6a 100644 --- a/pyproximal/proximal/VStack.py +++ b/pyproximal/proximal/VStack.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from pylops.utils.typing import NDArray, ShapeLike @@ -50,8 +50,8 @@ class VStack(ProxOperator): def __init__( self, ops: list["LinearOperator"], - nn: Optional[list[ShapeLike]] = None, - restr: Optional[list["LinearOperator"]] = None, + nn: list[ShapeLike] | None = None, + restr: list["LinearOperator"] | None = None, ) -> None: super().__init__(None, any(op.hasgrad for op in ops)) self.ops = ops @@ -69,28 +69,32 @@ def __init__( # store required size of input self.nx = np.sum([restr.iava.size for restr in self.restr]) else: - raise ValueError("Provide either nn or restr") + msg = "Provide either nn or restr" + raise ValueError(msg) def __call__(self, x: NDArray) -> float: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) f = 0.0 if hasattr(self, "nn"): for iop, op in enumerate(self.ops): f += op(x[self.xin[iop] : self.xend[iop]]) else: - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f += op(restr.matvec(x)) return float(f) @_check_tau def prox(self, x: NDArray, tau: float) -> NDArray: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) + if hasattr(self, "nn"): f = np.hstack( [ @@ -100,15 +104,16 @@ def prox(self, x: NDArray, tau: float) -> NDArray: ) else: f = np.zeros_like(x) - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f[restr.iava] = op.prox(restr.matvec(x), tau) return f def grad(self, x: NDArray) -> NDArray: if x.size != self.nx: - raise ValueError( + msg = ( f"x must have size {self.nx}, instead the provided x has size {x.size}" ) + raise ValueError(msg) if hasattr(self, "nn"): f = np.hstack( [ @@ -118,6 +123,6 @@ def grad(self, x: NDArray) -> NDArray: ) else: f = np.zeros_like(x) - for op, restr in zip(self.ops, self.restr): + for op, restr in zip(self.ops, self.restr, strict=True): f[restr.iava] = op.grad(restr.matvec(x)) return f diff --git a/pyproximal/proximal/_Dykstra.py b/pyproximal/proximal/_Dykstra.py index 781ad77..3596435 100644 --- a/pyproximal/proximal/_Dykstra.py +++ b/pyproximal/proximal/_Dykstra.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List, Sequence, TypeVar +from collections.abc import Callable, Sequence +from typing import Any, TypeVar from pylops.utils.backend import get_array_module from pylops.utils.typing import NDArray @@ -20,7 +21,8 @@ def _select_impl_by_arity( ) -> ProxOpOrProj: """Choose implementation by ``len(items)`` and ``use_parallel``.""" if not items: - raise ValueError("items must not be empty") + msg = "items must not be empty" + raise ValueError(msg) if len(items) == 1: return single if len(items) == 2 and not use_parallel: @@ -59,7 +61,7 @@ def dykstra_two( def parallel_dykstra_projection( x0: NDArray, - proj_ops: List[Proj], + proj_ops: list[Proj], *, niter: int, tol: float, @@ -88,10 +90,10 @@ def parallel_dykstra_projection( def parallel_dykstra_prox( x0: NDArray, - prox_ops: List[ProxOp], + prox_ops: list[ProxOp], *, - weights: NDArray | List[float], - taus: NDArray | List[float], + weights: NDArray | list[float], + taus: NDArray | list[float], niter: int, tol: float, ) -> NDArray: diff --git a/pyproximal/proximal/_Simplex_cuda.py b/pyproximal/proximal/_Simplex_cuda.py index 6f137f6..75862fd 100644 --- a/pyproximal/proximal/_Simplex_cuda.py +++ b/pyproximal/proximal/_Simplex_cuda.py @@ -18,7 +18,7 @@ def bisect_jit_cuda( """Bisection method (See _Simplex_numba for details).""" a, b = bisect_lower, bisect_upper fa = fun_jit_cuda(a, x, coeffs, scalar, lower, upper) - for iiter in range(maxiter): + for _ in range(maxiter): c = (a + b) / 2.0 if (b - a) / 2 < xtol: return c diff --git a/pyproximal/proximal/_Simplex_numba.py b/pyproximal/proximal/_Simplex_numba.py index 26ecfbf..f04537b 100644 --- a/pyproximal/proximal/_Simplex_numba.py +++ b/pyproximal/proximal/_Simplex_numba.py @@ -1,9 +1,8 @@ -from typing import Any import os +from typing import Any import numpy as np from numba import jit - from pylops.utils.typing import NDArray # detect whether to use parallel or not @@ -62,7 +61,7 @@ def bisect_jit( """ a, b = bisect_lower, bisect_upper fa = fun_jit(a, x, coeffs, scalar, lower, upper) - for iiter in range(maxiter): + for _ in range(maxiter): c = (a + b) / 2.0 if (b - a) / 2 < xtol: return c diff --git a/pyproximal/utils/__init__.py b/pyproximal/utils/__init__.py index 9fd9eee..bef7422 100644 --- a/pyproximal/utils/__init__.py +++ b/pyproximal/utils/__init__.py @@ -1,4 +1,4 @@ from .backend import * -from .utils import Report from .moreau import moreau from .typing import * +from .utils import Report diff --git a/pyproximal/utils/bilinear.py b/pyproximal/utils/bilinear.py index 564e001..ed63e3f 100644 --- a/pyproximal/utils/bilinear.py +++ b/pyproximal/utils/bilinear.py @@ -48,7 +48,7 @@ def __init__(self) -> None: pass @abstractmethod - def __call__(self, x: NDArray, y: Optional[NDArray] = None) -> Any: + def __call__(self, x: NDArray, y: NDArray | None = None) -> Any: pass @abstractmethod @@ -147,7 +147,7 @@ def __init__( self.sizex = self.n * self.k self.sizey = self.m * self.k - def __call__(self, x: NDArray, y: Optional[NDArray] = None) -> float: + def __call__(self, x: NDArray, y: NDArray | None = None) -> float: # x can be concatenated [x,y] or just x if y is provided if y is None: x, y = x[: self.sizex], x[self.sizex :] @@ -185,12 +185,14 @@ def _matvecy(self, y: NDArray) -> NDArray: def matvec(self, x: NDArray) -> NDArray: # check that no ambiguous situation arises due to n==m if self.n == self.m: - raise NotImplementedError( + msg = ( "Since n=m, this method" "cannot distinguish automatically" "between _matvecx and _matvecy. " "Explicitely call either of those two methods." ) + raise NotImplementedError(msg) + if x.size == self.sizex: y = self._matvecx(x) else: @@ -201,7 +203,8 @@ def lx(self, x: NDArray) -> float: if self.Op is not None: # Lipschitz constant for grad_y H involves Op.H Op and X.H X. # This is non-trivial and depends on Op's norm. - raise ValueError("lx cannot be computed when using Op") + msg = "lx cannot be computed when using Op" + raise ValueError(msg) # if Op is None, H = 0.5 * ||XY - d||^2. grad_y H = X.H (XY-d) # Lipschitz of grad_y H involves ||X.H X||_F or ||X||_2^2 X = x.reshape(self.n, self.k) @@ -211,7 +214,8 @@ def ly(self, y: NDArray) -> float: if self.Op is not None: # Lipschitz constant for grad_x H involves Op.H Op and Y Y.H. # This is non-trivial and depends on Op's norm. - raise ValueError("ly cannot be computed when using Op") + msg = "ly cannot be computed when using Op" + raise ValueError(msg) # if Op is None, H = 0.5 * ||XY - d||^2. grad_x H = (XY-d)Y.H # Lipschitz of grad_x H involves ||Y Y.H||_F or ||Y||_2^2 Y = y.reshape(self.k, self.m) diff --git a/pyproximal/utils/gradtest.py b/pyproximal/utils/gradtest.py index fc7c26e..d3e48be 100644 --- a/pyproximal/utils/gradtest.py +++ b/pyproximal/utils/gradtest.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import numpy as np from pylops.utils.backend import get_module from pylops.utils.typing import NDArray @@ -11,7 +9,7 @@ def gradtest_proximal( Op: ProxOperator, n: int, - x: Optional[NDArray] = None, + x: NDArray | None = None, dtype: str = "float64", delta: float = 1e-6, rtol: float = 1e-6, @@ -97,7 +95,7 @@ def gradtest_proximal( iqx = np.random.randint(0, n) r_or_i = np.random.randint(0, 2) - delta1: Union[float, complex] = delta + delta1: float | complex = delta if r_or_i != 0: delta1 = delta * 1j @@ -217,7 +215,7 @@ def gradtest_bilinear( iqx, iqy = np.random.randint(0, nx), np.random.randint(0, ny) x_or_y = np.random.randint(0, 2) - delta1: Union[float, complex] = delta + delta1: float | complex = delta if complexflag: r_or_i = np.random.randint(0, 2) if r_or_i == 1: diff --git a/pyproximal/utils/moreau.py b/pyproximal/utils/moreau.py index b230d45..c5f684f 100644 --- a/pyproximal/utils/moreau.py +++ b/pyproximal/utils/moreau.py @@ -64,5 +64,6 @@ def moreau( return True else: if raiseerror: - raise ValueError("Moreau identity not verified") + msg = "Moreau identity not verified" + raise ValueError(msg) return False diff --git a/pyproximal/utils/typing.py b/pyproximal/utils/typing.py index 5693847..05c801c 100644 --- a/pyproximal/utils/typing.py +++ b/pyproximal/utils/typing.py @@ -3,9 +3,9 @@ "IntCallableLike", ] -from typing import Callable, Union +from collections.abc import Callable from pylops.utils.typing import NDArray -FloatCallableLike = Union[float, NDArray, Callable[[int], Union[float, NDArray]]] -IntCallableLike = Union[int, Callable[[int], int]] +FloatCallableLike = float | NDArray | Callable[[int], float | NDArray] +IntCallableLike = int | Callable[[int], int] diff --git a/pyproximal/utils/utils.py b/pyproximal/utils/utils.py index b060f01..0b9bb06 100644 --- a/pyproximal/utils/utils.py +++ b/pyproximal/utils/utils.py @@ -1,7 +1,6 @@ __all__ = ["Report"] from types import ModuleType -from typing import Optional try: # scooby is a soft dependency for pyproximal @@ -11,7 +10,7 @@ class ScoobyReport: # type: ignore[no-redef] def __init__( self, - additional: Optional[list[str | ModuleType]], + additional: list[str | ModuleType] | None, core: list[str | ModuleType] | None, optional: list[str | ModuleType] | None, ncol: int, @@ -76,7 +75,7 @@ class Report(ScoobyReport): # type: ignore[misc] def __init__( self, - add_pckg: Optional[list[str | ModuleType]] = None, + add_pckg: list[str | ModuleType] | None = None, ncol: int = 3, text_width: int = 80, sort: bool = False, diff --git a/pytests/test_basic.py b/pytests/test_basic.py index 80233fa..d826e30 100644 --- a/pytests/test_basic.py +++ b/pytests/test_basic.py @@ -66,7 +66,8 @@ def test_precomposition_type(par): _ = l1.precomposition(a=1, b=np.ones(5)) # should be float _ = l1.precomposition( - a=1.0, b=[1, 2, 3] # should be float + a=1.0, + b=[1, 2, 3], # should be float ) # should be float, np.ndarray or cp.ndarray diff --git a/pytests/test_dykstra.py b/pytests/test_dykstra.py index 1b07ee0..efe549a 100644 --- a/pytests/test_dykstra.py +++ b/pytests/test_dykstra.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict +from collections.abc import Callable +from typing import Any import numpy as np import pytest @@ -28,7 +29,7 @@ @pytest.mark.parametrize("par", [(par1proj), (par2proj)]) -def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: +def test_GenericIntersectionProx(par: dict[str, Any]) -> None: """GenericIntersectionProx and proximal/dual proximal of related indicator""" rng = np.random.default_rng(10) @@ -72,11 +73,8 @@ def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: [box, eucl, half_space], ] for proj in projections: - # different torelance for float32 and float64 - tol = ( - float(np.finfo(par["dtype"]).resolution) * 10.0 - ) # pylint: disable=no-member + tol = float(np.finfo(par["dtype"]).resolution) * 10.0 # pylint: disable=no-member d = GenericIntersectionProx(proj, tol=tol, niter=1000) @@ -103,7 +101,7 @@ def test_GenericIntersectionProx(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l1_l1(par: Dict[str, Any]) -> None: +def test_Sum_l1_l1(par: dict[str, Any]) -> None: """Check Sum for L1 + L1""" atol = 1e-6 @@ -137,7 +135,7 @@ def test_Sum_l1_l1(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l2_l2(par: Dict[str, Any]) -> None: +def test_Sum_l2_l2(par: dict[str, Any]) -> None: """Check Sum for L2 + L2""" atol = 1e-6 @@ -171,7 +169,7 @@ def test_Sum_l2_l2(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_l21_l1(par: Dict[str, Any]) -> None: +def test_Sum_l21_l1(par: dict[str, Any]) -> None: """Check Sum for L21 + L1""" atol = 1e-5 @@ -204,7 +202,7 @@ def test_Sum_l21_l1(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_f1f2f3f4(par: Dict[str, Any]) -> None: +def test_Sum_f1f2f3f4(par: dict[str, Any]) -> None: """Check Sum for f1+f2+f3+f4""" tau = 1.0 @@ -231,7 +229,7 @@ def test_Sum_f1f2f3f4(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1prox), (par2prox)]) -def test_Sum_numeric_projection(par: Dict[str, Any]) -> None: +def test_Sum_numeric_projection(par: dict[str, Any]) -> None: """Check Sum for numeric prox + indicator function""" rng = np.random.default_rng(10) diff --git a/pytests/test_solver.py b/pytests/test_solver.py index 2c532eb..b0649ef 100644 --- a/pytests/test_solver.py +++ b/pytests/test_solver.py @@ -1,4 +1,4 @@ -from typing import Dict, Any +from typing import Any import numpy as np import pytest @@ -9,12 +9,12 @@ ADMM, ADMML2, HQS, + PPXA, + ConsensusADMM, DouglasRachfordSplitting, GeneralizedProximalGradient, LinearizedADMM, ProximalGradient, - PPXA, - ConsensusADMM, ) from pyproximal.proximal import L1, L2 @@ -231,7 +231,7 @@ def test_ADMM_DRS(par): @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: +def test_PPXA_with_ADMM(par: dict[str, Any]) -> None: """Check equivalency of PPXA and ADMM when using a single regularization term """ @@ -257,7 +257,8 @@ def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, + l2, + l1, x0=np.zeros(m), tau=tau, niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 @@ -274,9 +275,8 @@ def test_PPXA_with_ADMM(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: - """Check equivalency of PPXA and GeneralizedProximalGradient - """ +def test_PPXA_with_GPG(par: dict[str, Any]) -> None: + """Check equivalency of PPXA and GeneralizedProximalGradient""" np.random.seed(0) n, m = par["n"], par["m"] @@ -318,14 +318,14 @@ def test_PPXA_with_GPG(par: Dict[str, Any]) -> None: [l2_1, l2_2, l1_1, l1_2], x0=np.zeros(m), tau=np.random.uniform(3 * tau, 5 * tau), - show=True + show=True, ) assert_array_almost_equal(xppxa, xgpg, decimal=2) @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: +def test_ConsensusADMM_with_ADMM(par: dict[str, Any]) -> None: """Check equivalency of ConsensusADMM and ADMM when two proximable functions """ @@ -351,11 +351,12 @@ def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: tau = 0.5 / L xadmm, _ = ADMM( - l2, l1, + l2, + l1, x0=np.zeros(m), tau=tau, niter=2000, # niter=1500 makes this test fail for seeds 0 to 499 - show=True + show=True, ) xcadmm = ConsensusADMM( [l2, l1], @@ -368,7 +369,7 @@ def test_ConsensusADMM_with_ADMM(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: +def test_ConsensusADMM_with_ADMM_for_Lasso(par: dict[str, Any]) -> None: """Check equivalency of ConsensusADMM and ADMM when more than two proximable functions for lasso """ @@ -394,7 +395,7 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: # 1/2||R1||_2^2, 1/2||R2||_2^2, 1/2||R3||_2^2 l2_ops = [ L2(Op=MatrixMult(Ri), b=yi, niter=50, warm=False) - for Ri, yi in zip(R_list, y_list) + for Ri, yi in zip(R_list, y_list, strict=True) ] # 1/2 || [R1; R2; R3] ||_2^2 @@ -432,9 +433,8 @@ def test_ConsensusADMM_with_ADMM_for_Lasso(par: Dict[str, Any]) -> None: @pytest.mark.parametrize("par", [(par1), (par2)]) -def test_ConsensusADMM_with_GPG(par: Dict[str, Any]) -> None: - """Check equivalency of ConsensusADMM and GeneralizedProximalGradient - """ +def test_ConsensusADMM_with_GPG(par: dict[str, Any]) -> None: + """Check equivalency of ConsensusADMM and GeneralizedProximalGradient""" np.random.seed(0) diff --git a/tutorials/groupsparsity.py b/tutorials/groupsparsity.py index c91a138..944ae92 100644 --- a/tutorials/groupsparsity.py +++ b/tutorials/groupsparsity.py @@ -76,8 +76,9 @@ np.random.seed(10) perc_subsampling = (0.1, 0.6) -Nsub1, Nsub2 = int(np.round(N * perc_subsampling[0])), int( - np.round(N * perc_subsampling[1]) +Nsub1, Nsub2 = ( + int(np.round(N * perc_subsampling[0])), + int(np.round(N * perc_subsampling[1])), ) iava1 = np.sort(np.random.permutation(np.arange(N))[:Nsub1]) iava2 = np.sort(np.random.permutation(np.arange(N))[:Nsub2]) diff --git a/tutorials/hankel_matrix_estimation.py b/tutorials/hankel_matrix_estimation.py index 452ee01..d2f02c6 100644 --- a/tutorials/hankel_matrix_estimation.py +++ b/tutorials/hankel_matrix_estimation.py @@ -48,7 +48,7 @@ t = np.linspace(-1, 1, 200) f_gt = np.zeros_like(t) -for i in range(n_signal): +for _ in range(n_signal): d = np.random.uniform(-1, 1) phi = np.random.uniform(-40 * np.pi, 40 * np.pi) t0 = np.random.uniform(-1, 1) diff --git a/tutorials/nonlinearconstrained.py b/tutorials/nonlinearconstrained.py index afee690..c2689bc 100644 --- a/tutorials/nonlinearconstrained.py +++ b/tutorials/nonlinearconstrained.py @@ -101,7 +101,7 @@ def grad(self, x): def optimize(self): self.solhist = [] sol = self.x0.copy() - for iiter in range(self.niter): + for _ in range(self.niter): x1, x2 = sol dfx1, dfx2 = self._gradprox(sol, self.tau) x1 -= self.alpha * dfx1 @@ -123,7 +123,7 @@ def optimize(self): steps = [ (0, 0), ] -for iiter in range(niters): +for _ in range(niters): x, y = steps[-1] dfx, dfy = rosenbrock_grad(x, y) x -= alpha * dfx diff --git a/tutorials/nrsfm.py b/tutorials/nrsfm.py index f5e53ce..5b70684 100644 --- a/tutorials/nrsfm.py +++ b/tutorials/nrsfm.py @@ -48,8 +48,16 @@ import numpy as np import scipy as sp +from pyproximal import Nuclear, ProxOperator +from pyproximal.optimization.primal import ADMM +from pyproximal.ProxOperator import _check_tau + plt.close("all") np.random.seed(0) + +############################################################################### +# Let's start by loading the data. + data = np.load("../testdata/mocap.npz", allow_pickle=True) X_gt = data["X_gt"] markers = data["markers"].item() @@ -62,7 +70,7 @@ def plot_first_3d_pose(ax, X, color="b", marker="o", linecolor="k"): ax.scatter(X[0, :], X[1, :], X[2, :], color, marker=marker) - for j, ind in enumerate(markers.values()): + for _, ind in enumerate(markers.values()): ax.plot(X[0, ind], X[1, ind], X[2, ind], "-", color=linecolor) ax.set_box_aspect(np.ptp(X[:3, :], axis=1)) ax.view_init(20, 25) @@ -209,9 +217,6 @@ def unstack(Xs: np.ndarray): # are simply those of the (stacked) nuclear norm and the Frobenius norm. # We implement these next: -from pyproximal import Nuclear, ProxOperator -from pyproximal.ProxOperator import _check_tau - class BlockDiagFrobenius(ProxOperator): r"""Proximal operator for 1/2 * ||RX - M||_F^2 where R is block-diagonal. @@ -262,8 +267,6 @@ def prox(self, x, tau): ############################################################################### # Now we are ready to solve the problem using ADMM. -from pyproximal.optimization.primal import ADMM - mu = 1 R = data["R"] Rblk = sp.linalg.block_diag(*R) @@ -289,8 +292,8 @@ def prox(self, x, tau): # Furthermore, we compute some statistics on the reconstruction performance. You # can vary the regulation strength :math:`\mu` to see if you can achieve better # performance yourself! -print(f'Datafit: {np.linalg.norm(Rblk @ X_rec - M, "fro")}') -print(f'Distance to GT: {np.linalg.norm(X_rec - X_gt, "fro")}') +print(f"Datafit: {np.linalg.norm(Rblk @ X_rec - M, 'fro')}") +print(f"Distance to GT: {np.linalg.norm(X_rec - X_gt, 'fro')}") ############################################################################### # One issue with the nuclear norm is that you have the hyperparameter