feat: initial release
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestModifiedBesselTabulated pins I and K against tabulated values
|
||||
// at x = 1, the standard reference point.
|
||||
func TestModifiedBesselTabulated(t *testing.T) {
|
||||
x := mustFloats(t, []float64{1})
|
||||
i0, _ := BesselI0(x)
|
||||
i1, _ := BesselI1(x)
|
||||
k0, _ := BesselK0(x)
|
||||
k1, _ := BesselK1(x)
|
||||
want := []struct {
|
||||
name string
|
||||
got float64
|
||||
ref float64
|
||||
}{
|
||||
{"I0(1)", i0.FloatAt(0), 1.2660658777520084},
|
||||
{"I1(1)", i1.FloatAt(0), 0.5651591039924850},
|
||||
{"K0(1)", k0.FloatAt(0), 0.4210244382407083},
|
||||
{"K1(1)", k1.FloatAt(0), 0.6019072301972346},
|
||||
}
|
||||
for _, w := range want {
|
||||
if math.Abs(w.got-w.ref) > 5e-7*(1+math.Abs(w.ref)) {
|
||||
t.Fatalf("%s = %.16g, want %.16g", w.name, w.got, w.ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestModifiedBesselSymmetry checks the parity and small-argument
|
||||
// behaviour: I₀ even with I₀(0) = 1, I₁ odd with I₁(0) = 0, and the
|
||||
// K divergence to +Inf at the origin.
|
||||
func TestModifiedBesselSymmetry(t *testing.T) {
|
||||
x := mustFloats(t, []float64{-1, 1})
|
||||
i0, _ := BesselI0(x)
|
||||
if math.Abs(i0.FloatAt(0)-i0.FloatAt(1)) > 1e-14 {
|
||||
t.Fatalf("I₀ must be even: %v vs %v", i0.FloatAt(0), i0.FloatAt(1))
|
||||
}
|
||||
i1, _ := BesselI1(x)
|
||||
if math.Abs(i1.FloatAt(0)+i1.FloatAt(1)) > 1e-14 {
|
||||
t.Fatalf("I₁ must be odd: %v vs %v", i1.FloatAt(0), i1.FloatAt(1))
|
||||
}
|
||||
i1At0, err := BesselI1(mustFloats(t, []float64{0}))
|
||||
if err != nil {
|
||||
t.Fatalf("BesselI1(0): %v", err)
|
||||
}
|
||||
if i1At0.FloatAt(0) != 0 {
|
||||
t.Fatalf("I₁(0) = %v, want 0", i1At0.FloatAt(0))
|
||||
}
|
||||
k0, err := BesselK0(mustFloats(t, []float64{0}))
|
||||
if err != nil {
|
||||
t.Fatalf("BesselK0(0): %v", err)
|
||||
}
|
||||
if !math.IsInf(k0.FloatAt(0), 1) {
|
||||
t.Fatalf("K₀(0) = %v, want +Inf", k0.FloatAt(0))
|
||||
}
|
||||
}
|
||||
|
||||
// TestModifiedBesselWronskian checks the Wronskian identity
|
||||
// I₀·K₁ + I₁·K₀ = 1/x, which couples the two kinds at every point.
|
||||
func TestModifiedBesselWronskian(t *testing.T) {
|
||||
x := mustFloats(t, []float64{0.1, 0.5, 1, 5, 20})
|
||||
i0, _ := BesselI0(x)
|
||||
i1, _ := BesselI1(x)
|
||||
k0, _ := BesselK0(x)
|
||||
k1, _ := BesselK1(x)
|
||||
for i := range x.Len() {
|
||||
xv := x.FloatAt(i)
|
||||
w := i0.FloatAt(i)*k1.FloatAt(i) + i1.FloatAt(i)*k0.FloatAt(i)
|
||||
if math.Abs(w-1/xv) > 1e-6*(1/xv) {
|
||||
t.Fatalf("x=%v: wronskian = %.12g, want %.12g", xv, w, 1/xv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBesselRecurrences checks the three-term recurrences linking
|
||||
// consecutive integer orders, for both kinds.
|
||||
func TestBesselRecurrences(t *testing.T) {
|
||||
x := mustFloats(t, []float64{1, 4, 9})
|
||||
const n = 3
|
||||
in, err := BesselIn(n, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselIn: %v", err)
|
||||
}
|
||||
inm1, err := BesselIn(n-1, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselIn(n−1): %v", err)
|
||||
}
|
||||
inm2, err := BesselIn(n-2, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselIn(n−2): %v", err)
|
||||
}
|
||||
kn, err := BesselKn(n, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselKn: %v", err)
|
||||
}
|
||||
knm1, err := BesselKn(n-1, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselKn(n−1): %v", err)
|
||||
}
|
||||
knm2, err := BesselKn(n-2, x)
|
||||
if err != nil {
|
||||
t.Fatalf("BesselKn(n−2): %v", err)
|
||||
}
|
||||
for i := range x.Len() {
|
||||
xv := x.FloatAt(i)
|
||||
// I_{n−1} − I_{n+1} = 2n/x·I_n with n − 1 in the call.
|
||||
got := inm2.FloatAt(i) - in.FloatAt(i)
|
||||
want := 2 * float64(n-1) / xv * inm1.FloatAt(i)
|
||||
if math.Abs(got-want) > 1e-6*(1+math.Abs(want)) {
|
||||
t.Fatalf("I recurrence at %v: %v, want %v", xv, got, want)
|
||||
}
|
||||
// K_{n+1} = K_{n−1} + 2n/x·K_n with n − 1 in the call.
|
||||
got = knm2.FloatAt(i) + 2*float64(n-1)/xv*knm1.FloatAt(i)
|
||||
if math.Abs(got-kn.FloatAt(i)) > 1e-6*(1+math.Abs(kn.FloatAt(i))) {
|
||||
t.Fatalf("K recurrence at %v: %v, want %v", xv, got, kn.FloatAt(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestModifiedBesselRejects pins the input contracts: negative orders
|
||||
// are refused for both integer-order entry points, and K diverges to
|
||||
// +Inf at the origin on the diagonal of its domain.
|
||||
func TestModifiedBesselRejects(t *testing.T) {
|
||||
x := mustFloats(t, []float64{1})
|
||||
if _, err := BesselIn(-1, x); err == nil {
|
||||
t.Fatal("expected an error for a negative I order")
|
||||
}
|
||||
if _, err := BesselKn(-1, x); err == nil {
|
||||
t.Fatal("expected an error for a negative K order")
|
||||
}
|
||||
k0, err := BesselK0(mustFloats(t, []float64{0}))
|
||||
if err != nil {
|
||||
t.Fatalf("BesselK0(0): %v", err)
|
||||
}
|
||||
if !math.IsInf(k0.FloatAt(0), 1) {
|
||||
t.Fatalf("K₀(0) = %v, want +Inf", k0.FloatAt(0))
|
||||
}
|
||||
k1, err := BesselK1(mustFloats(t, []float64{0}))
|
||||
if err != nil {
|
||||
t.Fatalf("BesselK1(0): %v", err)
|
||||
}
|
||||
if !math.IsInf(k1.FloatAt(0), 1) {
|
||||
t.Fatalf("K₁(0) = %v, want +Inf", k1.FloatAt(0))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user