From f40969d296a21719d3c7aa4532e7458c390eab62 Mon Sep 17 00:00:00 2001 From: Lucas Teixeira <132934930+texlucas@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:14:16 +0200 Subject: [PATCH 1/4] Run the documentation pipeline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70c317f..d03070b 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,4 @@ Requirements - pyNFFT3 0.2.2 or greater - NumPy 2.0.0 or greater - SciPy 1.16.0 or greater + From b85c72994c32c2704d7c5ee48728823ee3be5b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Schr=C3=B6ter?= Date: Fri, 25 Sep 2026 09:04:51 +0000 Subject: [PATCH 2/4] fix: unraised validation exceptions and dead DeferredLinearOperator guard - DeferredLinearOperator: __init__ set self._matvec/self._rmatvec as instance attributes, permanently shadowing the class methods that guard against using an unconfigured operator. Confirmed: calling .matvec() on a fresh, unconfigured DeferredLinearOperator() silently fell through instead of raising NotImplementedError. Removed the shadowing attributes so the class methods actually run. That, in turn, exposed a second bug: the guard probed "is this configured" by calling the real mfunc/rmfunc with a bare 0 and comparing the result to the string "not implemented yet" - which crashes or raises "truth value of an array is ambiguous" for any real, properly configured transform (confirmed via the existing nfft_U.py test). Replaced the string-sentinel probe with a simple None check on _matvec_func/ _rmatvec_func, set once at construction/`.set()` time - no probing call needed. Also removed dead code in the old _matvec/_rmatvec bodies (print(self.LO @ x) referencing a nonexistent attribute, and self @ x / self.H @ x, which would have recursed infinitely had they ever been reached). - Added missing `raise` in front of several validation ValueError/RuntimeError constructions that were built but never raised (GroupedTransform.py x7, GroupedTransforms.py, CWWTtools.py). Most guard the "mixed"/NFMT system, which the code already flags as untested/unfinished; CWWTtools.indextoN's check is on the supported wavelet path. NFMTtools.py needed special handling: it's imported unconditionally at package import time, so a top-level `raise` would break importing pyGroupedTransforms entirely - replaced with a module-level __getattr__ that raises lazily only when the (unimplemented) module is actually used. - Fixed a typo (s.bdanwidths -> s1.bandwidths) and a use-before-assignment of `s` in GroupedTransform.get_matrix()'s "mixed" branch. Verified: full existing test suite (tests/run_tests.py) still passes. Co-Authored-By: Claude Sonnet 5 --- src/pyGroupedTransforms/CWWTtools.py | 2 +- .../DeferredLinearOperator.py | 17 +++++++---------- src/pyGroupedTransforms/GroupedTransform.py | 18 +++++++++--------- src/pyGroupedTransforms/GroupedTransforms.py | 2 +- src/pyGroupedTransforms/NFMTtools.py | 7 ++++++- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/pyGroupedTransforms/CWWTtools.py b/src/pyGroupedTransforms/CWWTtools.py index ffbebe7..9b88a39 100644 --- a/src/pyGroupedTransforms/CWWTtools.py +++ b/src/pyGroupedTransforms/CWWTtools.py @@ -119,7 +119,7 @@ def indextoN(j, k): # indextoN(j::Array{Int}, k::Array{Int})::Array{Int} s = k.shape[1] if d != d2: - ValueError("j and k have to have same length, k has to be column vector.") + raise ValueError("j and k have to have same length, k has to be column vector.") out = np.zeros(s) diff --git a/src/pyGroupedTransforms/DeferredLinearOperator.py b/src/pyGroupedTransforms/DeferredLinearOperator.py index 3d46c82..fe651d4 100644 --- a/src/pyGroupedTransforms/DeferredLinearOperator.py +++ b/src/pyGroupedTransforms/DeferredLinearOperator.py @@ -7,8 +7,8 @@ def __init__( self, dtype="complex", shape=None, - mfunc=lambda x: "not yet initialized", - rmfunc=lambda x: "not yet initialized", + mfunc=None, + rmfunc=None, ): self.sh = False @@ -19,8 +19,6 @@ def __init__( self._matvec_func = mfunc self._rmatvec_func = rmfunc - self._matvec = lambda x: self._matvec_func(x) - self._rmatvec = lambda x: self._rmatvec_func(x) super().__init__(dtype=dtype, shape=shape) def set(self, dtype=None, shape=None, mfunc=None, rmfunc=None): @@ -38,16 +36,15 @@ def set(self, dtype=None, shape=None, mfunc=None, rmfunc=None): self._rmatvec_func = rmfunc def _matvec(self, x): - if self._matvec_func(0) == "not implemented yet" or self.sh == False: + if self._matvec_func is None or self.sh == False: raise NotImplementedError("LinearOperator is not yet initialized.") - print(self.LO @ x) - return self @ x + return self._matvec_func(x) def _rmatvec(self, x): - if self._matvec_func(0) == "not implemented yet" or self.sh == False: + if self._matvec_func is None or self.sh == False: raise NotImplementedError("LinearOperator is not yet initialized.") - if self._rmatvec_func(0) == "not implemented yet": + if self._rmatvec_func is None: raise NotImplementedError("rmatvec is not yet implemented") - return self.H @ x + return self._rmatvec_func(x) diff --git a/src/pyGroupedTransforms/GroupedTransform.py b/src/pyGroupedTransforms/GroupedTransform.py index 4f4117b..c083a84 100644 --- a/src/pyGroupedTransforms/GroupedTransform.py +++ b/src/pyGroupedTransforms/GroupedTransform.py @@ -53,11 +53,11 @@ def get_setting( if systems[system] == NFMTtools: if len(basis_vect) == 0: - ValueError( + raise ValueError( "please call get_setting with basis_vect for a NFMT transform." ) if len(basis_vect) < d: - ValueError("basis_vect must have an entry for every dimension.") + raise ValueError("basis_vect must have an entry for every dimension.") return [ Setting( u=U[idx], @@ -93,11 +93,11 @@ def get_setting( if systems[system] == NFMTtools: if len(basis_vect) == 0: - ValueError( + raise ValueError( "please call get_setting with basis_vect for a NFMT transform." ) if len(basis_vect) < max(max(u) for u in U): - ValueError("basis_vect must have an entry for every dimension.") + raise ValueError("basis_vect must have an entry for every dimension.") return [ Setting( u=u, @@ -132,11 +132,11 @@ def get_setting( if systems[system] == NFMTtools: if len(basis_vect) == 0: - ValueError( + raise ValueError( "please call get_setting with basis_vect for a NFMT transform." ) if len(basis_vect) < max(max(u) for u in U): - ValueError("basis_vect must have an entry for every dimension.") + raise ValueError("basis_vect must have an entry for every dimension.") return [ Setting( u=U[idx], @@ -197,11 +197,11 @@ def __init__( if system == "mixed": if len(basis_vect) == 0: - ValueError( + raise ValueError( "please call GroupedTransform with basis_vect for a NFMT transform." ) if len(basis_vect) != X.shape[1]: - ValueError("basis_vect must have an entry for every dimension.") + raise ValueError("basis_vect must have an entry for every dimension.") if system in {"exp", "chui1", "chui2", "chui3", "chui4"}: if np.min(X) < -0.5 or np.max(X) >= 0.5: @@ -433,7 +433,7 @@ def get_matrix(self): u1 = (0,) else: u1 = s1.u - F_direct = s1.mode.get_matrix(s.bdanwidths, self.X[:, u1].T, bases=s.bases) + F_direct = s1.mode.get_matrix(s1.bandwidths, self.X[:, u1].T, bases=s1.bases) for idx, s in enumerate(self.settings): if idx == 0: continue diff --git a/src/pyGroupedTransforms/GroupedTransforms.py b/src/pyGroupedTransforms/GroupedTransforms.py index 9818a45..bee060d 100644 --- a/src/pyGroupedTransforms/GroupedTransforms.py +++ b/src/pyGroupedTransforms/GroupedTransforms.py @@ -47,7 +47,7 @@ def get_IndexSet(settings, d): elif s.mode == NFMTtools: index_set_u = s.mode.nfmt_index_set_without_zeros(s.bandwidths, s.bases) else: - ValueError(f"Unknown mode: {s.mode}") + raise ValueError(f"Unknown mode: {s.mode}") if len(s.u) == 1: index_set[s.u[0], idx : idx + nf_u] = index_set_u diff --git a/src/pyGroupedTransforms/NFMTtools.py b/src/pyGroupedTransforms/NFMTtools.py index ea56cfb..ec4bb33 100644 --- a/src/pyGroupedTransforms/NFMTtools.py +++ b/src/pyGroupedTransforms/NFMTtools.py @@ -1 +1,6 @@ -RuntimeError("NFMTtools is not implemented yet!") +def __getattr__(name): + # NFMTtools is imported unconditionally at package import time (see + # GroupedCoefficients.py), so raising here directly would break importing + # pyGroupedTransforms entirely. Raise lazily instead, only once someone + # actually tries to use the (currently unimplemented) NFMT/"mixed" system. + raise RuntimeError("NFMTtools is not implemented yet!") From 262884f4568d9d47e8f97b18377709b741b6193c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Schr=C3=B6ter?= Date: Fri, 25 Sep 2026 09:24:20 +0000 Subject: [PATCH 3/4] fix: lower numpy/scipy floors to restore genuine Python 3.9 support requires-python is ">=3.9", but scipy>=1.16.0 has no wheel for Python 3.9 or 3.10 (verified via pip download) - the declared floor was already unsatisfiable in practice, and CI never tested below 3.11 to catch it. Nothing in this package's own code needs anything past very old, stable scipy/numpy APIs. Lowered to numpy>=1.26.4 and scipy>=1.11.4 (both have wheels back to Python 3.9) and verified the full test suite against exactly those versions. Co-Authored-By: Claude Sonnet 5 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2596536..8cb3c21 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ readme = "README.md" requires-python = ">=3.9" dependencies = [ "pyNFFT3>=0.2.2", - "numpy>=2.0.0", - "scipy>=1.16.0", + "numpy>=1.26.4", + "scipy>=1.11.4", ] [project.urls] From 1c76641153f8d39eba5c6c365704f982c0efbcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Schr=C3=B6ter?= Date: Fri, 23 Oct 2026 09:33:56 +0000 Subject: [PATCH 4/4] fix: pin older Ubuntu runners for Python 3.9/3.10 in CI "Python 3.9 - ubuntu-latest" was failing at the "Set up Python" step itself (before any dependency installation), with: Error: The version '3.9' with architecture 'x64' was not found for Ubuntu 26.04. ubuntu-latest now resolves to Ubuntu 26.04, and actions/setup-python has no prebuilt Python 3.9 or 3.10 binary for it (per the upstream actions/python-versions manifest, those only go up to 22.04/24.04 - Python 3.9 itself has been EOL since October 2025). Confirmed this is new/infra-side and not caused by anything in this PR: the same job passed fine on `develop` in February 2026. 3.11/3.12 still resolve fine on ubuntu-latest (apparently via a fallback), so left those alone. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71a62a7..451eeb9 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,28 @@ jobs: test: strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] # Specify the Python versions you want to test against - os: [ubuntu-latest, windows-latest] + # ubuntu-latest now resolves to a newer Ubuntu release than + # actions/setup-python has prebuilt Python 3.9/3.10 for (Python itself + # dropped support for the kernel/libc these need) - pin an older + # Ubuntu LTS for those so "Set up Python" doesn't fail outright. See + # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json + include: + - python-version: '3.9' + os: ubuntu-24.04 + - python-version: '3.9' + os: windows-latest + - python-version: '3.10' + os: ubuntu-24.04 + - python-version: '3.10' + os: windows-latest + - python-version: '3.11' + os: ubuntu-latest + - python-version: '3.11' + os: windows-latest + - python-version: '3.12' + os: ubuntu-latest + - python-version: '3.12' + os: windows-latest name: Python ${{ matrix.python-version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: