Files
tensor/integrate/cubature_test.go
T
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

115 lines
3.8 KiB
Go
Raw 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 integrate
import (
"math"
"testing"
)
// TestCubatureGaussian pins the 2-D Gaussian against its exact box
// value π·erf(3)²; the infinite-domain π is not what a box integral
// returns.
func TestCubatureGaussian(t *testing.T) {
got, err := IntegrateND(func(x []float64) float64 {
return math.Exp(-x[0]*x[0] - x[1]*x[1])
}, []float64{-3, -3}, []float64{3, 3}, CubatureOptions{Tolerance: 1e-11})
if err != nil {
t.Fatalf("IntegrateND: %v", err)
}
want := math.Pi * math.Erf(3) * math.Erf(3)
if math.Abs(got-want) > 1e-9 {
t.Fatalf("∫∫e^{-r²} = %.12f, want %.12f", got, want)
}
}
// TestCubaturePolynomials pins exactness on products of polynomials.
func TestCubaturePolynomials(t *testing.T) {
got, err := IntegrateND(func(x []float64) float64 {
return x[0] * x[0] * x[1]
}, []float64{0, 0}, []float64{1, 1}, CubatureOptions{})
if err != nil {
t.Fatalf("IntegrateND: %v", err)
}
if math.Abs(got-1.0/6.0) > 1e-13 {
t.Fatalf("∫x²y = %.14f, want 1/6", got)
}
// 3-D volume of the unit cube shifted.
got3, err := IntegrateND(func(x []float64) float64 { return 1 },
[]float64{1, 2, 3}, []float64{3, 5, 7}, CubatureOptions{})
if err != nil {
t.Fatalf("IntegrateND: %v", err)
}
if math.Abs(got3-24) > 1e-12 {
t.Fatalf("volume = %.12f, want 24", got3)
}
}
// TestCubaturePeaked pins adaptivity: a sharp ridge that uniform
// refinement would crawl on, checked against a dense product Simpson.
func TestCubaturePeaked(t *testing.T) {
f := func(x []float64) float64 {
d2 := (x[0] - 0.4) * (x[0] - 0.4)
d2 += (x[1] - 0.6) * (x[1] - 0.6)
return 1 / (0.003 + d2)
}
got, err := IntegrateND(f, []float64{0, 0}, []float64{1, 1}, CubatureOptions{Tolerance: 1e-9})
if err != nil {
t.Fatalf("IntegrateND: %v", err)
}
// Reference: 800×800 composite midpoint product.
const n = 800
h := 1.0 / n
ref := 0.0
for i := range n {
for j := range n {
ref += h * h * f([]float64{(float64(i) + 0.5) * h, (float64(j) + 0.5) * h})
}
}
if math.Abs(got-ref) > 2e-4*ref {
t.Fatalf("peaked integral = %.8f, reference %.8f", got, ref)
}
}
// TestCubatureMatches1D pins the degenerate dimension against the
// one-dimensional adaptive quadrature.
func TestCubatureMatches1D(t *testing.T) {
f := func(x float64) float64 { return math.Exp(-x) * math.Cos(3*x) }
got, err := IntegrateND(func(x []float64) float64 { return f(x[0]) },
[]float64{0}, []float64{5}, CubatureOptions{Tolerance: 1e-12})
if err != nil {
t.Fatalf("IntegrateND: %v", err)
}
ref, _, err := IntegrateFunction(func(x float64) (float64, error) { return f(x), nil },
0, 5, QuadratureOptions{})
if err != nil {
t.Fatalf("IntegrateFunction: %v", err)
}
if math.Abs(got-ref) > 1e-9 {
t.Fatalf("1-D degenerate = %.12f, quadrature says %.12f", got, ref)
}
}
// TestCubatureErrors pins the input gates.
func TestCubatureErrors(t *testing.T) {
if _, err := IntegrateND(func(x []float64) float64 { return 0 },
[]float64{}, []float64{}, CubatureOptions{}); err == nil {
t.Error("empty bounds accepted")
}
if _, err := IntegrateND(func(x []float64) float64 { return 0 },
[]float64{1, 0}, []float64{0, 1}, CubatureOptions{}); err == nil {
t.Error("reversed edge accepted")
}
if _, err := IntegrateND(func(x []float64) float64 { return math.NaN() },
[]float64{0}, []float64{1}, CubatureOptions{}); err == nil {
t.Error("non-finite integrand accepted")
}
// The budget only bites when refinement is actually needed, so the
// integrand must carry an error estimate a constant cannot.
if _, err := IntegrateND(func(x []float64) float64 { return math.Sin(x[0] * x[1]) },
[]float64{0, 0, 0}, []float64{1, 1, 1}, CubatureOptions{MaxEvals: 1}); err == nil {
t.Error("exhausted budget accepted")
}
}