Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
# Changelog

## 0.1.0.dev0 - Unreleased
## [Unreleased]

## [0.2.0] — 2026-05-07

### Added
- `linoss_scan(U, A, dt, ...)` — sequence-level helper that runs `linoss_step` over a `(T, n)` input and returns the full trajectory.
- `damped_oscillator_closed_form(y, z, omega, gamma, dt)` — analytic closed-form damped harmonic oscillator step supporting variable `dt` (irregular sampling). Handles underdamped, critically damped, and overdamped regimes via exact matrix exponential.
- `stability` module: `is_stable`, `eigvals_to_freq_damping`, `freq_damping_to_oscillator_block`, `period_from_omega`, `harmonic_stack`.
- Optional `[probabilistic]` extra (requires scipy):
- `discretize_lti_with_noise`, `discretize_control`, `oscillator_mats` — exact matrix-exponential discretization helpers (van Loan's method).
- `kalman_filter`, `rts_smoother` — Bayesian state estimation over oscillator state-space models.
- `fit_oscillator_mle` — MLE parameter recovery from observed time series via log-space optimization.
- `examples/` directory with 8 runnable tutorials covering single-step, forced, damped, energy, convergence, validation errors, sunspots, and phase tracking.
- `docs/api.md` — per-function API reference.
- `docs/roadmap.md` — public vision document.

### Changed
- `__init__.py` — `__all__` extended with all new public symbols (23 total). Optional scipy-gated symbols included unconditionally; modules raise `LinOSSError` at call time if scipy is missing.
- `README.md` — Public API table extended; new Vision section linking to roadmap; new Quickstart subsections for scan, Kalman filtering, and MLE fitting.
- `PROVENANCE.md` — added framing-only attribution clarification and code-provenance section.
- `docs/architecture.md` — added Lineage section with mermaid timeline diagram.

### Notes
- Core remains NumPy-only. SciPy is gated behind the `[probabilistic]` extra.
- v0.1.0 was never released to PyPI; v0.2.0 is the first PyPI release.

## 0.1.0.dev0 — Unreleased (pre-release work)

- Initial public-alpha split of `linoss-dynamics` as a standalone package.
- NumPy-only LinOSS-style stepping helpers.
Expand Down
8 changes: 5 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
cff-version: 1.2.0
message: "If you use linoss-dynamics, please cite this package and the upstream LinOSS / D-LinOSS papers listed in PROVENANCE.md."
title: "linoss-dynamics"
version: "0.1.0.dev0"
date-released: 2026-05-03
version: "0.2.0"
date-released: 2026-05-07
repository-code: "https://github.com/bionicbutterfly13/linoss-dynamics"
url: "https://github.com/bionicbutterfly13/linoss-dynamics"
license: "MIT"
type: software
authors:
- name: "linoss-dynamics contributors"
- family-names: Saint-Victor
given-names: Mani
email: drmani215@gmail.com
abstract: "A small NumPy runtime package for LinOSS-style oscillator dynamics with explicit non-negative damping support."
keywords:
- linoss
Expand Down
17 changes: 17 additions & 0 deletions PROVENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ Status: active provenance record for public alpha.

The package is planned as MIT because it is a pure math/runtime primitive with no novelty claim over LinOSS or D-LinOSS.

## Code Provenance (Framing-only)

`linoss-dynamics` is an original NumPy implementation of the LinOSS algorithm described in Rusch & Rus (2024, arXiv:2410.03943) and the D-LinOSS damping extension (Boyer, Rusch & Rus, 2025, arXiv:2505.12171). No code is shared with the upstream JAX/Equinox reference implementation at `tk-rusch/linoss`.

**Upstream vs. this package:**

| Aspect | `tk-rusch/linoss` (upstream) | `linoss-dynamics` (this package) |
| --- | --- | --- |
| Runtime | JAX + Equinox | NumPy only |
| API style | `LinOSSLayer`, `LinOSSBlock`, `LinOSS` (Equinox modules) | Functions and error classes |
| Entry points | `apply_linoss_im`, `apply_linoss_imex` (JAX scan ops) | `linoss_step`, `damped_linoss_step`, `linoss_scan` |
| Class name collision | None — namespaces do not overlap | — |

**License obligation:** None. The LinOSS algorithm is described in the public papers above; this package is an independently written implementation. The upstream repository uses the MIT license; this package is also MIT-licensed, but there is no dependency or code-sharing relationship that creates an attribution obligation beyond the paper citations already present in `CITATION.cff`.

**Continuous.py provenance:** `damped_oscillator_closed_form` implements the analytic matrix-exponential solution for the forced damped harmonic oscillator. This technique is standard in the classical oscillator state-space literature (Hartikainen & Särkkä 2010; Solin & Särkkä 2014) and was motivated by 2026 work on closed-form damped oscillators for irregular time series (see the deep-research dossier section "Recent research trends" for the underlying line of work; no arXiv id is cited here to avoid fabricating an identifier for a paper whose exact metadata has not been independently verified).

## Evidence

- Package core: `src/linoss_dynamics/`
Expand Down
136 changes: 134 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,54 @@
`linoss-dynamics` is a small NumPy runtime package for LinOSS-style oscillator
dynamics with explicit non-negative damping support.

Status: **public alpha**. The package is not published to PyPI yet.
Status: **public alpha**.

## Vision

`linoss-dynamics` is the runtime physics layer for oscillatory state-space dynamics. The current scope (v0.2) covers the deterministic oscillator step, irregular-time stepping, Bayesian filtering, and stability/frequency introspection. The longer roadmap — including unified oscillator + attractor primitives and spatio-temporal extensions — is documented in [docs/roadmap.md](docs/roadmap.md).

## How this differs from the upstream LinOSS reference

The original [tk-rusch/linoss](https://github.com/tk-rusch/linoss) is a
**training-time JAX/Equinox neural-network library** that exposes LinOSS as
trainable layers (`LinOSSLayer`, `LinOSSBlock`, `LinOSS`) inside a deep-learning
model. It is the right choice when you are building or training a sequence model
that uses LinOSS as a learnable encoder.

`linoss-dynamics` is a **runtime physics package**. Same mathematics, different
audience and abstraction:

| Dimension | Upstream `tk-rusch/linoss` | `linoss-dynamics` |
|---|---|---|
| Use case | Training neural networks with LinOSS layers | Runtime simulation, control, replay-safe systems |
| Stack | JAX + Equinox | NumPy only — zero deep-learning dependencies |
| Granularity | Sequence-level via JAX `scan` (`apply_linoss_im`, `apply_linoss_imex`) | Single-step (`linoss_step`, `damped_linoss_step`) |
| Damping | Implicit (rolled into `A`) | **Explicit, non-negativity validated** (`G` parameter) |
| Energy diagnostics | Not exposed | `energy`, `delta_energy`, `convergence_window` |
| Errors | Generic shape errors | Typed hierarchy (`LinOSSError`, `InvalidShapeError`, `InvalidDampingError`, `UnsupportedModeError`) |
| Determinism contract | Best-effort (training-oriented) | Replay-safe — pure NumPy, deterministic, no hidden state |
| Install footprint | Heavy (JAX, Equinox, dataset deps) | Tiny (NumPy 1.24+) |

### Who needs `linoss-dynamics` specifically

- **Replay-safe agentic systems** — needed inside deterministic kernels where
every step must be byte-reproducible (e.g. consumed by
[`elume`](https://github.com/bionicbutterfly13/elume) at runtime).
- **Simulation and control** — robotics, signal processing, vibration analysis,
and any context that uses oscillatory dynamics as a runtime model rather than
a learnable layer.
- **Reference implementation for porting** — a clean NumPy baseline to validate
custom JAX/PyTorch ports of LinOSS against.
- **Embedded or minimal-dependency contexts** — anywhere a JAX install is too
heavy or unavailable.
- **Teaching and pedagogy** — a self-contained, well-tested, single-file solver
that students can read end-to-end in an afternoon.
- **Energy-based analysis** — applications that need to monitor energy drift
and detect convergence; these diagnostics are not exposed by the upstream
reference.

If you are training a neural network, use the upstream package. If you are
running a system that needs LinOSS as a deterministic physics step, use this one.

## What It Provides

Expand All @@ -19,7 +66,13 @@ Status: **public alpha**. The package is not published to PyPI yet.

## Install

Install from GitHub:
From PyPI (recommended):

```bash
pip install linoss-dynamics
```

From GitHub (latest main):

```bash
python -m pip install "linoss-dynamics @ git+https://github.com/bionicbutterfly13/linoss-dynamics.git@main"
Expand Down Expand Up @@ -66,6 +119,54 @@ y_next, z_next, metrics = damped_linoss_step(y, z, A, G, dt=0.1)
assert metrics["damping_mode"] == "explicit_g"
```

Sequence-level scan over a time series:

```python
import numpy as np
from linoss_dynamics import linoss_scan

U = np.random.default_rng(0).standard_normal((100, 1)) # (T, n)
A = np.array([1.0])

Y, Z, metrics = linoss_scan(U, A, dt=0.05)
print(Y.shape) # (101, 1) — includes initial state at Y[0]
```

Bayesian filtering over an observed time series (requires `[probabilistic]`):

```python
# pip install linoss-dynamics[probabilistic]
import numpy as np
from linoss_dynamics import kalman_filter, rts_smoother, oscillator_mats

Ad, Qd, Bd = oscillator_mats(beta=0.1, omega=2.0, sigma_proc=0.3, dt=0.05)
H = np.array([[1.0, 0.0]])
R = np.array([[0.1]])

y_obs = np.sin(np.linspace(0, 10, 200)) + 0.1 * np.random.default_rng(42).standard_normal(200)
filt = kalman_filter(y_obs, Ad, Qd, H, R)
smooth = rts_smoother(Ad, filt)

print(filt["m_f"].shape) # (200, 2)
print(smooth["m_s"].shape) # (200, 2)
```

Parameter fit on observed data (requires `[probabilistic]`):

```python
# pip install linoss-dynamics[probabilistic]
import numpy as np
from linoss_dynamics import fit_oscillator_mle

y = np.sin(np.linspace(0, 20, 400)) + 0.15 * np.random.default_rng(0).standard_normal(400)
result = fit_oscillator_mle(y, dt=0.05)

print(f"fitted omega = {result['omega']:.3f} rad/s")
print(f"fitted period = {result['period']:.3f} s")
```

Runnable tutorials for all use cases are in [examples/](examples/README.md).

## Numerical Contract

`A` controls oscillator stiffness/frequency.
Expand All @@ -88,19 +189,50 @@ Supported step modes:

## Public API

### Solver

| Name | Role |
| --- | --- |
| `linoss_step(y, z, A, dt, mode="implicit", B=None, u=None, damping=None, G=None)` | Advance one step, optionally dispatching to explicit damping when `damping` or `G` is supplied. |
| `damped_linoss_step(y, z, A, G, dt, mode="implicit", B=None, u=None)` | Advance one step with explicit non-negative damping. |
| `linoss_scan(U, A, dt, mode="implicit", B=None, y0=None, z0=None, damping=None, G=None)` | Run `linoss_step` over a `(T, n)` input sequence; returns `(Y, Z, metrics)`. |
| `energy(y, z, A)` | Return diagonal oscillator energy. |
| `delta_energy(previous_energy, next_energy)` | Return signed energy delta. |
| `convergence_window(deltas, threshold, window)` | Return true when recent absolute deltas are below a threshold. |
| `linoss_step_impl(...)` | Backward-compatible alias for callers using the implementation name. |

### Stability

| Name | Role |
| --- | --- |
| `is_stable(A, G=None, dt=None, mode="implicit")` | Return `(stable, reason)` for given oscillator parameters. |
| `eigvals_to_freq_damping(eigenvalues)` | Map complex eigenvalues to `(frequencies, damping_ratios)`. |
| `freq_damping_to_oscillator_block(omega, gamma, dt=1.0)` | Return a 2×2 real-form discrete oscillator block. |
| `period_from_omega(omega)` | Return `2π / omega`, vectorized. |
| `harmonic_stack(omegas, dampings=None, dt=1.0)` | Build a block-diagonal state matrix from `(omega, damping)` pairs. |

### Continuous (irregular-time)

| Name | Role |
| --- | --- |
| `damped_oscillator_closed_form(y, z, omega, gamma, dt, forcing=0.0)` | Analytic closed-form step using matrix exponential. Supports variable `dt` for irregular sampling. |

### Probabilistic (requires `pip install linoss-dynamics[probabilistic]`)

| Name | Role |
| --- | --- |
| `discretize_lti_with_noise(A_c, L, Qc, dt)` | Exact van Loan discretization — returns `(Ad, Qd)`. |
| `discretize_control(A_c, B, dt)` | ZOH discretization for control input — returns `(Ad, Bd)`. |
| `oscillator_mats(beta, omega, sigma_proc, dt=1.0, kappa=1.0)` | Build `(Ad, Qd, Bd)` for a single forced damped oscillator. |
| `kalman_filter(y, Ad, Qd, H, R, ...)` | Linear-Gaussian Kalman filter; returns filtered moments and log-likelihood. |
| `rts_smoother(Ad, filt)` | Rauch-Tung-Striebel backward smoother over Kalman filter outputs. |
| `fit_oscillator_mle(y, dt, ...)` | MLE parameter recovery for a damped oscillator SSM; returns fitted parameters and filter outputs. |

Public errors:

| Error | Raised when |
| --- | --- |
| `LinOSSError` | Base error for all LinOSS dynamics failures. |
| `InvalidShapeError` | Inputs cannot be broadcast to the oscillator state. |
| `InvalidDampingError` | Damping is outside the supported stable path, including negative `G`. |
| `UnsupportedModeError` | The discretization mode is unsupported. |
Expand Down
Loading