Promote weak form SINDy from a feature library to a sibling SINDy class.#678
Promote weak form SINDy from a feature library to a sibling SINDy class.#678Jacob-Stevens-Haas merged 47 commits intomainfrom
SINDy class.#678Conversation
Long term, some revision of TensoredLibrary is in order, as the inputs_per_library is GeneralizedLibrary bleeding in. This is a consequence of the fitted/unfitted mutability of scikitlearn estimators. Along the way, fix a potential bug in GeneralizedLibrary and extract PDE feature naming function.
Enforces input shape commonality across all models
This ensures that coefficients and feature names will line up. It does require reassigning the sorted library to self in order to make predict work.
In addition to having a differentiation method, this change adds simulate() and score() to WeakSINDy.
Fixed in previous commit, but einsum was seeing the ellipses on diferent arguments as different (and not shared) axes. Two cases: - Ellipsis in two subscripts handled as shared axes - Conflicting ellipsis axis names across operands raise a ValueError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add two tests exercising _eval_semiterm directly: - test_eval_semiterm_output_shape: verifies the result has shape (n_subdomains, n_coord) for a trivial no-derivative, no-feature term. - test_eval_semiterm_phi_prime_vanishes: verifies that integrating a constant against phi' gives zero, since the bump test function vanishes at both boundaries (an analytically exact result). Also keep more types as AxesArray and handle spatial derivatives inside semiterms more correctly based on user arguments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This allows discovery of steady-state differential equations, e.g. Poisson's equation. This requires some functions to allow eliminating the time derivative, and perhaps that's an uncomfortable amount of spooky action. Would love a more elegant way, but the things I do for backwards compatibility. For such systems, convert_u_dot_integral does *not* move derivative onto test function, assuming they are handled by the caller
This allows discovery of steady-state differential equations, e.g. Poisson's equation. This requires some functions to allow eliminating the time derivative, and perhaps that's an uncomfortable amount of spooky action. Would love a more elegant way, but the things I do for backwards compatibility. For such systems, convert_u_dot_integral does *not* move derivative onto test function, assuming they are handled by the caller
Add `ParallelImplicitSINDy` in `pysindy/_sindypi.py`, to replace the removed functionality from SINDyPI optimizer and implicit=True in PDELibrary. This does not work with WeakSINDy. In the future, weak SINDy PI can be restored with (a) a new PDEFIND class that handles spatial_grid the same way as WeakSINDy, then (b) more symbolic handling of features. That would allow WeakSINDy and PDEFIND the opportunity to understand derivative features in the RHS better and provide and API for ParallelImplicitSINDy to call SINDy subclasses to transform the derivative and non-derivative features. Deprecate the `SINDyPI` optimizer (which was a CVXPY-based SR3 variant) with a `DeprecationWarning` pointing users to the new class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add docstrings to WeakSINDy.fit, WeakSINDy.simulate, and _make_domains. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f6e0948 to
0d9700f
Compare
So it looks like some early work I did to improve the clarity of how different axes are differentiated in I think it's an acceptable slowdown, given that this is truly the beginning of our benchmarking experience in pysindy and previously these regressions would've gone unnoticed. Also, the PR speeds up |
This is failing tests in 3.14, even when the same version of numpy passes on 3.13
0d9700f to
aa12f01
Compare
|
@llfung FYSA this touches BINDy (in pretty trivial ways):
More interestingly, it enables the kind of numerical-accuracy benchmarks you originally wrote for BINDy as a lorenz test file. If you're interested adding something like it back in, see this file for the skeleton - you'd just need to make a benchmark subclass that instantiates the |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Pull request overview
Refactors weak-form SINDy into a first-class WeakSINDy model (rather than a special-cased feature library), and splits SINDy-PI into a dedicated ParallelImplicitSINDy class, while also standardizing array/axes conventions across core APIs, differentiation, and tests.
Changes:
- Added
WeakSINDy(pysindy/_weak.py) with supporting weak-form integration utilities and a comprehensive new test suite. - Added
ParallelImplicitSINDy(pysindy/_sindypi.py) and deprecated the legacySINDyPIoptimizer path. - Updated core input validation + differentiation call patterns to prefer differentiators as callables (vs
._differentiate) and aligned CI/tooling to newer Python/pre-commit versions.
Reviewed changes
Copilot reviewed 38 out of 40 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
test/utils/test_axes.py |
Adds einsum ellipsis tests for AxesArray behavior. |
test/test_weak.py |
New unit tests covering weak-form utilities and WeakSINDy. |
test/test_sindyc.py |
Updates expected exceptions/messages for new input validation rules. |
test/test_pysindy.py |
Updates tests for revised trajectory validation + adds ParallelImplicitSINDy tests. |
test/test_optimizers/test_optimizers.py |
Adjusts optimizer coef shape expectations to standardized conventions. |
test/test_feature_library.py |
Updates library tests to match PDELibrary API changes and removes some fixtures. |
test/test_differentiation.py |
Migrates tests from ._differentiate to differentiator call syntax and updates shapes. |
test/test_bindy.py |
Aligns BINDy tests with new input validation exception types. |
test/conftest.py |
Reshapes fixtures to match updated array conventions and removes data_1d_bad_shape. |
pysindy/utils/bindy.py |
Switches to callable differentiator interface. |
pysindy/utils/base.py |
Introduces validate_no_reshape for stricter validation + updates control validation. |
pysindy/utils/_axes.py |
Tightens einsum ellipsis handling and erroring on conflicting axis names. |
pysindy/optimizers/sindy_pi.py |
Deprecates SINDyPI optimizer in favor of ParallelImplicitSINDy. |
pysindy/optimizers/evidence_greedy.py |
Minor cleanup/formatting. |
pysindy/optimizers/base.py |
Makes optimizer interface more explicit (abstract fit). |
pysindy/feature_library/weak_pde_library.py |
Deprecation cleanup and internal differentiation call updates. |
pysindy/feature_library/pde_library.py |
Refactors PDELibrary toward derivative-only behavior + new feature name helper. |
pysindy/feature_library/generalized_library.py |
Adjusts get_feature_names input feature handling. |
pysindy/feature_library/base.py |
Removes legacy correct_shape; adds EmptyLibrary; tweaks feature-name handling. |
pysindy/differentiation/sindy_derivative.py |
Adjusts differentiation to new time/axes conventions. |
pysindy/differentiation/finite_difference.py |
Refactors coefficient building around AxesArray time representation. |
pysindy/differentiation/base.py |
Adds BaseDifferentiation.__call__ normalization + AxesArray wrapping. |
pysindy/_weak.py |
New weak-form implementation + WeakSINDy model. |
pysindy/_typing.py |
Updates type aliases and introduces TrajectoryType helper. |
pysindy/_sindypi.py |
New ParallelImplicitSINDy implementation. |
pysindy/_core.py |
Reworks multiple-trajectory validation/adaptation and shape standardization. |
pysindy/__init__.py |
Exposes WeakSINDy and ParallelImplicitSINDy at package top-level. |
pyproject.toml |
Bumps minimum Python to 3.11 and adds dev dependency sindy_exp. |
examples/tutorial_1/example.py |
Minor formatting tweak. |
examples/1_feature_overview/example.py |
Updates examples to use callable differentiators and revised PDE usage. |
docs/conf.py |
Enables sphinx doctest extension. |
asv_bench/benchmarks/special.py |
Adds a PDE steady-state benchmark (currently contains runtime issues). |
asv_bench/benchmarks/defaults.py |
Adds benchmark scaffolding/mixins for SINDy/WeakSINDy. |
asv_bench/asv.conf.json |
Updates ASV install command to include extras. |
.pre-commit-config.yaml |
Updates black/flake8 versions. |
.github/workflows/release.yml |
Bumps release workflow Python to 3.11. |
.github/workflows/notebooks.yml |
Bumps notebooks workflow Python to 3.11. |
.github/workflows/main.yml |
Updates CI Python versions + switches to pre-commit GitHub Action + adds ASV publishing steps. |
.flake8 |
Comment clarifications for ignored rules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Also fix bug in EmptyLibrary.transform, set_params (Weak) return self, and remove unused (and broken) fixture Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
yb6599
left a comment
There was a problem hiding this comment.
Hey Jake, the documentation and type annotations are thorough. Aside from some minor comments, LGTM.
Was silently broken for PDEs
This PR, long in the making, refactors the weak form. Previously, weak form was implemented as a feature library, and the LHS calculation was guarded by an "if weak: do this, else: calculate derivatives". However, the weak form is a lot more than just a feature library, and the implementation forced a lot of "spooky action at a distance" in other classes and functions, which meant that touching those classes could cause weak sindy to break (e.g. #395). See #351 for more overall motivation.
In order for PDELibrary to support this change, it was easier to remove SINDy-PI from the PDELibrary and make it its own class, as discussed in in #192. This PR implements the recommended fix.
What do we get from refactoring WeakSINDy?
st_gridinWeakSINDyis analagous totinSINDy.predict()in weak problems, because they use normal feature libraries. This includes simulate and score for ODEs.GeneralizedLibraryfor tensoring the weak form with a regular library are no longer needed. I believe there were also cases where they may also have been wrong in cases.SubdomainSpecsdataclass. People can implement alternate strategies (as mentioned in the original Messenger and Bortz paper) for choosing subdomains over the legacy random choice.BINDylike algorithm (See Feature: Adding BINDy (based on Greedy search that maximise evidence) to PySINDy #674)What does it cost?
WeakPDELibraryis maintained but deprecated because the newWeakSINDyclass is not compatible withu_1is a derivative of the first spatial dimension, whereasu1is the first system coordinate. This PR is a step in that direction, but ultimately we will need to usesympy.GeneralizedLibrary. However, this PR is is correct with odd tensored/concat constructions of libraries, due to the flattening/distributive operation, whereGeneralizedLibrarywas not. But because of the distributive operation, it's unclear howinputs_per_librarywould work. Perhaps ifinputs_per_librarywere passed intransform()(after all,input_featurescome infit, not__init__) it would be easier.Developer notes
The _weak.py module begins with a docstring describing the relevant helper objects.
SINDyandWeakSINDyruntime, peakmemory, and fit/predict performance.SemiTermis an internal object to keep track of an integral term symbolically. They areSemibecause sometimes multiple need to be summed up to get a single feature - this was always the case, but now it's explicit what operation each term is doing and what order to assemble the regression matrix.Future steps
_inputs_per_library, enablingGeneralizedLibraryPDELibrarymerely a dataclassfeature_library.transform(), as now).Remaining to do:
u_dot_wkeval_semiterm()Linting(Syntax for tests requires upgrading to 3.12, which requires upgrading pre-commits to conflicting versions. This is a TODO: For afterwards).fit()to allow steady-state solutions (e.g.Remove or modifyFixed PDE prediction shapes acrossreshape_samples_to_spatial_gridnow that we don't use it for weak (may still be needed for PDELibrary)SINDyandWeakSINDy. Still probably need to remove this method, though.SemiTerma class, not just a tuple type alias_linear_weightsworks for arbitrary test function