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.4.tar.gz (35.9 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.4-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: juniper_recurrence_model-0.1.4.tar.gz
  • Upload date:
  • Size: 35.9 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.4.tar.gz
Algorithm Hash digest
SHA256 a84ca82e901fe7fedbabe0a566fbba2de295d02c52f7ac2e9c73b379c8992e60
MD5 e370f0ca3cd24c193e38fa16615a6633
BLAKE2b-256 bbcaf90c070cdfff9e121378a37a50928ac660a346784ed4bf2b24a25f58b944

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for juniper_recurrence_model-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 989fdbc6b72805fdd1416963c157c97ce22c47f3d81b5e50d81097d6d11ecb46
MD5 32dd01516ff4bf839d5cef33e6a5fbf4
BLAKE2b-256 5a170282e5883289d12aae867276d46ea56461171bbc88e7e6ce25224c8e8255

See more details on using hashes here.

Provenance

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