Files
tensor/internal/core/special2_test.go
petrbalvin af4ee19703
Release / gates (push) Successful in 4m38s
Test / test (push) Successful in 5m16s
Release / release (push) Successful in 35s
feat: initial release
Assisted-by: GLM 5.3 Flash
2026-09-03 10:00:00 +02:00

145 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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)
}
}