Skip to content

v1.3.0: modernise for Python 3.10–3.13 and bundle an updated PyDSTool - #417

Open
jarmarshall wants to merge 9 commits into
masterfrom
modernise-python-pydstool
Open

jarmarshall wants to merge 9 commits into
masterfrom
modernise-python-pydstool

Conversation

@jarmarshall

@jarmarshall jarmarshall commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Brings MuMoT up to date with current Python (3.10–3.13) and the current scientific Python stack (NumPy 2, SciPy, SymPy 1.14, Matplotlib, IPython 9, Notebook 7/JupyterLab), unpins dependencies, and removes the dependency on the unmaintained PyDSTool package. Also prepares the v1.3.0 release (see Release below).

PyDSTool

  • A trimmed copy of PyDSTool 0.91.0 is bundled as mumot._vendor.pydstool. The first commit (f7c8a55) adds the upstream files unmodified, so git diff f7c8a55 -- mumot/_vendor shows every change; mumot/_vendor/pydstool/README.md lists them.
  • Removed: AUTO, the compiled integrators (Dopri/Radau/ADMC), the Toolbox, and all distutils use.
  • Ported: NumPy 2 names, SciPy API changes, a replacement (_cst.py) for the stdlib parser module removed in Python 3.10, and Python 3.13 locals() semantics (PEP 667). The _cst.py output was checked node-for-node against the real parser module on 180,000 random expressions.
  • Fixed a PyCont bug: at a branch point, which branch got followed depended on floating-point rounding. It now uses the continuation tangent and the SVD null space, so the result is deterministic.

Continuation API

  • New mumot/continuation.py: a backend-independent equilibrium-continuation API (SymPy symbols in, NumPy arrays out).
  • MuMoTbifurcationView uses only this API and no longer does any PyDSTool name mangling. A native continuation implementation could replace PyDSTool by implementing two methods.

Dependencies and packaging

  • Packaging moved to pyproject.toml (PEP 621), requiring Python ≥ 3.10.
  • Dependencies have no upper bounds, but each has a tested lower bound. The new minimum_versions_job CI job installs exactly those minimum versions and runs the unit tests.
  • Fresh installs on 3.10–3.13 have no conflicts (pip check) and no dependency cycles (pipdeptree -w fail).
  • The one exact pin is antlr4-python3-runtime==4.11.*, which SymPy's LaTeX parser requires. It is tracked in Remove the exact antlr4-python3-runtime==4.11.* pin #418.

Code structure

  • Removed the circular import between mumot.utils and the package; previously an uninstalled checkout could not be imported.
  • Symbolic derivations moved from views to a new mumot.equations module, so the internal import graph is now acyclic.

Library compatibility fixes

  • SymPy changed latex() on strings, simplify() evaluating derivatives, and subs() with non-numeric values. Without these fixes the van Kampen ODEs came out wrong for multi-species models.
  • Matplotlib: 3D axes, tick labels and Arrow3D updated for current APIs.
  • Notebook 7 and IPython 9 removed APIs MuMoT used; interactive figures now use the ipympl backend.
  • Non-finite floats in widget state replaced with finite values, as non-finite values are not valid JSON in widget messages.
  • Fixed complex-valued angles for the noise ellipses.
  • Fixed two latent bugs: broken chmod calls and an undefined variable in utils.

CI, docs, Binder

  • CI, tox, Read the Docs (config v2), Binder files and the installation docs updated.
  • nbval (≥ 0.10) runs notebook cells without input history, which broke the documented %%model + mumot.parseModel(In[n]) pattern under test only. A root conftest.py makes nbval record history as Jupyter does.
  • The nbval per-cell timeout is raised from 120 s to 600 s. Coverage tracing makes the symbolic noise calculations about 4× slower; without it they are no slower than in the previous release.

Release

  • Binder: the README badge and all Getting Started Binder links now point at v1.3.0.
  • CHANGELOG: has a complete v1.3.0 entry.
  • Read the Docs: now fetches git tags, so the docs show the release version.
  • about.rst: credits the bundled PyDSTool; the broken ERC link is fixed.
  • Tag: the v1.3.0 tag still needs to be created on master after this PR is merged, not before. Pushing a v* tag triggers the Test PyPI and PyPI upload in CI, and the Binder links only work once the tag exists.
  • Still to do by hand: reserve the DOI for this release in ORDA, then add the citation for release 1.3.0 to docs/source/about.rst.

Behaviour changes

  • showNoiseEquations() and showNoiseSolutions() now render moments as ⟨η⟩, as the code intended. SymPy 1.4 silently skipped that substitution.
  • The iopub rate-limit tweak was removed because it had no effect; pass --ServerApp.iopub_msg_rate_limit when starting Jupyter instead.
  • AUTO-based curve types (limit-cycle continuation) are not available from the bundled PyDSTool. MuMoT never used them.

Testing

  • Reference results come from the previous release run on Python 3.8 with its original pinned dependencies (SymPy 1.4, PyDSTool 0.91.0).
  • Bifurcation diagrams: all 8 cases (1D/2D, folds, pitchforks, branch switching, Greek/protected symbol names) agree with the reference. Most branches match to ~1e-9; a few are traversed in the opposite direction or run slightly further along the same analytic curve.
  • Symbolic outputs: all 60 comparable model getter results match the reference mathematically. These are now a unit test (tests/test_symbolic_regression.py); it fails if SymPy's default simplify is used again.
  • New unit tests for the continuation API (fold, pitchfork branch switching, protected names) and for the vendored parser shim.
  • CI is green on 3.10, 3.11, 3.12 and 3.13: 63 unit tests, the 5 test notebooks and the user manual (nbval), the manual's no-outputs check, and the Sphinx docs build.
  • Minimum versions: minimum_versions_job installs the lowest allowed version of every dependency (Python 3.10) and passes the unit tests. Locally, the bifurcation results also match the reference on that set.
  • Binder environment: reproduced the repo2docker setup locally (Python 3.12, requirements.txt, postBuild, JupyterLab 4.6 / Notebook 7). In Chromium, integrate(), stream() and bifurcation() rendered interactive ipympl figures and widgets with no errors, and a slider change redrew the figure. The real mybinder.org image build has not been tested.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw

