Skip to main content

Compiled ODE/DDE integration and Lyapunov analysis for dynamical systems.

Project description

tsdynamics

Adaptive, compiled integration for ODEs (JiTCODE) and DDEs (JiTCDDE) with a small, clean API. You write the math; we handle compilation, tolerances, trajectories, and Lyapunov spectra.

  • ODE base: DynSysJiTCODE (jitcode)
  • DDE base: DynSysDelayJiTCDDE (jitcdde)
  • Lyapunov spectra: jitcode_lyap / jitcdde_lyap
  • Parameters are simple dicts exposed as attributes
  • Deterministic output grids via generate_timesteps

Contents


Install

You’ll need a C/C++ toolchain for JiTCODE/JiTCDDE.

  • Linux: sudo apt-get install build-essential python3-dev (Debian/Ubuntu)
  • macOS: xcode-select --install
  • Windows: Install “Microsoft C++ Build Tools” (VS Build Tools)

Option A — uv (recommended)

# create & activate venv
uv venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

# library only
uv pip install .

# or editable + dev tools (tests, lint, etc.)
uv pip install -e ".[dev]"
# docs extras:
uv pip install -e ".[docs]"

Option B — pip (plain)

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

pip install .
# or
pip install -e ".[dev]" ".[docs]"

Option C — conda (env + pip for the package)

conda create -n tsdynamics python=3.12 -y
conda activate tsdynamics

pip install .
# or
pip install -e ".[dev]" ".[docs]"

Dependencies (from pyproject.toml): numpy>=2,<3, scipy>=1.14,<2, matplotlib>=3.10.6, numba==0.62.1, jitcdde==1.8.3, jitcode==1.7.3, symengine==0.14.1, sympy==1.14.0.


Quickstart

ODE — Lorenz

import numpy as np
from tsdynamics.base.ode_base import DynSys

class Lorenz(DynSys):
    params = {"sigma": 10.0, "rho": 28.0, "beta": 8.0/3.0}
    n_dim = 3

    @staticmethod
    def _rhs(y, t, beta, rho, sigma):
        x, yv, z = y(0), y(1), y(2)
        return (sigma*(yv - x), rho*x - x*z - yv, x*yv - beta*z)

lor = Lorenz()
t, X = lor.integrate(dt=0.01, final_time=50.0, method="dop853", rtol=1e-8, atol=1e-10)
exps = lor.lyapunov_spectrum(dt=0.1, burn_in=50.0, final_time=300.0,
                             method="dop853", rtol=1e-8, atol=1e-10)
print(exps)  # ~[0.91, ~0, -14.57]

DDE — Mackey–Glass

import numpy as np
from tsdynamics.base.dde_base import DynSysDelay

class MackeyGlass(DynSysDelay):
    params = {"beta": 0.2, "gamma": 0.1, "tau": 17.0, "n": 10}
    n_dim = 1

    @staticmethod
    def _rhs(y, t, beta, gamma, tau, n):
        y_tau = y(0, t - tau)
        y_now = y(0, t)
        return [beta * y_tau / (1 + y_tau**n) - gamma * y_now]

mg = MackeyGlass()
history = lambda s: [1.0 + 0.1*np.sin(0.2*s)]  # avoid trivial equilibrium history

t, y = mg.integrate(dt=0.05, final_time=200.0, history=history, rtol=1e-8, atol=1e-10)
exps = mg.lyapunov_spectrum(n_lyap=1, dt=0.2, burn_in=100.0, final_time=600.0,
                            history=history, rtol=1e-8, atol=1e-10)
print(exps)  # small positive (~1e-3)

ODE — Lorenz–96 (periodic)

class Lorenz96(DynSys):
    params = {"f": 8.0, "N": 20}
    n_dim = 20

    @staticmethod
    def _rhs(y, t, f, N):
        return [ (y((i+1)%N) - y((i-2)%N)) * y((i-1)%N) - y(i) + f
                 for i in range(N) ]

l96 = Lorenz96()
t, X = l96.integrate(dt=0.05, final_time=50.0, method="dop853", rtol=1e-8, atol=1e-10)

ODE — Kuramoto–Sivashinsky (FD, periodic)

