Skip to main content

Δt-native Legendre Memory Unit (Approach-C) and recurrent model core for the juniper-recurrence application

Project description

juniper-recurrence-model

The model-specific core for the juniper-recurrence application — the selected model P3-C (LMU + Approach-C).

This package ships the Δt-native Legendre Memory Unit (Approach-C) — a closed-form, variable-step LMU discretisation that is the only first-principles-clean ("C1") option natively handling irregularly-sampled time series — and LMURegressor, the recurrent model implementing the shared juniper-model-core TrainableModel interface (now that that package has landed). The regressor keeps the LMU memory fixed and trains only a linear readout in closed form (least squares — no BPTT, fully deterministic); it passes model-core's conformance kit unchanged, making it the WS-4 refactor template (a non-cascor model on the shared model seam).

Design of record (in juniper-ml): notes/JUNIPER_RECURRENCE_MODEL_DETAILED_DESIGN_2026-06-14.md.

Why Approach-C

An LMU's linear memory obeys theta * m'(t) = A·m(t) + B·u(t) with fixed, closed-form matrices. Because the system is linear, its exact discretisation is a matrix exponential — no ODE solver, no autodiff-through-solver. For irregular sampling, the discrete update is simply evaluated at the real per-step gap dt: the dataset's dt channel is the discretisation step. A/B are never trained; only the read-in/readout are. That is the entire C1-clean, irregular-Δt-native story.

Install

pip install juniper-recurrence-model          # once published
pip install -e ".[test]"                       # local development

numpy-only at the core (the memory is a fixed linear recurrence requiring no autodiff).

Quick start

import numpy as np
from juniper_recurrence_model import VariableStepLMUMemory

mem = VariableStepLMUMemory(d=16, theta=1.0)   # order 16, window 1.0 (same unit as dt)

# Irregularly-sampled input: u driven on a non-uniform time grid
t = np.cumsum(np.r_[0.0, np.random.default_rng(0).uniform(0.02, 0.08, 239)])
dt = np.empty_like(t); dt[0] = 0.0; dt[1:] = np.diff(t)
u = np.sin(2.0 * t)

m = mem.rollout(u, dt)                          # (240, 16) memory trajectory
w = mem.decode_weights(rho=1.0)                 # read the input one full window ago
reconstruction = m @ w

Trainable model (LMURegressor)

The package also exposes LMURegressor, a juniper-model-core TrainableModel. The LMU memory is fixed; only a linear readout is fit, in closed form (least squares — no BPTT, fully deterministic). It is Δt-native: pass per-step gaps dt ((n, T)) and an optional readout_mask to fit / predict; both default to uniform gaps and the final step, so the bare ABC predict(X) works too. It reports canonical regression metrics (mse, rmse, mae, r2).

import numpy as np
from juniper_recurrence_model import LMURegressor, LMUSerializer

n, T, F = 48, 6, 3
X = np.random.default_rng(0).normal(size=(n, T, F))
y = X.reshape(n, -1) @ np.random.default_rng(1).normal(size=(T * F, 1))
dt = np.zeros((n, T)); dt[:, 1:] = np.random.default_rng(2).integers(1, 4, size=(n, T - 1))

model = LMURegressor(d=6)             # theta resolved data-driven from dt at fit time
result = model.fit(X, y, dt=dt)                 # closed-form readout solve
preds = model.predict(X, dt=dt)                 # (n, 1)
print(result.final_metrics["r2"], model.describe_topology()["model_type"])

LMUSerializer().save(model, "/tmp/lmu")   # writes /tmp/lmu.npz (lossless round-trip)

LMURegressor passes model-core's conformance kit unchanged (tests/test_conformance.py), proving the WS-4 refactor template.

Verified behaviour

Check Result
A (d=16) max eigenvalue real part −6.49 (< 0 → stable)
Reconstruction RMSE e_reg (regular grid) ≈ 0.035 (< 0.05)
Grid-invariance e_irr (irregular grid) ≈ 0.039–0.043 (≈1.15× e_reg; < 3·e_reg + 0.02)

Pinned by tests/test_lmu_grid_invariance.py. Numerics match the reference util/ad-hoc/verify_delta_t_reference_code.py in juniper-ml.

Numerical guardrails

  • Keep d ≲ 64 — the eigenvector matrix of A becomes ill-conditioned for large d (Padé scaling-and-squaring is the documented fallback for larger orders).
  • Stability is automatic for dt > 0 (Re(λ) < 0 ⇒ |e^z| < 1).
  • dt may be quantised (e.g. integer calendar-day gaps) and Abar/Bbar cached per bucket.

Versioning

PEP 440 + Keep a Changelog. Consumers should pin juniper-recurrence-model>=A.B,<A+1. See CHANGELOG.md.

License

MIT — see LICENSE.

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

juniper_recurrence_model-0.1.5.tar.gz (43.5 kB view details)

Uploaded Source

Built Distribution

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

juniper_recurrence_model-0.1.5-py3-none-any.whl (29.4 kB view details)

Uploaded Python 3

File details

Details for the file juniper_recurrence_model-0.1.5.tar.gz.

File metadata

  • Download URL: juniper_recurrence_model-0.1.5.tar.gz
  • Upload date:
  • Size: 43.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for juniper_recurrence_model-0.1.5.tar.gz
Algorithm Hash digest
SHA256 2dfd5c6aba502acfa5dd44189a1779e7ebf76cac08f5460affbcc023826111c5
MD5 76828de3cc7c6806330cbe492c0a318c
BLAKE2b-256 b77d5954fbebc85616386cbf699f307403297b1e0e34060f4371f1800415ee1b

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_recurrence_model-0.1.5.tar.gz:

Publisher: publish-recurrence-model.yml on pcalnon/juniper-recurrence

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

File details

Details for the file juniper_recurrence_model-0.1.5-py3-none-any.whl.

File metadata

File hashes

Hashes for juniper_recurrence_model-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 0747042fb1f8d668580d76020d56559f0a130e1d29006763f5270a1405f6e9ef
MD5 00141415cf0215bed516853025aaf78a
BLAKE2b-256 24fa0d5b6e857d4dd382b314cf4f0e360d76d516bd4ec46f3f32040191a06d80

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_recurrence_model-0.1.5-py3-none-any.whl:

Publisher: publish-recurrence-model.yml on pcalnon/juniper-recurrence

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