// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: MIT package core import ( "math" "strings" "testing" ) // TestBesselJTabulated pins J against independently computed values // (mpmath, 30 significant digits) at points covering the power-series // branch, the downward Miller branch and both parity laws. func TestBesselJTabulated(t *testing.T) { cases := []struct { name string n int x float64 want float64 }{ {"J_0(0)", 0, 0, 1}, {"J_1(0)", 1, 0, 0}, {"J_0(1)", 0, 1, 0.765197686557966551}, {"J_1(1)", 1, 1, 0.440050585744933516}, {"J_2(1)", 2, 1, 0.11490348493190048}, {"J_5(1)", 5, 1, 0.000249757730211234431}, {"J_7(2)", 7, 2, 0.000174944074868274169}, {"J_0(5)", 0, 5, -0.177596771314338304}, {"J_3(4)", 3, 4, 0.43017147387562194}, {"J_2(10)", 2, 10, 0.254630313685120623}, {"J_0(15.5)", 0, 15.5, -0.109230650900050168}, {"J_2(15.5)", 2, 15.5, 0.130806545138985284}, {"J_0(25)", 0, 25, 0.0962667832759581162}, {"J_5(25)", 5, 25, -0.0660079953984229934}, {"J_1(30)", 1, 30, -0.118751062616622937}, {"J_1(-1)", 1, -1, -0.440050585744933516}, {"J_2(-1)", 2, -1, 0.11490348493190048}, {"J_-1(2)", -1, 2, -0.576724807756873387}, {"J_-3(-2)", -3, -2, 0.128943249474402051}, } for _, c := range cases { if got := BesselJ(c.n, c.x); math.Abs(got-c.want) > 1e-9*math.Abs(c.want) { t.Errorf("%s = %.16g, want %.16g", c.name, got, c.want) } } } // TestBesselYTabulated pins Y against independently computed values // (mpmath, 30 significant digits): the series seeds at small x, the // asymptotic seeds above the crossover and the upward recurrence // between them, plus the negative-order parity law. func TestBesselYTabulated(t *testing.T) { cases := []struct { name string n int x float64 want float64 }{ {"Y_0(0.05)", 0, 0.05, -1.97931100081720967}, {"Y_3(0.5)", 3, 0.5, -42.0594943047238827}, {"Y_0(1)", 0, 1, 0.088256964215676958}, {"Y_1(1)", 1, 1, -0.781212821300288717}, {"Y_2(1)", 2, 1, -1.65068260681625439}, {"Y_-2(1)", -2, 1, -1.65068260681625439}, {"Y_3(3)", 3, 3, -0.538541616105031618}, {"Y_5(2)", 5, 2, -9.93598912848197498}, {"Y_7(2)", 7, 2, -271.54802536799367}, {"Y_10(0.7)", 10, 0.7, -4244719426.07038669}, {"Y_0(10)", 0, 10, 0.0556711672835993914}, {"Y_0(15.5)", 0, 15.5, 0.170644911229434617}, {"Y_2(15.5)", 2, 15.5, -0.155833796066422704}, {"Y_5(15.5)", 5, 15.5, 0.204463657248615881}, {"Y_0(20)", 0, 20, 0.0626405968093838312}, {"Y_4(25)", 4, 25, -0.0910410709900928362}, {"Y_1(30)", 1, 30, 0.0844255706617472349}, {"Y_-1(3)", -1, 3, -0.324674424791799978}, } for _, c := range cases { got, err := BesselY(c.n, c.x) if err != nil { t.Errorf("%s: %v", c.name, err) continue } if math.Abs(got-c.want) > 1e-9*math.Abs(c.want) { t.Errorf("%s = %.16g, want %.16g", c.name, got, c.want) } } } // TestBesselJRecurrence checks the three-term recurrence // J_{ν−1} + J_{ν+1} = 2ν/x·J_ν (ν = 4) across both evaluation // branches, which any branch inconsistency would break. func TestBesselJRecurrence(t *testing.T) { for _, x := range []float64{1.5, 8, 15.5, 30} { nu := 4 jm1 := BesselJ(nu-1, x) j0 := BesselJ(nu, x) jp1 := BesselJ(nu+1, x) got := jm1 + jp1 want := 2 * float64(nu) / x * j0 if math.Abs(got-want) > 1e-9*math.Abs(want) { t.Errorf("x=%v: J_%d + J_%d = %.16g, want %.16g", x, nu-1, nu+1, got, want) } } } // TestBesselYRecurrence checks the same recurrence for the second // kind, tying the series seeds to the recurrence-climbed orders at // both small and large arguments. func TestBesselYRecurrence(t *testing.T) { for _, x := range []float64{0.7, 2, 15.5} { nu := 4 at := func(k int) float64 { v, err := BesselY(k, x) if err != nil { t.Fatalf("BesselY(%d, %v): %v", k, x, err) } return v } got := at(nu-1) + at(nu+1) want := 2 * float64(nu) / x * at(nu) if math.Abs(got-want) > 1e-9*math.Abs(want) { t.Errorf("x=%v: Y_%d + Y_%d = %.16g, want %.16g", x, nu-1, nu+1, got, want) } } } // TestBesselYRejects pins the domain contract: Yₙ is defined for // x > 0 only and reports the violation as an error with the package // prefix, never as a NaN. func TestBesselYRejects(t *testing.T) { for _, x := range []float64{0, -1, -1e-300, math.NaN()} { v, err := BesselY(2, x) if err == nil { t.Errorf("BesselY(2, %g): expected an error, got %v", x, v) } else if !strings.Contains(err.Error(), "tensor: BesselY") { t.Errorf("BesselY(2, %g): error %q lacks the prefixed name", x, err) } } if _, err := BesselY(2, 1); err != nil { t.Errorf("BesselY(2, 1): %v", err) } }