blob: 020f2c1b0cd88dafea09f302726e93aea1a898cf [file] [log] [blame]
// 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"
"math/rand"
"testing"
"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())
}
}
}