Fix audit findings A-H: ctypes boundary, DLR basis functions, and test-suite self-sabotage - #85
Merged
Merged
Conversation
…t suite Source fixes: * DLR `.u`/`.uhat` returned the *IR* basis functions, so `g_dlr @ dlr.u(tau)` silently produced wrong values. The C API accepts the DLR handle in `spir_basis_get_u`/`spir_basis_get_uhat`, so these properties now build the DLR function sets from the DLR handle (A). * Every remaining boundary crossing normalizes its input with `np.ascontiguousarray(x, dtype=<explicit>)` and takes the pointer from the converted object: sampling evaluate/fit (both classes, real and complex paths), DLR `from_IR`/`to_IR` real paths, DLR poles, and the evaluation points in `poly.py`. A float32 or non-contiguous array was previously reinterpreted through a `c_double` pointer, reading 8 bytes per 4-byte element (B, C). * `FunctionSet.__call__`/`FunctionSetFT.__call__` had squeeze bugs; the returned shape is now `(n_funcs,) + np.shape(x)` with the function axis dropped only for a single function and the point axes only for scalar `x` (D). * `FiniteTempBasis.rescale` raised `NameError` on an undefined `new_lambda`; it now holds `lambda_ == beta * wmax` fixed, reuses the SVE, and validates `new_beta > 0` (E). * `TauConst` rejects fermionic statistics: its Fourier transform is `sqrt(beta) * (n == 0)` and fermionic reduced frequencies are odd, so the augmentation column vanished identically and the basis was rank-deficient (F). * Reduced Matsubara indices are validated for integrality and parity instead of being truncated by `int()`/`astype(np.int64)`, and the `i % n` index wrap-around in both `__getitem__` implementations is replaced by explicit negative-index resolution plus `IndexError` (G). Also fixed in passing: axis normalization (negative axes resolved before they reach C, out-of-range axes raise `IndexError`), non-finite input rejected before it reaches a factorization, null handle checks, status compared against `COMPUTATION_SUCCESS`, the bare `except: pass` in both `release()` methods, a stray `print` in `overlap`, the `AttributeError`s from `basis._slice_to_size`, `AugmentedTauFunction.xmax` returning `xmin`, the `*daug` argument mismatch in `AugmentedTauFunction.deriv`, and `__all__` naming nonexistent `TauPoles`/`MatsubaraPoles` (which made `from sparse_ir import *` raise). Test suite (H): * The 8 skips in `test_sve_advanced.py` were except-skip blocks hiding a test bug: `sve_result_new`/`basis_new` were called with the Python kernel wrapper instead of `kernel._ptr` (and with the wrong arity). Fixed and un-skipped; the suite now has no skips. * Removed the try/except-skip wrappers in `test_advanced_features.py`, `test_sampling_advanced.py` and `tests/conftest.py`. * Deleted the dead triple-quoted test blocks and the shadowed duplicate `test_broadcast_uv` in `test_poly.py` (tracked in #83), and added the missing `assert` on line 22. * New `tests/test_ffi_boundary.py`: dtype matrix (float32/float64/complex64/complex128 plus integer) and non-contiguous input for every boundary function touched, axis coverage, concrete exception types with `match=`, nonzero-norm assertions, DLR function reconstruction against the IR result, shape tests for the squeeze semantics, `rescale` round-trip, and an `__all__` smoke test. Test counts: 101 passed / 8 skipped -> 181 passed / 0 skipped. Version 2.1.3 -> 2.1.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Re-verified each of the eight audit findings against
mainline(v2.1.3) beforefixing it, per
AGENTS.md->spm-agent-rules(rules/ffi-boundary.md,rules/python.md,rules/testing.md).Disposition
.u/.uhatreturned the IR basis functions, not the DLR onesspir_basis_get_u/spir_basis_get_uhataccept the DLRspir_basis*handle, so noNotImplementedErrorand no C-API issue is needed. Both properties now build the DLR function sets lazily from the DLR handle, andg_dlr @ dlr.u(tau)reproducesgl @ basis.u(tau)(asserted in the new tests)..ctypes.data_assite in the package (sampling.py,dlr.py,poly.py;basis.py,sve.py,kernel.py,augment.pyhave none). Each input goes through a new_util.as_boundary_real/as_boundary_complex/as_boundary_matsubarahelper (np.ascontiguousarray(x, dtype=<explicit>)) with the pointer taken from the converted object.__init__pointer provenanceFunctionSet.__call__squeeze bugs(n_funcs,) + np.shape(x), the leading axis is dropped only for a single function, the point axes only for scalarx.FunctionSetFT.__call__follows the same rule. This is what the previously-skippedtest_zero_frequencyexpected (basis.v(np.array([0.0])).shape == (size, 1)).rescaleraisedNameError(undefinednew_lambda)lambda_ == beta * wmaxfixed, computesnew_wmax = lambda_ / new_beta, reuses the existing kernel and SVE (both depend only onlambda_andeps), and rejects non-positivenew_betawithValueError.TauConstgives a rank-deficient basisValueErrornaming the reason:hat(n) == sqrt(beta) * (n == 0)and fermionic reduced frequencies are odd, so the augmentation column is identically zero. The two fermionic parametrizations intest_augment.pywere converted into apytest.raisestest.i % nindex wrap-aroundcheck_reduced_matsubararejects complex input (TypeError), non-finite and non-integral values (ValueErrornaming the offending value, e.g.1.9), and wrong parity (naming "odd"/"even").FunctionSetFT.__call__validates before converting toint64. Both__getitem__implementations now use_util.resolve_function_indices: negative indices resolve with Python semantics, out-of-range raisesIndexError, non-integral raisesValueError.Additional tracking issues opened for things deliberately not forced in this PR:
#82 (should
TauLinearalso be bosonic-only?), #83 (restore theSVEResult.part()-based coverage deleted fromtest_poly.py), #84 (basistruncation
basis[:n]is still unimplemented in the C API).Finding H in detail
tests/test_sve_advanced.pywere not legitimatepreconditions. They were
except Exception: pytest.skip(...)blocks hiding abug in the tests themselves:
sve_result_new/basis_newwere called with thePython
LogisticKernel/RegularizedBoseKernelwrapper instead ofkernel._ptr(TypeError: expected LP_LP__spir_kernel instance instead of LogisticKernel), andbasis_newwas called with 5 of its 7 arguments. Fixedand un-skipped; all 8 now run and pass.
try/except -> pytest.skipwrappers intests/test_advanced_features.py(10 of them),tests/test_sampling_advanced.py,and the
except Exception: print(...)in thetest_basesfixture intests/conftest.py, which handed tests a silently incomplete dict.tests/test_poly.py: deleted the two dead triple-quoted blocks (8 stringifiedtests depending on the unavailable
SVEResult.part(), tracked in Restore SVEResult.part()-based poly coverage once the C API exposes it #83), deletedthe shadowed duplicate
test_broadcast_uvthat referenced undefinedbeta/atol, and added the missingasserton the no-op comparison at line 22.Regression tests
New
tests/test_ffi_boundary.py(68 tests), plus the converted augmentation tests:float32/float64/complex64/complex128and integerinput for
TauSampling.evaluate/fit,MatsubaraSampling.evaluate/fit,DLR.from_IR/to_IR, andFunctionSet.__call__; each compared against thefloat64reference at the input dtype's own precision (relative to the norm,since the coefficient vectors span many decades).
match=— non-integral Matsubara index(
1.9), wrong parity, complex indices, complex evaluation points, NaN input,out-of-range axis, out-of-range and non-integral function index, empty /
multi-dimensional sampling points, non-positive
rescalebeta, fermionicTauConst.scalar and tensorial
x, for bothuanduhat).g_dlr @ dlr.u(tau)andg_dlr @ dlr.uhat(n)checked against the IR result.
0,1,-1,-2) with round-trip checks.rescaleround-trip (lambda_preserved, spectrum preserved, basis usable).__all__-derived smoke test and afrom sparse_ir import *test.Test counts: 101 passed / 8 skipped -> 181 passed / 0 skipped.
Other defects fixed in passing
Negative axes are resolved before reaching C (which takes a non-negative target
dimension) and out-of-range axes raise
IndexError; non-finite input is rejectedbefore it reaches a factorization; returned handles are null-checked and statuses
compared against
COMPUTATION_SUCCESS; the bareexcept: passin bothrelease()methods and a strayprint(type(...))inPiecewiseLegendrePoly.overlapare gone;
basis._slice_to_size(anAttributeErrorat both call sites inaugment.py) is replaced by a local range-checking_slice_to_size;AugmentedTauFunction.xmaxreturnedxmin;AugmentedTauFunction.derivpassed*daugwhere the constructor takes a list; and__all__no longer names thenonexistent
TauPoles/MatsubaraPoles, which madefrom sparse_ir import *raise
AttributeError.Behavior changes worth reviewer attention
TauSamplingon aMatsubaraConst-augmented (vertex) basis now raisesValueErrorexplaining that the augmentation is undefined in imaginary time,instead of handing NaNs to the C factorization. No existing test covered this
path; a new one does.
TauConst(beta, 'F')now raises instead of building a rank-deficient basis.truncated or accepted.
No
REPOSITORY_RULES.mdoverride applies to this change beyond what it alreadydocuments (status codes from
pylibsparseir.constants, no localCDLL, and theversion-consistency check).
Version
pyproject.toml2.1.3 -> 2.1.4;python check_libsparseir_version_consistency.pypasses (
pylibsparseir>=0.8.3,<0.10.0in bothpyproject.tomland.conda/meta.yaml).🤖 Generated with Claude Code