This commit is contained in:
Anatoly Antonov 2026-05-18 03:17:17 +09:00
parent 7a8d5d13fa
commit 9e663db9dc
68 changed files with 5647 additions and 2958 deletions

View file

@ -0,0 +1,61 @@
package numerics
import (
"math"
"testing"
)
// scalarAdd / scalarLerp let us drive RK4 on a plain float64.
func scalarAdd(y float64, k float64, dy float64) float64 { return y + k*dy }
func scalarLerpF(a, b float64, l float64) float64 { return Lerp(a, b, l) }
func TestRK4ExponentialDecay(t *testing.T) {
// dy/dt = -y → exact: y(t) = y0 * exp(-t).
deriv := func(_ float64, y float64) float64 { return -y }
y := 1.0
tnow := 0.0
dt := 0.01
for range 100 {
y = RK4Step(tnow, y, dt, deriv, scalarAdd)
tnow += dt
}
want := math.Exp(-1.0)
if math.Abs(y-want) > 1e-8 {
t.Errorf("RK4 exp decay at t=1: got %v, want %v (diff %v)", y, want, y-want)
}
}
func TestRK4ReverseTime(t *testing.T) {
// dy/dt = y → exact: y(t) = y0 * exp(t).
// Integrating from t=1 backwards with dt=-0.01 over 100 steps should give y0.
deriv := func(_ float64, y float64) float64 { return y }
y := math.E
tnow := 1.0
dt := -0.01
for range 100 {
y = RK4Step(tnow, y, dt, deriv, scalarAdd)
tnow += dt
}
if math.Abs(y-1.0) > 1e-8 {
t.Errorf("RK4 reverse: got %v, want 1.0 (diff %v)", y, y-1.0)
}
}
func TestRefineTrigger(t *testing.T) {
// y crosses 0 at l=0.4 between y1=1 and y2=-1.5.
y1, y2 := 1.0, -1.5
t1, t2 := 0.0, 1.0
trig := func(_ float64, y float64) bool { return y <= 0 }
tr, yr := RefineTrigger(t1, y1, t2, y2, trig, scalarLerpF, 0.001)
// The exact crossing is at l = 1/(1+1.5) = 0.4 → t = 0.4, y = 0.
if math.Abs(tr-0.4) > 0.01 {
t.Errorf("Refined t = %v, want ~0.4", tr)
}
if math.Abs(yr) > 0.01 {
t.Errorf("Refined y = %v, want ~0", yr)
}
}