// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: MIT package integrate import ( "math" "testing" "sourcedock.dev/petrbalvin/tensor/internal/core" ) // TestFixedStepTimeGrid pins the exact time grid of the fixed-step // solvers: the stage times come from t0 + i·h, never from an // accumulated t += h, whose rounding walks over a long run. On // y' = cos t from t0 = 1e6 the walk used to hold the answer at an // error of 1.1e-8 that no step count refined away; the grid puts RK4 // at its own truncation floor and lets the first-order schemes show // their h. The tolerances sit an order below the old walk, so the pin // fails on an accumulated-time build. func TestFixedStepTimeGrid(t *testing.T) { const t0, span, steps = 1e6, 1e-1, 1e5 cos := func(t float64, y *core.Array) (*core.Array, error) { return core.FromFloats([]float64{math.Cos(t)}, 1) } exact := math.Sin(t0 + span) end, err := IntegrateRK4(cos, t0, t0+span, mustFloats(t, []float64{math.Sin(t0)}), steps) if err != nil { t.Fatalf("IntegrateRK4: %v", err) } if got := math.Abs(end.FloatAt(0) - exact); got > 1e-11 { t.Fatalf("RK4 error %.3e exceeds 1e-11: the stage times have left the exact grid", got) } end, err = IntegrateBackwardEuler(cos, t0, t0+span, mustFloats(t, []float64{math.Sin(t0)}), 1e6, ODEOptions{}) if err != nil { t.Fatalf("IntegrateBackwardEuler: %v", err) } if got := math.Abs(end.FloatAt(0) - exact); got > 5e-9 { t.Fatalf("backward Euler error %.3e exceeds 5e-9: the step times have left the exact grid", got) } }