1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
;;; Copyright (c) 2026 Nicholas Vermeulen
;;; SPDX-License-Identifier: AGPL-3.0-or-later
;; ode.lisp — fixed-step ODE integrators with VERIFIED trajectory invariants.
;; Pure Lisp, zero interpreter changes. robot.lisp's inductive-safety story for
;; a discrete controller, carried to a continuous-ish plant: an ODE integrated
;; at a FIXED step is a deterministic discrete map, so a property that holds
;; "after N steps for every initial condition in a finite grid" is provable by
;; `check-exhaustive` — ran everywhere on the declared IC×step grid, never
;; sampled.
;;
;; State is a VECTOR (a list of numbers); the right-hand side is a pure
;; `f(t, y) -> dy/dt` returning a vector the same length. Two steppers:
;; forward `ode-euler` and 4th-order `ode-rk4`. `trajectory` records every
;; state; `ode-solve` returns just the final one.
;;
;; CLAIM DISCIPLINE — this is the load-bearing caveat. Every claim is over the
;; DISCRETE semantics of a fixed step h on a declared finite domain:
;; "after N steps of THIS stepper, property P holds for all IC in grid D."
;; NEVER "converges to the true solution as h -> 0" (an unbounded limit claim
;; this module does not and cannot make), and NEVER "stable" / "accurate" in
;; general. A closed-form match is asserted only WITHIN a declared epsilon on a
;; declared grid. Deterministic: integers/rationals in, IEEE f64 out; the only
;; libm call (`exp`) appears solely inside an epsilon-comparison, never in
;; printed output, so a 1-ULP platform difference cannot move a verdict.
;; ── Vector state algebra ──────────────────────────────────────────────────
;; (Rusty `map` is single-list only, so a two-list zip is hand-written, same
;; as units.lisp's dim-map2 / csp.lisp's csp-zip.)
;; ── Steppers: (stepper f t y h) -> next state ─────────────────────────────
;; ── Trajectory recorder ───────────────────────────────────────────────────
;; Returns the list of n+1 states (y0, y1, …, yn). Tail-recursive by hand:
;; std `take`/naive recursion overflow past a few thousand elements, and a long
;; integration is exactly that many steps.
;; ── Trajectory invariants (predicates over a recorded run) ────────────────
;; Every recorded state satisfies pred.
;; Every component of every state stays within [-B, B].
;; Energy of the unit harmonic oscillator y=(position velocity): ½(p²+v²).
;; f-ho below is its RHS; conserved by the exact flow, approximately by RK4,
;; and GROWN by forward Euler — the module's canonical verified/refuted pair.
; p'=v, v'=-p
; y'=-y (scalar in a 1-vector)
;; ── Exhaustive verification over IC × step grids ──────────────────────────
;; Each helper returns a real `check-exhaustive` result: 'verified means the
;; property held at EVERY grid point; a counterexample is ((args…) reason),
;; and for these boolean properties the failing IC/step IS the witness.
;; (1) Box stability of a SCALAR decay y'=-y under forward Euler: does the
;; trajectory stay within [-B,B] for every (y0, h) in the grid? Euler here is
;; y_{k+1} = (1-h)·y_k, so |1-h|>1 (a too-large step) escapes any box — the
;; witness is the first (y0,h) that leaves it. Property takes (y0 h).
;; (2) Energy stays within a relative band [lo·E0, hi·E0] for the whole run,
;; for every 2-D IC (p0,v0) in the grid. RK4 → 'verified (tiny drift); Euler →
;; the first IC with E0>0 as counterexample (it grows past the band). A
;; zero-energy IC is vacuously in band. Property takes (p0 v0).
;; (3) The RK4 final state matches a closed-form solution within eps, for every
;; scalar IC in the grid. `closed` maps y0 -> the exact value at T = h·n. This
;; is the ONLY place the true solution enters, and only inside |·|<eps — never
;; a claim of convergence, just "within eps on this grid at this step". y0 arg.