Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/codacy-coverage-reporter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: PyProximal-coverage
on:
pull_request:
push:
branches: [main]
branches: [dev]

jobs:
build:
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/
17 changes: 10 additions & 7 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import sys
import os
import datetime
import os
import sys

from sphinx_gallery.sorting import ExampleTitleSortKey

Expand Down Expand Up @@ -93,17 +92,17 @@
# 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__
if len(version.split("+")) > 1 or version == "unknown":
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"
Expand Down Expand Up @@ -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",
Expand Down
24 changes: 22 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,30 +82,50 @@ 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"
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.*"]
Expand Down
38 changes: 21 additions & 17 deletions pyproximal/ProxOperator.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down
7 changes: 4 additions & 3 deletions pyproximal/optimization/bregman.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down
36 changes: 18 additions & 18 deletions pyproximal/optimization/palm.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -27,7 +27,7 @@ def _backtracking(

def ftilde(
x: NDArray,
y: List[NDArray],
y: list[NDArray],
f: BilinearOperator,
g: NDArray,
tau: float,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions pyproximal/optimization/pnp.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down
Loading