class KuramotoSivashinsky(DynSys):
    def __init__(self, N, L, initial_conds=None):
        if N < 5: raise ValueError("N >= 5 required")
        super().__init__(n_dim=N, params={"N": int(N), "L": float(L)}, initial_conds=initial_conds)

    @staticmethod
    def _rhs(y, t, N, L):
        dx = L / N
        inv_dx, inv_dx2, inv_dx4 = 1.0/dx, 1.0/dx**2, 1.0/dx**4
        rhs = []
        for j in range(N):
            jm2, jm1, jp1, jp2 = (j-2)%N, (j-1)%N, (j+1)%N, (j+2)%N
            u = y(j)
            nonlinear = - (y(jp1)**2 - y(jm1)**2) * (0.25*inv_dx)   # -0.5*(u^2)_x
            uxx      = (y(jp1) - 2*u + y(jm1)) * inv_dx2
            uxxxx    = (y(jm2) - 4*y(jm1) + 6*u - 4*y(jp1) + y(jp2)) * inv_dx4
            rhs.append(nonlinear - uxx - uxxxx)
        return rhs

ks = KuramotoSivashinsky(N=64, L=32.0, initial_conds=1e-2*np.random.randn(64))
t, U = ks.integrate(dt=0.1, final_time=300.0, method="dop853", rtol=1e-8, atol=1e-10)

Define your own system

ODE (DynSys)

  • Set params (dict) and n_dim (int).
  • Implement static _rhs(y, t, **params); return n_dim expressions. Use y(i) to access state components.
class MyODE(DynSys):
    params = {"a": 2.0, "b": 0.5}
    n_dim = 2
    @staticmethod
    def _rhs(y, t, a, b):
        x, z = y(0), y(1)
        return (a*x - b*z, x + z - x*z)

DDE (DynSysDelay)

  • Same pattern but delayed access is y(i, t - tau).
class MyDDE(DynSysDelay):
    params = {"tau": 1.5, "k": 2.0}
    n_dim = 1
    @staticmethod
    def _rhs(y, t, tau, k):
        x_tau = y(0, t - tau)
        x_now = y(0, t)
        return [k * x_tau - x_now]

Map (DynMap)

  • Same pattern but iterate is used instead of integrate.
class MyMap(DynMap):
    params = {"a": 2.0, "b": 0.5}
    n_dim = 2
    @staticmethod
    def _rhs(y, a, b):
        x, z = y(0), y(1)
        return (a*x - b*z, x + z - x*z)

Lyapunov spectra

# ODE
exps = obj.lyapunov_spectrum(
    dt=0.1, burn_in=50.0, final_time=300.0,
    n_lyap=None, method="dop853", rtol=1e-8, atol=1e-10
)

# DDE
exps = obj.lyapunov_spectrum(
    n_lyap=2, dt=0.2, burn_in=100.0, final_time=600.0,
    history=history, rtol=1e-8, atol=1e-10
)
  • ODE: time-weight the local LEs; constant dt ≈ simple mean.
  • DDE: use the weight returned by jitcdde_lyap at each step.

Contributing

We welcome sharp, clean contributions. See CONTRIBUTING.md for setup (uv/pip/conda), style, tests, and PR workflow.


License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tsdynamics-0.1.0.tar.gz (65.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tsdynamics-0.1.0-py3-none-any.whl (65.7 kB view details)

Uploaded Python 3

File details

Details for the file tsdynamics-0.1.0.tar.gz.

File metadata

  • Download URL: tsdynamics-0.1.0.tar.gz
  • Upload date:
  • Size: 65.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tsdynamics-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dd1803cd7e75a9754454a3de7890268d1a286729fb5ce0b394a3286bcf1a0adb
MD5 728ae27ee0d9f8492472176709f80797
BLAKE2b-256 e5e2ba9e8f25fdd558cc4be8b855692746bb4715f621816f38fd2da0eeb7fba8

See more details on using hashes here.

Provenance

The following attestation bundles were made for tsdynamics-0.1.0.tar.gz:

Publisher: publish.yml on El3ssar/TSDynamics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tsdynamics-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tsdynamics-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 65.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tsdynamics-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f492d0f100e39d9cc24f0d351d8131e6d7df9eb61fae047e1231b0cfedb10d99
MD5 986c1fb906bfc993d9148ef2263d1cfa
BLAKE2b-256 35e167958aca7f3e33cf5342ef58b0d8bde40b3672966e1e6265092d581e0a5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for tsdynamics-0.1.0-py3-none-any.whl:

Publisher: publish.yml on El3ssar/TSDynamics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page