TSDynamics
Dynamical systems in Python: 177 built-in systems, a native Rust integration engine, and a chaos-analysis toolkit — with the simplest system-definition contract anywhere.
You write the math (one symbolic method); TSDynamics lowers it to a native Rust engine and handles integration, Lyapunov spectra, bifurcation diagrams, Poincaré sections, attractors & basins — and even the documentation page for your system.
A built-in Lorenz attractor — integrated, spun, and saved to a GIF (code below).
import tsdynamics as ts
lor = ts.systems.Lorenz(ic=[1.0, 1.0, 1.0])
traj = lor.run(final_time=100.0, dt=0.01) # `run` is the one verb, every family
traj["x"] # named component access
print(ts.analysis.lyapunov_spectrum(lor)) # an analysis is a free function
# LyapunovSpectrum λ = [0.916, 0.0001893, -14.58] chaotic · D_KY = 2.063 (Lorenz)
📖 Documentation: https://el3ssar.github.io/TSDynamics/
Define your own system
import tsdynamics as ts
class Rossler(ts.ContinuousSystem):
variables = ("x", "y", "z") # dim inferred = 3
params = {"a": 0.2, "b": 0.2, "c": 5.7}
_reference = "Rössler (1976), Phys. Lett. A 57, 397-398"
def _equations(y, t, a, b, c):
x, yv, z = y(0), y(1), y(2)
return (-yv - z, x + a * yv, b + z * (x - c))
That's the whole contract. The class auto-registers: every analysis tool works
on it, the test-suite sweeps it, and the docs build renders its equations
(LaTeX, straight from the symbolics) and its attractor — zero extra steps. Delay
systems use y(0, t - tau); maps implement _step (the Jacobian is derived from
it); SDEs add a _diffusion term. Rossler().info prints everything the library
read back to you.
From equations to figures
There is one plotting verb, and it has one rule: ts.plot draws everything
you hand it on one figure and gives you back a Plot — a positional string
says how to draw, everything else says what to draw, and a Plot handed back in
is just another thing to draw. That last clause is why grids, movies and
escape-to-matplotlib need no extra API. The Plot is backend-neutral: tweak it
fluently, then save to matplotlib, plotly (interactive), three.js or JSON.
Bifurcation diagram of the logistic map, with the period-doubling onsets
marked — orbit_diagram is one call, and .bifurcation_points() finds the
cascade ($r_1 = 3$, $r_2 = 1 + \sqrt6 \approx 3.449$, …):
import numpy as np, tsdynamics as ts
orbit = ts.analysis.orbit_diagram(ts.systems.Logistic(), "r", np.linspace(2.8, 4.0, 2000))
pts = orbit.bifurcation_points()
p = ts.plot(orbit, color="k", markersize=0.2, alpha=0.5, # tiny translucent dots
xlabel="r", ylabel="x*", title="Logistic bifurcation")
for i, lbl in [(0, " r₁"), (1, " r₂"), (3, " r₄")]: # mark the onsets
p.vline(pts[i], label=lbl, color="red", linestyle="dashed")
p.save("bifurcation.png", size=(1600, 700))
A PDE, too — the Kuramoto–Sivashinsky equation is a built-in spatially extended system; its space–time field is auto-detected and drawn as a heatmap:
import tsdynamics as ts
ks = ts.systems.KuramotoSivashinsky(N=128, L=22.0)
traj = ks.run(final_time=200.0, dt=0.25)
ts.plot(traj).save("ks.png") # 128-mode space–time field
Named plots, grids, primitives. Beyond the auto-detected default, a
positional string names a plot transform — 38 of them, from nullclines and
streamlines to ftle, cobweb, invariant_density and trace_determinant.
Each declares which primitives can draw it, so you pick the drawing without
touching a backend; and because a Plot is itself a legal subject, a grid of
different plots is the same call:
import numpy as np, tsdynamics as ts
vdp = ts.systems.VanDerPol(params={"mu": 1.0})
traj = vdp.run(final_time=30.0, dt=0.01, ic=[0.5, 0.0])
ts.plot(vdp, traj, "flow_speed", "nullclines") # overlay, order-free
ts.plot(ts.plot(traj), ts.plot(traj, "psd"), layout="row") # a grid
ts.viz.draw({"x": np.arange(5.0), "y": np.arange(5.0) ** 2}, "line") # bare arrays
@ts.viz.transforms.register(source="data", frame="time", kind="diagnostic_curve",
primitives=("line", "points"))
def speed(traj):
"""Instantaneous speed |dx/dt| along the orbit."""
return {"x": traj.t[1:],
"y": np.linalg.norm(np.diff(traj.y, axis=0), axis=1) / np.diff(traj.t)}
ts.plot(traj, "speed") # ...now in the gallery, the matrix, and every plot door
A transform owns no new math — it adapts an estimator from tsdynamics.analysis.
The gallery is
generated from the registry, so every picture there is produced by the snippet
printed beside it.
The spinning attractor at the top is the same call, animated:
import tsdynamics as ts
traj = ts.systems.Lorenz().run(final_time=100.0, dt=0.01)
p = ts.plot(traj, animate=True)
p.style(axes=False).trail(None).camera(spin=0.4) # full curve, no axes, rotate
p.animate(fps=30, duration=10, loop=True)
p.save("lorenz.gif")
A taste of the analysis layer
import numpy as np, tsdynamics as ts
# Poincaré section of the Rössler attractor (root-refined crossings)
section = ts.analysis.poincare_section(ts.systems.Rossler(), plane=("y", 0.0, "up"), crossings=500)
# Fixed points of the Hénon map, with stability — the repr IS the report
print(ts.analysis.fixed_points(ts.systems.Henon()))
# FixedPointSet 2 points · 0 stable, 2 unstable (Henon)
# [0] x* = [-1.1314 -0.3394] unstable |λ|max = 3.26
# [1] x* = [0.6314 0.1894] unstable |λ|max = 1.924
# Maximal Lyapunov exponent — no Jacobian needed
ts.analysis.lyapunov_spectrum(ts.systems.Lorenz(), k=1, ic=[1, 1, 1]) # ≈ 0.90
Plus: attractors & basins of any flow or map, correlation/Rényi fractal dimensions, RQA (recurrence quantification), delay embedding (Takens, optimal τ, Cao/FNN), periodic orbits (shooting, Davidchack–Lai, rigorous interval enclosure), GALI, the 0–1 chaos test & Hunt–Ott expansion entropy, and Lyapunov exponents from a bare time series (Kantz/Rosenstein).
Highlights
-
Four families, one interface — ODEs, DDEs, SDEs and discrete maps all answer the same
runverb and the same stepping protocol (reinit/step/state/time), so every analysis composes over all of them. -
A small surface —
tsdynamics.<TAB>is 17 names,lorenz.<TAB>is 19, and every analysis is a free function whose first argument is its subject (ts.analysis.lyapunov_spectrum(lorenz)). A name that moved raises an error printing its new address; that error is the migration guide. -
177 built-in systems with literature parameters (142 ODEs, 26 maps, 6 DDEs, 3 SDEs).
-
Native engine: equations lower to a Rust engine in-process and run on a built-in Cranelift JIT (or a bit-for-bit identical SSA-tape interpreter); parameters are runtime values, so changing them is free, and nothing is ever written to disk.
-
dtsamples,rtoldecides — the output grid and the accuracy knob are separate. The adaptive steppers use genuine dense output, so a coarsedtis cheap without being less accurate, andmax_step=is there when you need to bound the step explicitly. Defaults arertol=1e-9/atol=1e-12. -
Composition — a Poincaré section of a flow is a discrete map, so
ts.analysis.orbit_diagram(ros.poincare("y", 0.0), "c", values)draws the bifurcation diagram of a flow in one line. -
Backend-neutral plotting — one
Plotrenders to matplotlib, plotly (interactive + animated HTML), three.js, or JSON, with a fluent styling/theming vocabulary and registries for transforms, primitives, themes and renderers.
Install
pip install tsdynamics # or: uv add tsdynamics
A prebuilt abi3 wheel (manylinux / musllinux / macOS / Windows) bundles the
native Rust engine. No Rust toolchain and no C compiler needed to install or
run. Optional plotting extra: tsdynamics[plot,interactive] (matplotlib, plotly).
Development
git clone https://github.com/El3ssar/TSDynamics && cd TSDynamics
uv sync --group dev --group docs
make test # change-scoped fast tier (the loop)
make test-slow # change-scoped slow tier
make test-all # whole fast tier — pre-push sanity
TSD_DOCS_FIGURES=0 uv run mkdocs serve # docs preview
The suite is registry-driven — every test is parametrized over all 177 systems —
so a plain pytest is thousands of items. make test runs only what your diff
touches, which is what CI does too.
Releases are automated: conventional-commit PR titles drive semantic-release on merge. See CONTRIBUTING.
License
MIT © Daniel Estevez
Metadata
Release files for tsdynamics 6.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tsdynamics-6.0.1.tar.gz | 1.6 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| tsdynamics-6.0.1-cp312-abi3-win_amd64.whl | CPython 3.12 | abi3 | Windows x86-64 | Details |
| tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_x86_64.whl | CPython 3.12 | abi3 | Linux musl 1.2+ x86-64 | Details |
| tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_aarch64.whl | CPython 3.12 | abi3 | Linux musl 1.2+ ARM64 | Details |
| tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.12 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| tsdynamics-6.0.1-cp312-abi3-macosx_11_0_arm64.whl | CPython 3.12 | abi3 | macOS 11.0+ ARM64 | Details |
| tsdynamics-6.0.1-cp312-abi3-macosx_10_12_x86_64.whl | CPython 3.12 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 25.2 MB
Release files / tsdynamics-6.0.1.tar.gz
| Download URL | tsdynamics-6.0.1.tar.gz |
|---|---|
| Size | 1.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8b5c8380f3385179a2e6cd8f78a5baab31e7440b8ed942993a7ae3e530ed7797
|
|
BLAKE2b-256 checksum How to use checksums |
fc20faf7b5ba597e61d0ced5bfe71c6ad5c35fa8cf959eaee0a4364d28fb6e0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-win_amd64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-win_amd64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.12 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
514ab0eac997ac27e5995d2cdf4fbd673de2ca556f624796b7379bfa2e0153cd
|
|
BLAKE2b-256 checksum How to use checksums |
a52ed9ec2a992a8efc669652d5dab3e8078ce6582a1133576fdf15862e048cdc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_x86_64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | CPython 3.12 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
a5372c2bda94d4c29f75b9988e7b6a57d53b6875126af58a6eac56985b68c9dd
|
|
BLAKE2b-256 checksum How to use checksums |
006254eb9f34585b8f01ebe38a1a9dec65852f14570ace46d07112bc100ca0c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_aarch64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | CPython 3.12 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
f172222d99c676053f4e19136c8ad72d3fb27194efd60c5ca035a0b5b6969f18
|
|
BLAKE2b-256 checksum How to use checksums |
e3e1dfe54b42d2d15263d60aced30567c87572f59529188d349a319d554bd09f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
10a79af49b2f31aea12ba68a004e9792337885fd0778fcca04acd10251270499
|
|
BLAKE2b-256 checksum How to use checksums |
f6d0714dc109e4b15198fe31feddd75fb32fc48ccb1bb30e0e6b88d833d06e9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
9be8ce90f0627ba8d3ea2cad5baa280539ddfd7f3af0da6f597c176588dc216a
|
|
BLAKE2b-256 checksum How to use checksums |
d0d5477b7e73d222e0915464507b3f7acf7485f21ffe024f2296f80f2924d680
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-macosx_11_0_arm64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.9 MB |
| Tags | CPython 3.12 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8954218a6601abecf4ac1e5582505a0f45d17257dcb9b03b9418c2dbcf6e3233
|
|
BLAKE2b-256 checksum How to use checksums |
5dbb31014982ad5a89ad2d3efab1fb1c1b3975a22d1763cf7b2235f2eb92679c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / tsdynamics-6.0.1-cp312-abi3-macosx_10_12_x86_64.whl
| Download URL | tsdynamics-6.0.1-cp312-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.12 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
e049dad88e8bbfb22c111d2e24942261008ed0692dace452313e2f55517315a0
|
|
BLAKE2b-256 checksum How to use checksums |
757cd463fbfbef66d4054d2d320232be748b00dd7361b251ef64ad245a999b74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency log