mirror of
https://github.com/gonum/gonum.git
synced 2025-10-04 23:02:42 +08:00
270 lines
5.8 KiB
Go
270 lines
5.8 KiB
Go
// Copyright ©2016 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package fd
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"golang.org/x/exp/rand"
|
|
|
|
"gonum.org/v1/gonum/floats"
|
|
"gonum.org/v1/gonum/mat"
|
|
)
|
|
|
|
func vecFunc13(y, x []float64) {
|
|
y[0] = 5*x[0] + x[2]*math.Sin(x[1]) + 1
|
|
}
|
|
func vecFunc13Jac(jac *mat.Dense, x []float64) {
|
|
jac.Set(0, 0, 5)
|
|
jac.Set(0, 1, x[2]*math.Cos(x[1]))
|
|
jac.Set(0, 2, math.Sin(x[1]))
|
|
}
|
|
|
|
func vecFunc22(y, x []float64) {
|
|
y[0] = x[0]*x[0]*x[1] + 1
|
|
y[1] = 5*x[0] + math.Sin(x[1]) + 1
|
|
}
|
|
func vecFunc22Jac(jac *mat.Dense, x []float64) {
|
|
jac.Set(0, 0, 2*x[0]*x[1])
|
|
jac.Set(0, 1, x[0]*x[0])
|
|
jac.Set(1, 0, 5)
|
|
jac.Set(1, 1, math.Cos(x[1]))
|
|
}
|
|
|
|
func vecFunc43(y, x []float64) {
|
|
y[0] = x[0] + 1
|
|
y[1] = 5*x[2] + 1
|
|
y[2] = 4*x[1]*x[1] - 2*x[2] + 1
|
|
y[3] = x[2]*math.Sin(x[0]) + 1
|
|
}
|
|
func vecFunc43Jac(jac *mat.Dense, x []float64) {
|
|
jac.Set(0, 0, 1)
|
|
jac.Set(0, 1, 0)
|
|
jac.Set(0, 2, 0)
|
|
jac.Set(1, 0, 0)
|
|
jac.Set(1, 1, 0)
|
|
jac.Set(1, 2, 5)
|
|
jac.Set(2, 0, 0)
|
|
jac.Set(2, 1, 8*x[1])
|
|
jac.Set(2, 2, -2)
|
|
jac.Set(3, 0, x[2]*math.Cos(x[0]))
|
|
jac.Set(3, 1, 0)
|
|
jac.Set(3, 2, math.Sin(x[0]))
|
|
}
|
|
|
|
func TestJacobian(t *testing.T) {
|
|
rand.Seed(1)
|
|
|
|
// Test with default settings.
|
|
for tc, test := range []struct {
|
|
m, n int
|
|
f func([]float64, []float64)
|
|
jac func(*mat.Dense, []float64)
|
|
}{
|
|
{
|
|
m: 1,
|
|
n: 3,
|
|
f: vecFunc13,
|
|
jac: vecFunc13Jac,
|
|
},
|
|
{
|
|
m: 2,
|
|
n: 2,
|
|
f: vecFunc22,
|
|
jac: vecFunc22Jac,
|
|
},
|
|
{
|
|
m: 4,
|
|
n: 3,
|
|
f: vecFunc43,
|
|
jac: vecFunc43Jac,
|
|
},
|
|
} {
|
|
const tol = 1e-6
|
|
|
|
x := randomSlice(test.n, 10)
|
|
xcopy := make([]float64, test.n)
|
|
copy(xcopy, x)
|
|
|
|
want := mat.NewDense(test.m, test.n, nil)
|
|
test.jac(want, x)
|
|
|
|
got := mat.NewDense(test.m, test.n, nil)
|
|
fillNaNDense(got)
|
|
Jacobian(got, test.f, x, nil)
|
|
if !mat.EqualApprox(want, got, tol) {
|
|
t.Errorf("Case %d (default settings): unexpected Jacobian.\nwant: %v\ngot: %v",
|
|
tc, mat.Formatted(want, mat.Prefix(" ")), mat.Formatted(got, mat.Prefix(" ")))
|
|
}
|
|
if !floats.Equal(x, xcopy) {
|
|
t.Errorf("Case %d (default settings): x modified", tc)
|
|
}
|
|
}
|
|
|
|
// Test with non-default settings.
|
|
for tc, test := range []struct {
|
|
m, n int
|
|
f func([]float64, []float64)
|
|
jac func(*mat.Dense, []float64)
|
|
tol float64
|
|
formula Formula
|
|
}{
|
|
{
|
|
m: 1,
|
|
n: 3,
|
|
f: vecFunc13,
|
|
jac: vecFunc13Jac,
|
|
tol: 1e-6,
|
|
formula: Forward,
|
|
},
|
|
{
|
|
m: 1,
|
|
n: 3,
|
|
f: vecFunc13,
|
|
jac: vecFunc13Jac,
|
|
tol: 1e-6,
|
|
formula: Backward,
|
|
},
|
|
{
|
|
m: 1,
|
|
n: 3,
|
|
f: vecFunc13,
|
|
jac: vecFunc13Jac,
|
|
tol: 1e-9,
|
|
formula: Central,
|
|
},
|
|
{
|
|
m: 2,
|
|
n: 2,
|
|
f: vecFunc22,
|
|
jac: vecFunc22Jac,
|
|
tol: 1e-6,
|
|
formula: Forward,
|
|
},
|
|
{
|
|
m: 2,
|
|
n: 2,
|
|
f: vecFunc22,
|
|
jac: vecFunc22Jac,
|
|
tol: 1e-6,
|
|
formula: Backward,
|
|
},
|
|
{
|
|
m: 2,
|
|
n: 2,
|
|
f: vecFunc22,
|
|
jac: vecFunc22Jac,
|
|
tol: 1e-9,
|
|
formula: Central,
|
|
},
|
|
{
|
|
m: 4,
|
|
n: 3,
|
|
f: vecFunc43,
|
|
jac: vecFunc43Jac,
|
|
tol: 1e-6,
|
|
formula: Forward,
|
|
},
|
|
{
|
|
m: 4,
|
|
n: 3,
|
|
f: vecFunc43,
|
|
jac: vecFunc43Jac,
|
|
tol: 1e-6,
|
|
formula: Backward,
|
|
},
|
|
{
|
|
m: 4,
|
|
n: 3,
|
|
f: vecFunc43,
|
|
jac: vecFunc43Jac,
|
|
tol: 1e-9,
|
|
formula: Central,
|
|
},
|
|
} {
|
|
x := randomSlice(test.n, 10)
|
|
xcopy := make([]float64, test.n)
|
|
copy(xcopy, x)
|
|
|
|
want := mat.NewDense(test.m, test.n, nil)
|
|
test.jac(want, x)
|
|
|
|
got := mat.NewDense(test.m, test.n, nil)
|
|
fillNaNDense(got)
|
|
Jacobian(got, test.f, x, &JacobianSettings{
|
|
Formula: test.formula,
|
|
})
|
|
if !mat.EqualApprox(want, got, test.tol) {
|
|
t.Errorf("Case %d: unexpected Jacobian.\nwant: %v\ngot: %v",
|
|
tc, mat.Formatted(want, mat.Prefix(" ")), mat.Formatted(got, mat.Prefix(" ")))
|
|
}
|
|
if !floats.Equal(x, xcopy) {
|
|
t.Errorf("Case %d: x modified", tc)
|
|
}
|
|
|
|
fillNaNDense(got)
|
|
Jacobian(got, test.f, x, &JacobianSettings{
|
|
Formula: test.formula,
|
|
Concurrent: true,
|
|
})
|
|
if !mat.EqualApprox(want, got, test.tol) {
|
|
t.Errorf("Case %d (concurrent): unexpected Jacobian.\nwant: %v\ngot: %v",
|
|
tc, mat.Formatted(want, mat.Prefix(" ")), mat.Formatted(got, mat.Prefix(" ")))
|
|
}
|
|
if !floats.Equal(x, xcopy) {
|
|
t.Errorf("Case %d (concurrent): x modified", tc)
|
|
}
|
|
|
|
fillNaNDense(got)
|
|
origin := make([]float64, test.m)
|
|
test.f(origin, x)
|
|
Jacobian(got, test.f, x, &JacobianSettings{
|
|
Formula: test.formula,
|
|
OriginValue: origin,
|
|
})
|
|
if !mat.EqualApprox(want, got, test.tol) {
|
|
t.Errorf("Case %d (origin): unexpected Jacobian.\nwant: %v\ngot: %v",
|
|
tc, mat.Formatted(want, mat.Prefix(" ")), mat.Formatted(got, mat.Prefix(" ")))
|
|
}
|
|
if !floats.Equal(x, xcopy) {
|
|
t.Errorf("Case %d (origin): x modified", tc)
|
|
}
|
|
|
|
fillNaNDense(got)
|
|
Jacobian(got, test.f, x, &JacobianSettings{
|
|
Formula: test.formula,
|
|
OriginValue: origin,
|
|
Concurrent: true,
|
|
})
|
|
if !mat.EqualApprox(want, got, test.tol) {
|
|
t.Errorf("Case %d (concurrent, origin): unexpected Jacobian.\nwant: %v\ngot: %v",
|
|
tc, mat.Formatted(want, mat.Prefix(" ")), mat.Formatted(got, mat.Prefix(" ")))
|
|
}
|
|
if !floats.Equal(x, xcopy) {
|
|
t.Errorf("Case %d (concurrent, origin): x modified", tc)
|
|
}
|
|
}
|
|
}
|
|
|
|
// randomSlice returns a slice of n elements from the interval [-bound,bound).
|
|
func randomSlice(n int, bound float64) []float64 {
|
|
x := make([]float64, n)
|
|
for i := range x {
|
|
x[i] = 2*bound*rand.Float64() - bound
|
|
}
|
|
return x
|
|
}
|
|
|
|
// fillNaNDense fills the matrix m with NaN values.
|
|
func fillNaNDense(m *mat.Dense) {
|
|
r, c := m.Dims()
|
|
for i := 0; i < r; i++ {
|
|
for j := 0; j < c; j++ {
|
|
m.Set(i, j, math.NaN())
|
|
}
|
|
}
|
|
}
|