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.3.tar.gz (32.4 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.3-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: juniper_recurrence_model-0.1.3.tar.gz
  • Upload date:
  • Size: 32.4 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.3.tar.gz
Algorithm Hash digest
SHA256 f5772662f5b720cb18b73fd1473afd8d70b6c91fb5d550e7506b1baf3183e346
MD5 c40282e3bd85969e6ff0ae0f1e75a039
BLAKE2b-256 ac67750c0bd37c7592eef3c4ace79f0aac66c75fce37cc1455bef6036d1fe261

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_recurrence_model-0.1.3.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.3-py3-none-any.whl.

File metadata

File hashes

Hashes for juniper_recurrence_model-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 28c4d75a381baf2d9c9c0a40672cc6fe8505926c7f42ed2d73b2f5c5a59df96c
MD5 6584640cafc4af39e851eab660bdd639
BLAKE2b-256 40653158b9bdc65c602a617a2aa5e932272d2dec05e9a20bb89cd2d56f3c1b04

See more details on using hashes here.

Provenance

The following attestation bundles were made for juniper_recurrence_model-0.1.3-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