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

204 lines
5.4 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 linalg_test
// Runnable examples for the flagship workflows of the package. `go
// test` executes them against the Output comments below, so the
// documentation cannot drift from the code.
import (
"fmt"
"log"
"math"
"sourcedock.dev/petrbalvin/tensor"
"sourcedock.dev/petrbalvin/tensor/linalg"
)
// A dense solve, and the same matrix factored once for reuse.
func ExampleSolve() {
a, err := tensor.FromFloats([]float64{4, 1, 1, 3}, 2, 2)
if err != nil {
log.Fatal(err)
}
b, err := tensor.FromFloats([]float64{1, 2}, 2)
if err != nil {
log.Fatal(err)
}
x, err := linalg.Solve(a, b)
if err != nil {
log.Fatal(err)
}
fmt.Printf("x = %.4f %.4f\n", x.FloatAt(0), x.FloatAt(1))
// The same matrix through Cholesky: a = L·Lᵀ, one factor for any
// number of right-hand sides.
l, err := linalg.Cholesky(a)
if err != nil {
log.Fatal(err)
}
fmt.Printf("L = [%.1f %.1f; %.1f %.4f]\n",
l.FloatAt(0), l.FloatAt(1), l.FloatAt(2), l.FloatAt(3))
// Output:
// x = 0.0909 0.6364
// L = [2.0 0.0; 0.5 1.6583]
}
// A symmetric eigenproblem: ascending eigenvalues and orthonormal
// eigenvector columns.
func ExampleEigen() {
a, err := tensor.FromFloats([]float64{1, 2, 2, 1}, 2, 2)
if err != nil {
log.Fatal(err)
}
values, vectors, err := linalg.Eigen(a)
if err != nil {
log.Fatal(err)
}
fmt.Printf("values = %.4f %.4f\n", values.FloatAt(0), values.FloatAt(1))
// The sign of an eigenvector is arbitrary, so the magnitudes are
// what a stable example can print. Here |V[i][j]| = 1/√2.
fmt.Printf("|V| = [%.4f %.4f; %.4f %.4f]\n",
math.Abs(vectors.FloatAt(0)), math.Abs(vectors.FloatAt(1)),
math.Abs(vectors.FloatAt(2)), math.Abs(vectors.FloatAt(3)))
// Output:
// values = -1.0000 3.0000
// |V| = [0.7071 0.7071; 0.7071 0.7071]
}
// A matrix function: the exponential of a rotation generator is the
// rotation itself.
func ExampleMatrixExp() {
// A = [[0, -1], [1, 0]] generates a quarter-turn rotation rate, so
// exp(A) = [[cos 1, -sin 1], [sin 1, cos 1]].
a, err := tensor.FromFloats([]float64{0, -1, 1, 0}, 2, 2)
if err != nil {
log.Fatal(err)
}
e, err := linalg.MatrixExp(a)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.4f %.4f %.4f %.4f\n",
e.FloatAt(0), e.FloatAt(1), e.FloatAt(2), e.FloatAt(3))
// Output: 0.5403 -0.8415 0.8415 0.5403
}
// A sparse symmetric positive definite solve by conjugate gradient.
func ExampleSpSolve() {
a := laplacian1D()
// The right-hand side A·1, so the exact solution is the vector of
// ones and the printed digits show the residual the iteration left.
b, err := tensor.FromFloats([]float64{1, 0, 0, 1}, 4)
if err != nil {
log.Fatal(err)
}
x, err := linalg.SpSolve(a, b, 0, 0)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.4f %.4f %.4f %.4f\n",
x.FloatAt(0), x.FloatAt(1), x.FloatAt(2), x.FloatAt(3))
// Output: 1.0000 1.0000 1.0000 1.0000
}
// A sparse direct factorisation under a fill-reducing ordering.
func ExampleNewSparseCholesky() {
a := laplacian1D()
b, err := tensor.FromFloats([]float64{1, 0, 0, 1}, 4)
if err != nil {
log.Fatal(err)
}
f, err := linalg.NewSparseCholesky(a, linalg.SparseOrderingReverseCuthillMcKee)
if err != nil {
log.Fatal(err)
}
x, err := f.Solve(b)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.4f %.4f %.4f %.4f\n",
x.FloatAt(0), x.FloatAt(1), x.FloatAt(2), x.FloatAt(3))
// The factor of a tridiagonal matrix keeps the band: 4 diagonal
// entries and 3 subdiagonal ones.
fmt.Println("factor non-zeros:", f.NNZ())
// Output:
// 1.0000 1.0000 1.0000 1.0000
// factor non-zeros: 7
}
// A natural cubic spline through samples of a straight line.
func ExampleNewCubicSpline() {
xs, err := tensor.FromFloats([]float64{0, 1, 2}, 3)
if err != nil {
log.Fatal(err)
}
ys, err := tensor.FromFloats([]float64{1, 3, 5}, 3)
if err != nil {
log.Fatal(err)
}
s, err := linalg.NewCubicSpline(xs, ys)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.4f %.4f\n", s.At(0.5), s.At(1.5))
// Extrapolation has no boundary condition here, so it is undefined.
fmt.Printf("%.4f\n", s.At(2.5))
// Output:
// 2.0000 4.0000
// NaN
}
// A sequence of element-wise steps read as one expression.
func ExamplePipe() {
a, err := tensor.FromFloats([]float64{1, 2, 3}, 3)
if err != nil {
log.Fatal(err)
}
out, err := linalg.Pipe(a).AddF(1).Sqrt().MulF(2).Result()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%.4f %.4f %.4f\n", out.FloatAt(0), out.FloatAt(1), out.FloatAt(2))
// A step that fails is recorded, and the steps after it are no-ops:
// adding a length-2 array to a length-3 one is a shape error.
short, err := tensor.FromFloats([]float64{1, 2}, 2)
if err != nil {
log.Fatal(err)
}
_, err = linalg.Pipe(a).Add(short).MulF(2).Result()
fmt.Println("error:", err != nil)
// Output:
// 2.8284 3.4641 4.0000
// error: true
}
// laplacian1D builds the 4×4 second-difference matrix with -1 on both
// off-diagonals and 2 on the diagonal, as a coordinate matrix.
func laplacian1D() *tensor.SparseCOO {
indices, err := tensor.FromInts([]int64{
0, 0, 1, 0, 0, 1, 1, 1,
2, 1, 1, 2, 2, 2, 3, 2,
2, 3, 3, 3,
}, 10, 2)
if err != nil {
panic(err)
}
values, err := tensor.FromFloats([]float64{
2, -1, -1, 2, -1, -1, 2, -1, -1, 2,
}, 10)
if err != nil {
panic(err)
}
a, err := tensor.NewSparseCOO(indices, values, []int{4, 4})
if err != nil {
panic(err)
}
return a
}