SolvLRDE: Examples
Each example is a verified run of a guided tour script from the shipped python/examples/
bundle (see PROGRESSION.md for the full 01→06 sequence). Run the guided tour
first: it narrates the scenario, explains each metric, and shows when to trust the result. Use
the production one-liner when wiring the same API call into your pipeline.
Beta release. Products in this catalog other than the SolvSRK family and SolvScout / SolvTune are beta — suitable for trials and evaluation; APIs and packaging may change before GA. Do not deploy beta builds in production programs without a signed agreement with Resonix. Activate a trial license before running examples — see Install and Licensing.
Journeys
Example 1: Journey 01 — First contact
Version, license, and the smallest direct linear solve. Every support ticket starts with the package version. SolvLRDE is gated by a machine-locked .lic file. In production you set SOLVLRDE_LICENSE_FILE or run python -m solvlrde activate <file>.lic. There is no dev bypass - the seat is bound to this machine.
Walkthrough:
-
What you are doing — Confirm the wheel and the native libsolvlrde load, see your license state, and solve the smallest linear ODE that proves the solver is alive.
-
Version — Every support ticket starts with the package version.
version: 0.1.0
-
License — SolvLRDE is gated by a machine-locked .lic file. In production you set SOLVLRDE_LICENSE_FILE or run
python -m solvlrde activate <file>.lic. There is no dev bypass - the seat is bound to this machine.license_valid: Truedays_remaining: 29license_info: valid=1 licensee=ci seat_id=default expires=2026-08-29 duration_days=30 days_remaining=29
-
Machine code — If activation fails, this is the fingerprint you paste into the Resonix portal to be issued a seat. It is safe to share.
machine_code: B08B-F26C-BFC6-FE1C-A199-A07F-F34B-3F8C-9D7E-DA6D-CFB8-7480-BE42-9449-84B2-1AF5default_license_path: ~\AppData\Roaming\solvlrde\license.dat
-
Simplest solve — Solve exponential decay y' = -y from t=0 to T=1 with y(0)=1. The exact answer is exp(-1). SolvLRDE does NOT time-step: it evaluates y(T) directly by inverse-Laplace integration on a Talbot contour, all in the C core.
survived: Truestatus: oky: [0.367879]n_talbot_used: 32n_solves: 32nu_used: 0.6contour_scale: 12.8wall_s: 0.001expected: [0.367879]max_abs_error: 9.492e-11
Guided tour output (from a verified run):
Version, license, and the smallest direct linear solve
-- Act 1 - What you are doing --
-> Confirm the wheel and the native libsolvlrde load, see your license state, and solve the smallest linear ODE that proves the solver is alive.
-- Act 2 - Version --
-> Every support ticket starts with the package version.
version: 0.1.0
-- Act 3 - License --
-> SolvLRDE is gated by a machine-locked .lic file. In production you set SOLVLRDE_LICENSE_FILE or run `python -m solvlrde activate <file>.lic`. There is no dev bypass - the seat is bound to this machine.
license_valid: True
days_remaining: 29
license_info: valid=1 licensee=ci seat_id=default expires=2026-08-29 duration_days=30 days_remaining=29
-- Act 4 - Machine code --
-> If activation fails, this is the fingerprint you paste into the Resonix portal to be issued a seat. It is safe to share.
machine_code: B08B-F26C-BFC6-FE1C-A199-A07F-F34B-3F8C-9D7E-DA6D-CFB8-7480-BE42-9449-84B2-1AF5
default_license_path: ~\AppData\Roaming\solvlrde\license.dat
-- Act 5 - Simplest solve --
-> Solve exponential decay y' = -y from t=0 to T=1 with y(0)=1. The exact answer is exp(-1). SolvLRDE does NOT time-step: it evaluates y(T) directly by inverse-Laplace integration on a Talbot contour, all in the C core.
survived: True
status: ok
y: [0.367879]
n_talbot_used: 32
n_solves: 32
nu_used: 0.6
contour_scale: 12.8
wall_s: 0.001
expected: [0.367879]
max_abs_error: 9.492e-11
-- Run complete --
next_journey: 02_core_path.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/01_first_contact.py
# Production one-liner — same API call you ship:
python -c "import solvlrde as s; print(s.__version__, s.license_valid())"Example 2: Journey 02 — Core path
Solve y' = A y directly at a target time T. A = diag(-0.5, -1, -2) makes three independent decay modes. Because the modes are decoupled, y_i(T) = exp(lambda_i * T) * y0_i - a closed form we can check against. The result dict carries y plus n_talbot_used and n_solves: how many contour nodes (each a complex linear solve) the auto-tuned contour needed. That count - not T - is what drives the cost.
Walkthrough:
-
The workhorse call — solvlrde.solve(A, y0, T) is the one call you will use most. Give it a dense system matrix A, an initial state y0, and a target time T; it returns y(T) plus a receipt describing the contour it used.
-
A decoupled 3-mode system — A = diag(-0.5, -1, -2) makes three independent decay modes. Because the modes are decoupled, y_i(T) = exp(lambda_i * T) * y0_i - a closed form we can check against.
A_diag: [-0.5, -1.0, -2.0]y0: [1.0, 1.0, 1.0]T: 2.0
-
Solve and read the receipt — The result dict carries y plus n_talbot_used and n_solves: how many contour nodes (each a complex linear solve) the auto-tuned contour needed. That count - not T - is what drives the cost.
survived: Truestatus: oky: [0.367879, 0.135335, 0.0183156]n_talbot_used: 32n_solves: 32nu_used: 0.6contour_scale: 6.4wall_s: 0expected: [0.367879, 0.135335, 0.0183156]max_abs_error: 4.347e-10
-
Constant forcing — Add a constant source with b=. For y' = -y + 1, y(0)=0 the exact answer is 1 - exp(-T). Same call, one extra keyword.
survived: Truestatus: oky: [0.950213]n_talbot_used: 32n_solves: 32nu_used: 0.6contour_scale: 4.267wall_s: 0expected: [0.950213]max_abs_error: 2.688e-10
Guided tour output (from a verified run):
Solve y' = A y directly at a target time T
-- Act 1 - The workhorse call --
-> solvlrde.solve(A, y0, T) is the one call you will use most. Give it a dense system matrix A, an initial state y0, and a target time T; it returns y(T) plus a receipt describing the contour it used.
-- Act 2 - A decoupled 3-mode system --
-> A = diag(-0.5, -1, -2) makes three independent decay modes. Because the modes are decoupled, y_i(T) = exp(lambda_i * T) * y0_i - a closed form we can check against.
A_diag: [-0.5, -1.0, -2.0]
y0: [1.0, 1.0, 1.0]
T: 2.0
-- Act 3 - Solve and read the receipt --
-> The result dict carries y plus n_talbot_used and n_solves: how many contour nodes (each a complex linear solve) the auto-tuned contour needed. That count - not T - is what drives the cost.
survived: True
status: ok
y: [0.367879, 0.135335, 0.0183156]
n_talbot_used: 32
n_solves: 32
nu_used: 0.6
contour_scale: 6.4
wall_s: 0
expected: [0.367879, 0.135335, 0.0183156]
max_abs_error: 4.347e-10
-- Act 4 - Constant forcing --
-> Add a constant source with b=. For y' = -y + 1, y(0)=0 the exact answer is 1 - exp(-T). Same call, one extra keyword.
survived: True
status: ok
y: [0.950213]
n_talbot_used: 32
n_solves: 32
nu_used: 0.6
contour_scale: 4.267
wall_s: 0
expected: [0.950213]
max_abs_error: 2.688e-10
-- Run complete --
next_journey: 03_configuration.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/02_core_path.py
# Production one-liner — same API call you ship:
python -c "import solvlrde,numpy as np; print(solvlrde.solve(np.diag([-0.5,-1.,-2.]),[1,1,1],T=2.0)['y'])"Example 3: Journey 03 — Configuration (contour + N)
Tune the Talbot contour: node count, nu, and contour shape. Solve the stiff system with the default contour and note n_talbot_used and the achieved error. Raise n_talbot and nu on a copy of the config, pass it via cfg=, and watch n_solves rise with accuracy. This is the accuracy/cost dial.
Walkthrough:
-
What the config controls — config_defaults() returns a SolvlrdeConfig. The two knobs you tune most are n_talbot (number of contour nodes = accuracy vs cost) and nu (a contour-shape parameter). More nodes -> more accuracy and more linear solves; the auto-tuner picks a sane default per problem.
n_talbot: 32nu: 0.6mu_scale: 1.0emit_diagnostics: 0
-
Default nodes — Solve the stiff system with the default contour and note n_talbot_used and the achieved error.
survived: Truestatus: oky: [1.88928e-15, 0.36788]n_talbot_used: 32n_solves: 32nu_used: 0.6contour_scale: 12.8wall_s: 0expected: [0, 0.36788]max_abs_error: 9.153e-11
-
More nodes = tighter contour — Raise n_talbot and nu on a copy of the config, pass it via cfg=, and watch n_solves rise with accuracy. This is the accuracy/cost dial.
requested_n_talbot: 64requested_nu: 0.7survived: Truestatus: oky: [-3.92167e-10, 0.367866]n_talbot_used: 64n_solves: 64nu_used: 0.7contour_scale: 25.6wall_s: 0expected: [0, 0.36788]max_abs_error: 1.369e-05
-
Contour shape — SolvLRDE ships three contour families, selectable with the module-level constants CONTOUR_WEIDEMAN (default), CONTOUR_TREFETHEN, and CONTOUR_TREF_EQ. Apply one with the C config setter.
CONTOUR_WEIDEMAN: 0CONTOUR_TREFETHEN: 1CONTOUR_TREF_EQ: 2— Applied CONTOUR_TREFETHEN to a fresh config.survived: Truestatus: oky: [-5.15648e-16, 0.36788]n_talbot_used: 32n_solves: 32nu_used: 0.5contour_scale: 12.8wall_s: 0expected: [0, 0.36788]max_abs_error: 7.994e-09
Guided tour output (from a verified run):
Tune the Talbot contour: node count, nu, and contour shape
-- Act 1 - What the config controls --
-> config_defaults() returns a SolvlrdeConfig. The two knobs you tune most are n_talbot (number of contour nodes = accuracy vs cost) and nu (a contour-shape parameter). More nodes -> more accuracy and more linear solves; the auto-tuner picks a sane default per problem.
n_talbot: 32
nu: 0.6
mu_scale: 1.0
emit_diagnostics: 0
-- Act 2 - Default nodes --
-> Solve the stiff system with the default contour and note n_talbot_used and the achieved error.
survived: True
status: ok
y: [1.88928e-15, 0.36788]
n_talbot_used: 32
n_solves: 32
nu_used: 0.6
contour_scale: 12.8
wall_s: 0
expected: [0, 0.36788]
max_abs_error: 9.153e-11
-- Act 3 - More nodes = tighter contour --
-> Raise n_talbot and nu on a copy of the config, pass it via cfg=, and watch n_solves rise with accuracy. This is the accuracy/cost dial.
requested_n_talbot: 64
requested_nu: 0.7
survived: True
status: ok
y: [-3.92167e-10, 0.367866]
n_talbot_used: 64
n_solves: 64
nu_used: 0.7
contour_scale: 25.6
wall_s: 0
expected: [0, 0.36788]
max_abs_error: 1.369e-05
-- Act 4 - Contour shape --
-> SolvLRDE ships three contour families, selectable with the module-level constants CONTOUR_WEIDEMAN (default), CONTOUR_TREFETHEN, and CONTOUR_TREF_EQ. Apply one with the C config setter.
CONTOUR_WEIDEMAN: 0
CONTOUR_TREFETHEN: 1
CONTOUR_TREF_EQ: 2
-> Applied CONTOUR_TREFETHEN to a fresh config.
survived: True
status: ok
y: [-5.15648e-16, 0.36788]
n_talbot_used: 32
n_solves: 32
nu_used: 0.5
contour_scale: 12.8
wall_s: 0
expected: [0, 0.36788]
max_abs_error: 7.994e-09
-- Run complete --
next_journey: 04_time_independence.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/03_configuration.py
# Production one-liner — same API call you ship:
python -c "import solvlrde as s; c=s.config_defaults(); c.n_talbot=48; c.nu=0.7; print(c.n_talbot)"Example 4: Journey 04 — Time-independent cost
The headline property: cost does not grow with T. Solve at T = 1, 1e3, 1e6. Watch n_solves and wall_s stay flat while T spans six orders of magnitude. The slow component decays to ~0 by 1e6. Constant n_solves across a 1e6 range in T is the property no time-stepper has. If you need y at a far-future (or far-past-scale) horizon of a linear system, this is the tool.
Walkthrough:
-
Why this matters — A time-stepping solver pays for every step from 0 to T, so y(1e6) costs a million times more than y(1). SolvLRDE evaluates y(T) as a single contour integral: the number of linear solves depends on the contour, not on T. y(1e6) costs the same as y(1).
-
Same system, three horizons — Solve at T = 1, 1e3, 1e6. Watch n_solves and wall_s stay flat while T spans six orders of magnitude. The slow component decays to ~0 by 1e6.
survived: Truey_slow: 3.679162e-01n_talbot_used: 32n_solves: 32wall_s: 0survived: Truey_slow: 7.613409e-13n_talbot_used: 32n_solves: 32wall_s: 0survived: Truey_slow: 1.748079e-15n_talbot_used: 32n_solves: 32wall_s: 0
-
Takeaway — Constant n_solves across a 1e6 range in T is the property no time-stepper has. If you need y at a far-future (or far-past-scale) horizon of a linear system, this is the tool.
Guided tour output (from a verified run):
The headline property: cost does not grow with T
-- Act 1 - Why this matters --
-> A time-stepping solver pays for every step from 0 to T, so y(1e6) costs a million times more than y(1). SolvLRDE evaluates y(T) as a single contour integral: the number of linear solves depends on the contour, not on T. y(1e6) costs the same as y(1).
-- Act 2 - Same system, three horizons --
-> Solve at T = 1, 1e3, 1e6. Watch n_solves and wall_s stay flat while T spans six orders of magnitude. The slow component decays to ~0 by 1e6.
survived: True
y_slow: 3.679162e-01
n_talbot_used: 32
n_solves: 32
wall_s: 0
survived: True
y_slow: 7.613409e-13
n_talbot_used: 32
n_solves: 32
wall_s: 0
survived: True
y_slow: 1.748079e-15
n_talbot_used: 32
n_solves: 32
wall_s: 0
-- Act 3 - Takeaway --
-> Constant n_solves across a 1e6 range in T is the property no time-stepper has. If you need y at a far-future (or far-past-scale) horizon of a linear system, this is the tool.
-- Run complete --
next_journey: 05_banded_dae.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/04_time_independence.py
# Production one-liner — same API call you ship:
python -c "import solvlrde,numpy as np; print(solvlrde.solve(np.array([[-1e4,0.],[1e4,-1.]]),[1,0],T=1e6)['n_solves'])"Example 5: Journey 05 — Banded + DAE
Exploit sparsity, and reduce a linear DAE to canonical form. Solve the same heat problem two ways. banded_from_dense() builds the (kl+ku+1, n) layout; solve_banded() consumes it. The exact answer is exp(-pi^2 T) sin(pi x), so we can score both. A linear descriptor system M v' = -G v (+ f) is not yet in y' = A y + b form. reduce_dae(M, G, f) returns (A, b) with A = -M^-1 G. M may be a 1-D diagonal or a full matrix. Here M = diag(2, 4), G = diag(2, 8) gives v' = diag(-1, -2) v.
Walkthrough:
-
Banded is for structured operators — Many linear operators - a 1-D heat equation discretized by method of lines, a chain of coupled masses - are banded. Passing the dense matrix wastes O(n^2) memory; the banded path takes the compact band layout and the sub/super bandwidths (kl, ku).
-
Dense vs banded agree — Solve the same heat problem two ways. banded_from_dense() builds the (kl+ku+1, n) layout; solve_banded() consumes it. The exact answer is exp(-pi^2 T) sin(pi x), so we can score both.
dense_survived: Truebanded_survived: Truedense_vs_banded_maxdiff: 0.000e+00banded_vs_exact_maxerr: 3.580e-05band_shape: (3, 60)
-
DAE reduction — A linear descriptor system M v' = -G v (+ f) is not yet in y' = A y + b form. reduce_dae(M, G, f) returns (A, b) with A = -M^-1 G. M may be a 1-D diagonal or a full matrix. Here M = diag(2, 4), G = diag(2, 8) gives v' = diag(-1, -2) v.
A_from_dae: [[-1.0, -0.0], [-0.0, -2.0]]b_from_dae: Nonesurvived: Truestatus: oky: [0.367879, 0.135335]n_talbot_used: 32n_solves: 32nu_used: 0.6contour_scale: 12.8wall_s: 0.001expected: [0.367879, 0.135335]max_abs_error: 9.492e-11
Guided tour output (from a verified run):
Exploit sparsity, and reduce a linear DAE to canonical form
-- Act 1 - Banded is for structured operators --
-> Many linear operators - a 1-D heat equation discretized by method of lines, a chain of coupled masses - are banded. Passing the dense matrix wastes O(n^2) memory; the banded path takes the compact band layout and the sub/super bandwidths (kl, ku).
-- Act 2 - Dense vs banded agree --
-> Solve the same heat problem two ways. banded_from_dense() builds the (kl+ku+1, n) layout; solve_banded() consumes it. The exact answer is exp(-pi^2 T) sin(pi x), so we can score both.
dense_survived: True
banded_survived: True
dense_vs_banded_maxdiff: 0.000e+00
banded_vs_exact_maxerr: 3.580e-05
band_shape: (3, 60)
-- Act 3 - DAE reduction --
-> A linear descriptor system M v' = -G v (+ f) is not yet in y' = A y + b form. reduce_dae(M, G, f) returns (A, b) with A = -M^-1 G. M may be a 1-D diagonal or a full matrix. Here M = diag(2, 4), G = diag(2, 8) gives v' = diag(-1, -2) v.
A_from_dae: [[-1.0, -0.0], [-0.0, -2.0]]
b_from_dae: None
survived: True
status: ok
y: [0.367879, 0.135335]
n_talbot_used: 32
n_solves: 32
nu_used: 0.6
contour_scale: 12.8
wall_s: 0.001
expected: [0.367879, 0.135335]
max_abs_error: 9.492e-11
-- Run complete --
next_journey: 06_limits.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/05_banded_dae.py
# Production one-liner — same API call you ship:
python examples/journeys/05_banded_dae.pyExample 6: Journey 06 — Limits
What SolvLRDE is for, and what belongs to SolvSRK / SolvJump. A reactor with y' = -kyy, a pendulum with sin(theta), Robertson kinetics - all nonlinear. Reach for SolvSRK (resonix_solvsrk): it takes a Python rhs(t, y), integrates nonlinear IVPs to the horizon. Nonlinear IVP -> solvsrk.run(cfg, ndim, t0, t_end, y0, rhs_fn=...). If A is linear but you need y(T) for hundreds of parameterizations and want the best shortlisted, that is a study, not a solve. SolvJump (resonix_solvjump) runs the sweep, caches a fast path, and writes receipts.
Walkthrough:
-
The one hard requirement: constant, linear A — SolvLRDE solves y' = A y + b where A and b are CONSTANT. That linearity is exactly what lets it skip time-stepping and integrate a contour instead. If your dynamics are nonlinear (y' = f(t, y) with y appearing nonlinearly) or A changes with time, SolvLRDE cannot represent the problem - there is no rhs-function entry point on purpose.
-
Nonlinear -> use SolvSRK — A reactor with y' = -kyy, a pendulum with sin(theta), Robertson kinetics - all nonlinear. Reach for SolvSRK (resonix_solvsrk): it takes a Python rhs(t, y), integrates nonlinear IVPs to the horizon. Nonlinear IVP -> solvsrk.run(cfg, ndim, t0, t_end, y0, rhs_fn=...)
-
Many parameter sets -> use SolvJump — If A is linear but you need y(T) for hundreds of parameterizations and want the best shortlisted, that is a study, not a solve. SolvJump (resonix_solvjump) runs the sweep, caches a fast path, and writes receipts.
-
Honest refusal on blow-up — Even within its domain, SolvLRDE reports failure instead of returning junk. A = [[1.0]] is y' = +y; at T=800 the exact answer exp(800) overflows double precision. The solver flags it (survived=False) rather than emitting inf.
survived: Truestatus: oky: -3.549e-12message: (none)
-
Summary — SolvLRDE: constant-A linear systems, any horizon, cost independent of T, immune to stiffness. SolvSRK: nonlinear / general stiff IVPs. SolvJump: parameter studies on top of either.
Guided tour output (from a verified run):
What SolvLRDE is for, and what belongs to SolvSRK / SolvJump
-- Act 1 - The one hard requirement: constant, linear A --
-> SolvLRDE solves y' = A y + b where A and b are CONSTANT. That linearity is exactly what lets it skip time-stepping and integrate a contour instead. If your dynamics are nonlinear (y' = f(t, y) with y appearing nonlinearly) or A changes with time, SolvLRDE cannot represent the problem - there is no rhs-function entry point on purpose.
-- Act 2 - Nonlinear -> use SolvSRK --
-> A reactor with y' = -k*y*y, a pendulum with sin(theta), Robertson kinetics - all nonlinear. Reach for SolvSRK (resonix_solvsrk): it takes a Python rhs(t, y), integrates nonlinear IVPs to the horizon. Nonlinear IVP -> solvsrk.run(cfg, ndim, t0, t_end, y0, rhs_fn=...)
-- Act 3 - Many parameter sets -> use SolvJump --
-> If A is linear but you need y(T) for hundreds of parameterizations and want the best shortlisted, that is a study, not a solve. SolvJump (resonix_solvjump) runs the sweep, caches a fast path, and writes receipts.
-- Act 4 - Honest refusal on blow-up --
-> Even within its domain, SolvLRDE reports failure instead of returning junk. A = [[1.0]] is y' = +y; at T=800 the exact answer exp(800) overflows double precision. The solver flags it (survived=False) rather than emitting inf.
survived: True
status: ok
y: -3.549e-12
message: (none)
-- Act 5 - Summary --
-> SolvLRDE: constant-A linear systems, any horizon, cost independent of T, immune to stiffness. SolvSRK: nonlinear / general stiff IVPs. SolvJump: parameter studies on top of either.
-- Run complete --
next_journey: (end of suite - see examples/PROGRESSION.md)cd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/06_limits.py
# Production one-liner — same API call you ship:
python examples/journeys/06_limits.pyExample bundle
Every journey is a domain scenario with narrated acts — run the guided tour first to see what each number means, then copy the production one-liner into your pipeline.
| Resource | Purpose |
|---|---|
PROGRESSION.md | Ordered runbook — journeys 01→06 with dual commands |
COVERAGE.md | Capability matrix — which APIs each journey exercises |
APPLICATIONS.md | Where the product applies in real programs |
run_examples.py | Interactive menu to launch any journey |
Guided journeys
| # | Script | Scenario |
|---|---|---|
| 01 | 01_first_contact.py | Version, license, and the smallest direct linear solve |
| 02 | 02_core_path.py | Solve y' = A y directly at a target time T |
| 03 | 03_configuration.py | Tune the Talbot contour: node count, nu, and contour shape |
| 04 | 04_time_independence.py | The headline property: cost does not grow with T |
| 05 | 05_banded_dae.py | Exploit sparsity, and reduce a linear DAE to canonical form |
| 06 | 06_limits.py | What SolvLRDE is for, and what belongs to SolvSRK / SolvJump |
Run from the python/ directory after install and license activation.
Set SOLVLRDE_QUIET=1 only when you want silent CLI runs (no narration).