claude added 6 commits October 1, 2026 21:43
Add the subset of PyDSTool 0.91.0 (https://github.com/robclewley/pydstool,
BSD licence) that MuMoT needs for equilibrium continuation, exactly as
released on PyPI, so that the modernisation in the next commit can be
reviewed as a diff against upstream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
- Bundle a trimmed PyDSTool as mumot._vendor.pydstool, updated for
  Python >= 3.10, NumPy 2 and current SciPy: relative imports, removed
  AUTO/compiled integrators/Toolbox/distutils, NumPy 2 name aliases, and a
  replacement (_cst.py) for the removed stdlib `parser` module. See
  mumot/_vendor/pydstool/README.md for all changes.
- Fix PyCont branch switching at branch points, which depended on
  floating-point rounding, and make the Moore-Penrose corrector fall back to
  least squares when the bordered Jacobian is singular.
- Add mumot.continuation, a backend-independent equilibrium continuation API
  (SymPy symbols in, NumPy arrays out); the bifurcation view uses only this,
  so PyDSTool can later be replaced by a native implementation.
- Remove the circular import between mumot.utils and the package (new
  mumot._version) and move symbolic derivations out of views into
  mumot.equations; the internal import graph is now acyclic.
- Adapt to current SymPy (latex() of strings, simplify() evaluating
  derivatives, strict subs()), Matplotlib (3D axes, tick labels, Arrow3D),
  IPython 9 and Notebook 7 (ipympl backend); fix the broken chmod calls and
  an undefined variable in utils.
- Move packaging to pyproject.toml (Python >= 3.10, unpinned dependencies
  except antlr4 4.11 required by SymPy's LaTeX parser); update tox, CI,
  Read the Docs, Binder, docs and CHANGELOG.
- Add unit tests for the continuation API and the vendored parser shim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
Unbounded requirements let resolvers (or existing environments) pick
ancient releases, e.g. `--resolution lowest-direct` tried to build
ipython 0.10. Give each direct dependency the oldest version with the
APIs MuMoT relies on, and add a CI job that installs exactly those
versions, checks consistency and runs the unit tests, so the floors stay
honest. No upper bounds are added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
- Clamp the cosine used for the noise ellipse angle to a real value in
  [-1, 1]; round-off made the angle complex, which Matplotlib rejects.
- Use finite temporary slider maxima instead of float('inf'): non-finite
  floats in widget state are not valid JSON (deprecated by jupyter_client).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
tests/symbolic_reference.json holds srepr dumps of the model getters'
results (ODEs, van Kampen ODEs, stoichiometry, master equation, van Kampen
expansion, Fokker-Planck and noise equations/solutions) produced with the
previous release on Python 3.8 and SymPy 1.4. The test checks the current
results are mathematically equal. It fails if SymPy's default simplify()
(which evaluates derivatives) is used again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
PyDSTool defined generated functions with exec(code) and read them back
via locals()[name]. Since Python 3.13 locals() in a function returns a new
snapshot on every call, so the names were lost (KeyError
'_auxfn_getbound' in every bifurcation diagram). Exec into an explicit
namespace and read from it instead; behaviour on 3.10-3.12 is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
claude added 2 commits October 2, 2026 04:35
- nbval (>= 0.10) executes cells with store_history=False, so `In` stays
  empty and the user manual's documented `%%model` +
  `mumot.parseModel(In[2])` pattern raised IndexError in every tox job.
  A root conftest.py makes nbval execute cells with history, as Jupyter
  does; the manual and API are unchanged.
- minimum_versions_job: astral-sh/setup-uv already creates and activates
  .venv when python-version is set, so drop the failing `uv venv` step and
  run pytest from that environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
tox runs the test notebooks under branch coverage, which makes the
symbolic noise calculations about 4x slower: MuMoTtest_NoiseFixedPoints
cell 52 (noiseCorrelations) takes ~26 s normally but ~102 s under
coverage locally, so on a slower runner it exceeded the 120 s limit
(3.12 job; the same cell passed in the previous run). Without coverage it
is no slower than with the previous release (28 s). The timeout only
guards against hung cells, so give it headroom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
@jarmarshall jarmarshall self-assigned this Oct 2, 2026
- Point the README badge and all Getting Started Binder links at v1.3.0
  (README badge now uses the same urlpath=tree/... form as the docs).
- CHANGELOG: "Unreleased" becomes v1.3.0 and lists the later fixes.
- about.rst: credit the bundled PyDSTool (BSD licence); fix the broken
  European Research Council link.
- Read the Docs: fetch git tags after checkout so setuptools_scm reports
  the release version rather than 0.1.devN from the shallow clone.
- Update the docs copyright years and the Binder URL example in the
  release instructions.

The v1.3.0 tag itself is to be created on master after this PR is merged
(pushing a v* tag triggers the PyPI upload in CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPcTBfCTpRyi5y8kDcXFWw
@jarmarshall jarmarshall changed the title Modernise for Python 3.10–3.13 and bundle an updated PyDSTool v1.3.0: modernise for Python 3.10–3.13 and bundle an updated PyDSTool Oct 2, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants