Files
tensor/example_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

309 lines
9.2 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 tensor_test
// The godoc examples: every flagship workflow as a runnable, checked
// snippet. pkg.go.dev renders these beside the API, and `go test`
// executes them, so the documentation cannot rot.
import (
"fmt"
"log"
"math"
tensor "sourcedock.dev/petrbalvin/tensor"
grad "sourcedock.dev/petrbalvin/tensor/grad"
)
// A rank-1 array from literals, element-wise arithmetic, a reduction.
func ExampleAdd() {
a, err := tensor.FromFloats([]float64{1, 2, 3, 4}, 4)
if err != nil {
log.Fatal(err)
}
b, _ := tensor.FromFloats([]float64{10, 20, 30, 40}, 4)
sum, _ := tensor.Add(a, b)
mean, _ := tensor.Mean(sum)
fmt.Println(sum, mean)
// Output: float (4) [11, 22, 33, 44] 27.5
}
// The 2-D matrix product.
func ExampleMatMul2D() {
a, _ := tensor.FromFloats([]float64{1, 2, 3, 4}, 2, 2)
b, _ := tensor.FromFloats([]float64{5, 6, 7, 8}, 2, 2)
prod, err := tensor.MatMul2D(a, b)
if err != nil {
log.Fatal(err)
}
fmt.Println(prod)
// Output: float (2, 2) [19, 22, 43, 50]
}
// Einstein summation, batched over an ellipsis axis.
func ExampleEinsum() {
a, _ := tensor.FromFloats([]float64{1, 2, 3, 4, 5, 6, 7, 8}, 2, 2, 2)
b, _ := tensor.FromFloats([]float64{1, 0, 0, 1, 1, 0, 0, 1}, 2, 2, 2)
// Batched matrix product: each batch times its identity.
got, err := tensor.Einsum("...ij,...jk->...ik", a, b)
if err != nil {
log.Fatal(err)
}
fmt.Println(got.Shape(), got.FloatAt(0), got.FloatAt(3))
// Output: [2 2 2] 1 4
}
// Reverse-mode autograd: the gradient of Σ (x·w)² arrives exact.
func ExampleTensor_Backward() {
x, _ := grad.FromFloat64s([]float64{1, 2, 3}, true, 3)
w, _ := grad.FromFloat64s([]float64{0.5, -1, 2}, false, 3)
prod, _ := x.Mul(w)
sq, _ := prod.Pow(2)
loss, _ := sq.Sum()
if err := loss.Backward(); err != nil {
log.Fatal(err)
}
// dL/dx = 2·x·w², non-negative because w enters squared.
g := x.Grad()
fmt.Printf("%.4g %.4g %.4g\n", g.FloatAt(0), g.FloatAt(1), g.FloatAt(2))
// Output: 0.5 4 24
}
// Ordinary least squares with the full classical inference.
func ExampleLinearRegression() {
// y = 2 + 3x on x = 0..5, the intercept column first.
design, _ := tensor.FromFloats([]float64{
1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5,
}, 6, 2)
y, _ := tensor.FromFloats([]float64{2.1, 4.9, 8.2, 11.1, 13.8, 17.2}, 6)
res, err := tensor.LinearRegression(design, y)
if err != nil {
log.Fatal(err)
}
fmt.Printf("slope %.3f ± %.3f, R2 %.4f\n",
res.Coefficients[1], res.StandardErrors[1], res.RSquared)
// Output: slope 3.003 ± 0.044, R2 0.9991
}
// Solving an initial value problem: the exponential decay y' = −y.
func ExampleIntegrateODE() {
f := func(t float64, y *tensor.Array) (*tensor.Array, error) {
return tensor.MulF(y, -1), nil
}
y0, _ := tensor.FromFloats([]float64{1}, 1)
end, err := tensor.IntegrateODE(f, 0, 1, y0, tensor.ODEOptions{})
if err != nil {
log.Fatal(err)
}
fmt.Printf("y(1) = %.6f\n", end.FloatAt(0))
// Output: y(1) = 0.367880
}
// The globally adaptive cubature over a box: the 2-D Gaussian.
func ExampleIntegrateND() {
got, err := tensor.IntegrateND(func(x []float64) float64 {
return math.Exp(-x[0]*x[0] - x[1]*x[1])
}, []float64{-3, -3}, []float64{3, 3}, tensor.CubatureOptions{Tolerance: 1e-11})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.6f\n", got)
// Output: 3.141454
}
// The Crank-Nicolson heat equation holds its eigenmode shape while it
// decays.
func ExampleIntegrateHeat1D() {
const (
n = 49
kappa = 0.1
dx = 1.0 / 50
)
u0 := make([]float64, n)
for i := range n {
u0[i] = math.Sin(math.Pi * float64(i+1) * dx)
}
u0Arr, _ := tensor.FromFloats(u0, n)
states, err := tensor.IntegrateHeat1D(u0Arr, kappa, dx, 1.0, 0.001, 2, 0, 0)
if err != nil {
log.Fatal(err)
}
last := (states.Shape()[0] - 1) * n
// The mode's centre started at 1 and decays by exp(−κπ²t).
decay := math.Exp(-kappa * math.Pi * math.Pi)
fmt.Printf("decay %.4f, centre %.4f\n", decay, states.FloatAt(last+n/2))
// Output: decay 0.3727, centre 0.3728
}
// The Lanczos eigensolver on a sparse symmetric matrix.
func ExampleSpEigen() {
dense, _ := tensor.FromFloats([]float64{2, 1, 1, 2}, 2, 2)
sp, _ := tensor.SparseFrom(dense)
vals, _, err := tensor.SpEigen(sp, 2, tensor.NewGenerator(7))
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.6f %.6f\n", vals.FloatAt(0), vals.FloatAt(1))
// Output: 3.000000 1.000000
}
// The complete elliptic integral of the first kind by the AGM.
func ExampleEllipticK() {
m, _ := tensor.FromFloats([]float64{0, 0.5}, 2)
k, err := tensor.EllipticK(m)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.10f %.10f\n", k.FloatAt(0), k.FloatAt(1))
// Output: 1.5707963268 1.8540746773
}
// Haar wavelets turn an off-grid step into one detail coefficient,
// and the inverse restores the signal exactly.
func ExampleDWT() {
vals := make([]float64, 16)
for i := 5; i < 16; i++ {
vals[i] = 1
}
x, _ := tensor.FromFloats(vals, 16)
c, err := tensor.DWT(x, 1)
if err != nil {
log.Fatal(err)
}
restored, _ := tensor.IDWT(c, 1)
ok := math.Abs(restored.FloatAt(7)-1) < 1e-12 && restored.FloatAt(4) == 0
fmt.Printf("detail[2] = %.4f, restored: %v\n", c.FloatAt(10), ok)
// Output: detail[2] = -0.7071, restored: true
}
// The autocorrelation of a periodic signal is itself periodic.
func ExampleAutocorrelate() {
vals := make([]float64, 64)
for i := range vals {
vals[i] = math.Cos(2 * math.Pi * float64(i) / 16)
}
x, _ := tensor.FromFloats(vals, 64)
acf, err := tensor.Autocorrelate(x, 16)
if err != nil {
log.Fatal(err)
}
// The biased normalisation tapers the tail: 48/64 of the pairs
// overlap at lag 16.
fmt.Printf("acf(0) = %.4f, acf(16) = %.4f\n", acf.FloatAt(0), acf.FloatAt(16))
// Output: acf(0) = 1.0000, acf(16) = 0.7500
}
// Differential evolution crosses Rastrigin's minefield of local
// minima to the global one.
func ExampleMinimiseDifferentialEvolution() {
lower, _ := tensor.FromFloats([]float64{-5.12, -5.12}, 2)
upper, _ := tensor.FromFloats([]float64{5.12, 5.12}, 2)
_, fv, err := tensor.MinimiseDifferentialEvolution(func(a *tensor.Array) (float64, error) {
s := 0.0
for i := range 2 {
z := a.FloatAt(i)
s += z*z - 10*math.Cos(2*math.Pi*z) + 10
}
return s, nil
}, lower, upper, tensor.DifferentialEvolutionOptions{Generations: 800})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.1e\n", fv)
// Output: 0.0e+00
}
// Slicing a contiguous selection returns a read-only view sharing the
// storage; an interior range is copied.
func ExampleSlice() {
m, _ := tensor.FromFloats([]float64{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
}, 3, 4)
// Whole rows: a view.
rows, err := tensor.Slice(m, 0, 1, 3)
if err != nil {
log.Fatal(err)
}
// An interior column range: a copy.
block, _ := tensor.Slice(rows, 1, 1, 3)
fmt.Println(rows.Shape(), rows.FloatAt(0), block.Shape(), block.FloatAt(0))
// Output: [2 4] 5 [2 2] 6
}
// A size-1 dimension replicates over the target, and anything that
// would not is an error rather than a silent broadcast.
func ExampleBroadcastTo() {
col, _ := tensor.FromFloats([]float64{1, 2, 3}, 3, 1)
wide, err := tensor.BroadcastTo(col, 3, 4)
if err != nil {
log.Fatal(err)
}
_, bad := tensor.BroadcastTo(col, 2, 4)
fmt.Println(wide)
fmt.Println("refused:", bad != nil)
// Output: float (3, 4) [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]
// refused: true
}
// The gamma function, including the negative half line.
func ExampleGamma() {
x, _ := tensor.FromFloats([]float64{-0.5, 0.5, 5}, 3)
g, err := tensor.Gamma(x)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.7f %.7f %.1f\n", g.FloatAt(0), g.FloatAt(1), g.FloatAt(2))
// Output: -3.5449077 1.7724539 24.0
}
// Linear interpolation clamps outside the sampled range; the monotone
// cubic passes through the same knots with a bounded slope.
func ExampleInterpolate() {
xs, _ := tensor.FromFloats([]float64{0, 1, 3}, 3)
ys, _ := tensor.FromFloats([]float64{0, 2, 2.5}, 3)
query, _ := tensor.FromFloats([]float64{0.5, 2, 5}, 3)
lin, err := tensor.Interpolate(xs, ys, query)
if err != nil {
log.Fatal(err)
}
mono, _ := tensor.InterpolateMonotone(xs, ys, query)
// Both stay inside the bracketing samples, and the tail clamps to
// the last knot.
fmt.Printf("linear %.4f %.4f %.4f\n", lin.FloatAt(0), lin.FloatAt(1), lin.FloatAt(2))
fmt.Printf("pchip %.4f %.4f %.4f\n", mono.FloatAt(0), mono.FloatAt(1), mono.FloatAt(2))
// Output: linear 1.0000 2.2500 2.5000
// pchip 1.2621 2.3716 2.5000
}
// The Halton sequence stratifies progressively in every base; the
// index-zero origin is dropped, as it carries no information.
func ExampleHaltonPoints() {
pts, err := tensor.HaltonPoints(3, 2, 0)
if err != nil {
log.Fatal(err)
}
fmt.Println(pts.Shape())
for i := range 3 {
fmt.Printf("(%.4f, %.4f) ", pts.FloatAt(2*i), pts.FloatAt(2*i+1))
}
fmt.Println()
// Output: [3 2]
// (0.5000, 0.3333) (0.2500, 0.6667) (0.7500, 0.1111)
}
// A fixed seed replays the same stream, which is what makes a random
// workflow testable.
func ExampleNewGenerator() {
u, err := tensor.Floats(tensor.NewGenerator(7), 3)
if err != nil {
log.Fatal(err)
}
again, _ := tensor.Floats(tensor.NewGenerator(7), 3)
fmt.Printf("%.6f %.6f %.6f, equal: %v\n",
u.FloatAt(0), u.FloatAt(1), u.FloatAt(2), tensor.Equal(u, again))
// Output: 0.055360 0.172116 0.717576, equal: true
}