├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .travis.yml ├── .travis └── test-coverage.sh ├── README.md └── fd ├── diff.go ├── diff_test.go ├── example_test.go ├── gradient_test.go ├── jacobian.go └── jacobian_test.go /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### This repository is no longer actively maintained. 2 | 3 | Development of the packages in this repository has moved to https://github.com/gonum/gonum. 4 | Please file issues [there](https://github.com/gonum/gonum/issues) after having checked that your issue has not been fixed. 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### This repository is no longer actively maintained. 2 | 3 | Development of the packages in this repository has moved to https://github.com/gonum/gonum. 4 | Please send pull requests [there](https://github.com/gonum/gonum/pulls) after having checked that your addition has not already been made. 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | # Versions of go that are explicitly supported by gonum. 4 | go: 5 | - 1.5.4 6 | - 1.6.3 7 | - 1.7.3 8 | 9 | # Required for coverage. 10 | before_install: 11 | - go get golang.org/x/tools/cmd/cover 12 | - go get github.com/mattn/goveralls 13 | 14 | # Get deps, build, test, and ensure the code is gofmt'ed. 15 | # If we are building as gonum, then we have access to the coveralls api key, so we can run coverage as well. 16 | script: 17 | - go get -d -t -v ./... 18 | - go build -v ./... 19 | - go test -v ./... 20 | - test -z "$(gofmt -d .)" 21 | - if [[ $TRAVIS_SECURE_ENV_VARS = "true" ]]; then bash ./.travis/test-coverage.sh; fi 22 | -------------------------------------------------------------------------------- /.travis/test-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROFILE_OUT=$PWD/profile.out 4 | ACC_OUT=$PWD/acc.out 5 | 6 | testCover() { 7 | # set the return value to 0 (succesful) 8 | retval=0 9 | # get the directory to check from the parameter. Default to '.' 10 | d=${1:-.} 11 | # skip if there are no Go files here 12 | ls $d/*.go &> /dev/null || return $retval 13 | # switch to the directory to check 14 | pushd $d > /dev/null 15 | # create the coverage profile 16 | coverageresult=`go test -v -coverprofile=$PROFILE_OUT` 17 | # output the result so we can check the shell output 18 | echo ${coverageresult} 19 | # append the results to acc.out if coverage didn't fail, else set the retval to 1 (failed) 20 | ( [[ ${coverageresult} == *FAIL* ]] && retval=1 ) || ( [ -f $PROFILE_OUT ] && grep -v "mode: set" $PROFILE_OUT >> $ACC_OUT ) 21 | # return to our working dir 22 | popd > /dev/null 23 | # return our return value 24 | return $retval 25 | } 26 | 27 | # Init acc.out 28 | echo "mode: set" > $ACC_OUT 29 | 30 | # Run test coverage on all directories containing go files 31 | find . -maxdepth 10 -type d | while read d; do testCover $d || exit; done 32 | 33 | # Upload the coverage profile to coveralls.io 34 | [ -n "$COVERALLS_TOKEN" ] && goveralls -coverprofile=$ACC_OUT -service=travis-ci -repotoken $COVERALLS_TOKEN 35 | 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gonum diff [![Build Status](https://travis-ci.org/gonum/diff.svg)](https://travis-ci.org/gonum/diff) [![Coverage Status](https://coveralls.io/repos/gonum/diff/badge.svg?branch=master&service=github)](https://coveralls.io/github/gonum/diff?branch=master) [![GoDoc](https://godoc.org/github.com/gonum/diff?status.svg)](https://godoc.org/github.com/gonum/diff) 2 | 3 | # This repository is no longer maintained. Development has moved to https://github.com/gonum/gonum. 4 | 5 | This is a package for computing derivatives of functions for the Go language. 6 | 7 | ## Issues 8 | 9 | If you find any bugs, feel free to file an issue on the github [issue tracker for gonum/gonum](https://github.com/gonum/gonum/issues) if the bug exists in that reposity; no code changes will be made to this repository. Other dicussions should be taken to the gonum-dev Google Group. 10 | 11 | https://groups.google.com/forum/#!forum/gonum-dev 12 | 13 | ## License 14 | 15 | Please see github.com/gonum/license for general license information, contributors, authors, etc on the Gonum suite of packages. 16 | -------------------------------------------------------------------------------- /fd/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This repository is no longer maintained. 6 | // Development has moved to https://github.com/gonum/gonum. 7 | // 8 | // Package fd provides functions to approximate derivatives using finite differences. 9 | package fd 10 | 11 | import ( 12 | "math" 13 | "runtime" 14 | "sync" 15 | 16 | "github.com/gonum/floats" 17 | ) 18 | 19 | // A Point is a stencil location in a finite difference formula. 20 | type Point struct { 21 | Loc float64 22 | Coeff float64 23 | } 24 | 25 | // Formula represents a finite difference formula on a regularly spaced grid 26 | // that approximates the derivative of order k of a function f at x as 27 | // d^k f(x) ≈ (1 / Step^k) * \sum_i Coeff_i * f(x + Step * Loc_i). 28 | type Formula struct { 29 | // Stencil is the set of sampling Points which are used to estimate the 30 | // derivative. The locations will be scaled by Step and are relative to x. 31 | Stencil []Point 32 | Derivative int // The order of the approximated derivative. 33 | Step float64 // Default step size for the formula. 34 | } 35 | 36 | func (f Formula) isZero() bool { 37 | return f.Stencil == nil && f.Derivative == 0 && f.Step == 0 38 | } 39 | 40 | // Settings is the settings structure for computing finite differences. 41 | type Settings struct { 42 | // Formula is the finite difference formula used 43 | // for approximating the derivative. 44 | // Zero value indicates a default formula. 45 | Formula Formula 46 | // Step is the distance between points of the stencil. 47 | // If equal to 0, formula's default step will be used. 48 | Step float64 49 | 50 | OriginKnown bool // Flag that the value at the origin x is known. 51 | OriginValue float64 // Value at the origin (only used if OriginKnown is true). 52 | 53 | Concurrent bool // Should the function calls be executed concurrently. 54 | } 55 | 56 | // Derivative estimates the derivative of the function f at the given location. 57 | // The finite difference formula, the step size, and other options are 58 | // specified by settings. If settings is nil, the first derivative will be 59 | // estimated using the Forward formula and a default step size. 60 | func Derivative(f func(float64) float64, x float64, settings *Settings) float64 { 61 | if settings == nil { 62 | settings = &Settings{} 63 | } 64 | formula := settings.Formula 65 | if formula.isZero() { 66 | formula = Forward 67 | } 68 | if formula.Derivative == 0 || formula.Stencil == nil || formula.Step == 0 { 69 | panic("fd: bad formula") 70 | } 71 | step := settings.Step 72 | if step == 0 { 73 | step = formula.Step 74 | } 75 | 76 | var deriv float64 77 | if !settings.Concurrent || runtime.GOMAXPROCS(0) == 1 { 78 | for _, pt := range formula.Stencil { 79 | if settings.OriginKnown && pt.Loc == 0 { 80 | deriv += pt.Coeff * settings.OriginValue 81 | continue 82 | } 83 | deriv += pt.Coeff * f(x+step*pt.Loc) 84 | } 85 | return deriv / math.Pow(step, float64(formula.Derivative)) 86 | } 87 | 88 | wg := &sync.WaitGroup{} 89 | mux := &sync.Mutex{} 90 | for _, pt := range formula.Stencil { 91 | if settings.OriginKnown && pt.Loc == 0 { 92 | mux.Lock() 93 | deriv += pt.Coeff * settings.OriginValue 94 | mux.Unlock() 95 | continue 96 | } 97 | wg.Add(1) 98 | go func(pt Point) { 99 | defer wg.Done() 100 | fofx := f(x + step*pt.Loc) 101 | mux.Lock() 102 | defer mux.Unlock() 103 | deriv += pt.Coeff * fofx 104 | }(pt) 105 | } 106 | wg.Wait() 107 | return deriv / math.Pow(step, float64(formula.Derivative)) 108 | } 109 | 110 | // Gradient estimates the gradient of the multivariate function f at the 111 | // location x. If dst is not nil, the result will be stored in-place into dst 112 | // and returned, otherwise a new slice will be allocated first. Finite 113 | // difference kernel and other options are specified by settings. If settings is 114 | // nil, the gradient will be estimated using the Forward formula and a default 115 | // step size. 116 | // 117 | // Gradient panics if the length of dst and x is not equal, or if the derivative 118 | // order of the formula is not 1. 119 | func Gradient(dst []float64, f func([]float64) float64, x []float64, settings *Settings) []float64 { 120 | if dst == nil { 121 | dst = make([]float64, len(x)) 122 | } 123 | if len(dst) != len(x) { 124 | panic("fd: slice length mismatch") 125 | } 126 | if settings == nil { 127 | settings = &Settings{} 128 | } 129 | 130 | formula := settings.Formula 131 | if formula.isZero() { 132 | formula = Forward 133 | } 134 | if formula.Derivative == 0 || formula.Stencil == nil || formula.Step == 0 { 135 | panic("fd: bad formula") 136 | } 137 | if formula.Derivative != 1 { 138 | panic("fd: invalid derivative order") 139 | } 140 | 141 | step := settings.Step 142 | if step == 0 { 143 | step = formula.Step 144 | } 145 | 146 | expect := len(formula.Stencil) * len(x) 147 | nWorkers := 1 148 | if settings.Concurrent { 149 | nWorkers = runtime.GOMAXPROCS(0) 150 | if nWorkers > expect { 151 | nWorkers = expect 152 | } 153 | } 154 | 155 | var hasOrigin bool 156 | for _, pt := range formula.Stencil { 157 | if pt.Loc == 0 { 158 | hasOrigin = true 159 | break 160 | } 161 | } 162 | xcopy := make([]float64, len(x)) // So that x is not modified during the call. 163 | originValue := settings.OriginValue 164 | if hasOrigin && !settings.OriginKnown { 165 | copy(xcopy, x) 166 | originValue = f(xcopy) 167 | } 168 | 169 | if nWorkers == 1 { 170 | for i := range xcopy { 171 | var deriv float64 172 | for _, pt := range formula.Stencil { 173 | if pt.Loc == 0 { 174 | deriv += pt.Coeff * originValue 175 | continue 176 | } 177 | copy(xcopy, x) 178 | xcopy[i] += pt.Loc * step 179 | deriv += pt.Coeff * f(xcopy) 180 | } 181 | dst[i] = deriv / step 182 | } 183 | return dst 184 | } 185 | 186 | sendChan := make(chan fdrun, expect) 187 | ansChan := make(chan fdrun, expect) 188 | quit := make(chan struct{}) 189 | defer close(quit) 190 | 191 | // Launch workers. Workers receive an index and a step, and compute the answer. 192 | for i := 0; i < nWorkers; i++ { 193 | go func(sendChan <-chan fdrun, ansChan chan<- fdrun, quit <-chan struct{}) { 194 | xcopy := make([]float64, len(x)) 195 | for { 196 | select { 197 | case <-quit: 198 | return 199 | case run := <-sendChan: 200 | copy(xcopy, x) 201 | xcopy[run.idx] += run.pt.Loc * step 202 | run.result = f(xcopy) 203 | ansChan <- run 204 | } 205 | } 206 | }(sendChan, ansChan, quit) 207 | } 208 | 209 | // Launch the distributor. Distributor sends the cases to be computed. 210 | go func(sendChan chan<- fdrun, ansChan chan<- fdrun) { 211 | for i := range x { 212 | for _, pt := range formula.Stencil { 213 | if pt.Loc == 0 { 214 | // Answer already known. Send the answer on the answer channel. 215 | ansChan <- fdrun{ 216 | idx: i, 217 | pt: pt, 218 | result: originValue, 219 | } 220 | continue 221 | } 222 | // Answer not known, send the answer to be computed. 223 | sendChan <- fdrun{ 224 | idx: i, 225 | pt: pt, 226 | } 227 | } 228 | } 229 | }(sendChan, ansChan) 230 | 231 | for i := range dst { 232 | dst[i] = 0 233 | } 234 | // Read in all of the results. 235 | for i := 0; i < expect; i++ { 236 | run := <-ansChan 237 | dst[run.idx] += run.pt.Coeff * run.result 238 | } 239 | floats.Scale(1/step, dst) 240 | return dst 241 | } 242 | 243 | type fdrun struct { 244 | idx int 245 | pt Point 246 | result float64 247 | } 248 | 249 | // Forward represents a first-order accurate forward approximation 250 | // to the first derivative. 251 | var Forward = Formula{ 252 | Stencil: []Point{{Loc: 0, Coeff: -1}, {Loc: 1, Coeff: 1}}, 253 | Derivative: 1, 254 | Step: 2e-8, 255 | } 256 | 257 | // Backward represents a first-order accurate backward approximation 258 | // to the first derivative. 259 | var Backward = Formula{ 260 | Stencil: []Point{{Loc: -1, Coeff: -1}, {Loc: 0, Coeff: 1}}, 261 | Derivative: 1, 262 | Step: 2e-8, 263 | } 264 | 265 | // Central represents a second-order accurate centered approximation 266 | // to the first derivative. 267 | var Central = Formula{ 268 | Stencil: []Point{{Loc: -1, Coeff: -0.5}, {Loc: 1, Coeff: 0.5}}, 269 | Derivative: 1, 270 | Step: 6e-6, 271 | } 272 | 273 | // Central2nd represents a secord-order accurate centered approximation 274 | // to the second derivative. 275 | var Central2nd = Formula{ 276 | Stencil: []Point{{Loc: -1, Coeff: 1}, {Loc: 0, Coeff: -2}, {Loc: 1, Coeff: 1}}, 277 | Derivative: 2, 278 | Step: 1e-4, 279 | } 280 | -------------------------------------------------------------------------------- /fd/diff_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fd 6 | 7 | import ( 8 | "math" 9 | "testing" 10 | ) 11 | 12 | var xSquared = func(x float64) float64 { return x * x } 13 | 14 | type testPoint struct { 15 | f func(float64) float64 16 | loc float64 17 | fofx float64 18 | ans float64 19 | } 20 | 21 | var testsFirst = []testPoint{ 22 | { 23 | f: xSquared, 24 | loc: 0, 25 | fofx: 0, 26 | ans: 0, 27 | }, 28 | { 29 | f: xSquared, 30 | loc: 5, 31 | fofx: 25, 32 | ans: 10, 33 | }, 34 | { 35 | f: xSquared, 36 | loc: 2, 37 | fofx: 4, 38 | ans: 4, 39 | }, 40 | { 41 | f: xSquared, 42 | loc: -5, 43 | fofx: 25, 44 | ans: -10, 45 | }, 46 | } 47 | 48 | var testsSecond = []testPoint{ 49 | { 50 | f: xSquared, 51 | loc: 0, 52 | fofx: 0, 53 | ans: 2, 54 | }, 55 | { 56 | f: xSquared, 57 | loc: 5, 58 | fofx: 25, 59 | ans: 2, 60 | }, 61 | { 62 | f: xSquared, 63 | loc: 2, 64 | fofx: 4, 65 | ans: 2, 66 | }, 67 | { 68 | f: xSquared, 69 | loc: -5, 70 | fofx: 25, 71 | ans: 2, 72 | }, 73 | } 74 | 75 | func testDerivative(t *testing.T, formula Formula, tol float64, tests []testPoint) { 76 | for i, test := range tests { 77 | 78 | ans := Derivative(test.f, test.loc, &Settings{ 79 | Formula: formula, 80 | }) 81 | if math.Abs(test.ans-ans) > tol { 82 | t.Errorf("Case %v: ans mismatch serial: expected %v, found %v", i, test.ans, ans) 83 | } 84 | 85 | ans = Derivative(test.f, test.loc, &Settings{ 86 | Formula: formula, 87 | OriginKnown: true, 88 | OriginValue: test.fofx, 89 | }) 90 | if math.Abs(test.ans-ans) > tol { 91 | t.Errorf("Case %v: ans mismatch serial origin known: expected %v, found %v", i, test.ans, ans) 92 | } 93 | 94 | ans = Derivative(test.f, test.loc, &Settings{ 95 | Formula: formula, 96 | Concurrent: true, 97 | }) 98 | if math.Abs(test.ans-ans) > tol { 99 | t.Errorf("Case %v: ans mismatch concurrent: expected %v, found %v", i, test.ans, ans) 100 | } 101 | 102 | ans = Derivative(test.f, test.loc, &Settings{ 103 | Formula: formula, 104 | OriginKnown: true, 105 | OriginValue: test.fofx, 106 | Concurrent: true, 107 | }) 108 | if math.Abs(test.ans-ans) > tol { 109 | t.Errorf("Case %v: ans mismatch concurrent: expected %v, found %v", i, test.ans, ans) 110 | } 111 | } 112 | } 113 | 114 | func TestForward(t *testing.T) { 115 | testDerivative(t, Forward, 2e-4, testsFirst) 116 | } 117 | 118 | func TestBackward(t *testing.T) { 119 | testDerivative(t, Backward, 2e-4, testsFirst) 120 | } 121 | 122 | func TestCentral(t *testing.T) { 123 | testDerivative(t, Central, 1e-6, testsFirst) 124 | } 125 | 126 | func TestCentralSecond(t *testing.T) { 127 | testDerivative(t, Central2nd, 1e-3, testsSecond) 128 | } 129 | 130 | // TestDerivativeDefault checks that the derivative works when settings is nil 131 | // or zero value. 132 | func TestDerivativeDefault(t *testing.T) { 133 | tol := 1e-6 134 | for i, test := range testsFirst { 135 | ans := Derivative(test.f, test.loc, nil) 136 | if math.Abs(test.ans-ans) > tol { 137 | t.Errorf("Case %v: ans mismatch default: expected %v, found %v", i, test.ans, ans) 138 | } 139 | 140 | ans = Derivative(test.f, test.loc, &Settings{}) 141 | if math.Abs(test.ans-ans) > tol { 142 | t.Errorf("Case %v: ans mismatch zero value: expected %v, found %v", i, test.ans, ans) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /fd/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fd_test 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | 11 | "github.com/gonum/diff/fd" 12 | "github.com/gonum/matrix/mat64" 13 | ) 14 | 15 | func ExampleDerivative() { 16 | f := func(x float64) float64 { 17 | return math.Sin(x) 18 | } 19 | // Compute the first derivative of f at 0 using the default settings. 20 | fmt.Println("f'(0) ≈", fd.Derivative(f, 0, nil)) 21 | // Compute the first derivative of f at 0 using the forward approximation 22 | // with a custom step size. 23 | df := fd.Derivative(f, 0, &fd.Settings{ 24 | Formula: fd.Forward, 25 | Step: 1e-3, 26 | }) 27 | fmt.Println("f'(0) ≈", df) 28 | 29 | f = func(x float64) float64 { 30 | return math.Pow(math.Cos(x), 3) 31 | } 32 | // Compute the second derivative of f at 0 using 33 | // the centered approximation, concurrent evaluation, 34 | // and a known function value at x. 35 | df = fd.Derivative(f, 0, &fd.Settings{ 36 | Formula: fd.Central2nd, 37 | Concurrent: true, 38 | OriginKnown: true, 39 | OriginValue: f(0), 40 | }) 41 | fmt.Println("f''(0) ≈", df) 42 | 43 | // Output: 44 | // f'(0) ≈ 1 45 | // f'(0) ≈ 0.9999998333333416 46 | // f''(0) ≈ -2.999999981767587 47 | } 48 | 49 | func ExampleJacobian() { 50 | f := func(dst, x []float64) { 51 | dst[0] = x[0] + 1 52 | dst[1] = 5 * x[2] 53 | dst[2] = 4*x[1]*x[1] - 2*x[2] 54 | dst[3] = x[2] * math.Sin(x[0]) 55 | } 56 | jac := mat64.NewDense(4, 3, nil) 57 | fd.Jacobian(jac, f, []float64{1, 2, 3}, &fd.JacobianSettings{ 58 | Formula: fd.Central, 59 | Concurrent: true, 60 | }) 61 | fmt.Printf("J ≈ %.6v\n", mat64.Formatted(jac, mat64.Prefix(" "))) 62 | 63 | // Output: 64 | // J ≈ ⎡ 1 0 0⎤ 65 | // ⎢ 0 0 5⎥ 66 | // ⎢ 0 16 -2⎥ 67 | // ⎣ 1.62091 0 0.841471⎦ 68 | } 69 | -------------------------------------------------------------------------------- /fd/gradient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2014 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fd 6 | 7 | import ( 8 | "math" 9 | "math/rand" 10 | "testing" 11 | 12 | "github.com/gonum/floats" 13 | ) 14 | 15 | type Rosenbrock struct { 16 | nDim int 17 | } 18 | 19 | func (r Rosenbrock) F(x []float64) (sum float64) { 20 | deriv := make([]float64, len(x)) 21 | return r.FDf(x, deriv) 22 | } 23 | 24 | func (r Rosenbrock) FDf(x []float64, deriv []float64) (sum float64) { 25 | for i := range deriv { 26 | deriv[i] = 0 27 | } 28 | 29 | for i := 0; i < len(x)-1; i++ { 30 | sum += math.Pow(1-x[i], 2) + 100*math.Pow(x[i+1]-math.Pow(x[i], 2), 2) 31 | } 32 | for i := 0; i < len(x)-1; i++ { 33 | deriv[i] += -1 * 2 * (1 - x[i]) 34 | deriv[i] += 2 * 100 * (x[i+1] - math.Pow(x[i], 2)) * (-2 * x[i]) 35 | } 36 | for i := 1; i < len(x); i++ { 37 | deriv[i] += 2 * 100 * (x[i] - math.Pow(x[i-1], 2)) 38 | } 39 | 40 | return sum 41 | } 42 | 43 | func TestGradient(t *testing.T) { 44 | rand.Seed(1) 45 | for i, test := range []struct { 46 | nDim int 47 | tol float64 48 | formula Formula 49 | }{ 50 | { 51 | nDim: 2, 52 | tol: 2e-4, 53 | formula: Forward, 54 | }, 55 | { 56 | nDim: 2, 57 | tol: 1e-6, 58 | formula: Central, 59 | }, 60 | { 61 | nDim: 40, 62 | tol: 2e-4, 63 | formula: Forward, 64 | }, 65 | { 66 | nDim: 40, 67 | tol: 1e-6, 68 | formula: Central, 69 | }, 70 | } { 71 | x := make([]float64, test.nDim) 72 | for i := range x { 73 | x[i] = rand.Float64() 74 | } 75 | xcopy := make([]float64, len(x)) 76 | copy(xcopy, x) 77 | 78 | r := Rosenbrock{len(x)} 79 | trueGradient := make([]float64, len(x)) 80 | r.FDf(x, trueGradient) 81 | 82 | // Try with gradient nil. 83 | gradient := Gradient(nil, r.F, x, &Settings{ 84 | Formula: test.formula, 85 | }) 86 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 87 | t.Errorf("Case %v: gradient mismatch in serial with nil. Want: %v, Got: %v.", i, trueGradient, gradient) 88 | } 89 | if !floats.Equal(x, xcopy) { 90 | t.Errorf("Case %v: x modified during call to gradient in serial with nil.", i) 91 | } 92 | 93 | // Try with provided gradient. 94 | for i := range gradient { 95 | gradient[i] = rand.Float64() 96 | } 97 | Gradient(gradient, r.F, x, &Settings{ 98 | Formula: test.formula, 99 | }) 100 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 101 | t.Errorf("Case %v: gradient mismatch in serial. Want: %v, Got: %v.", i, trueGradient, gradient) 102 | } 103 | if !floats.Equal(x, xcopy) { 104 | t.Errorf("Case %v: x modified during call to gradient in serial with non-nil.", i) 105 | } 106 | 107 | // Try with known value. 108 | for i := range gradient { 109 | gradient[i] = rand.Float64() 110 | } 111 | Gradient(gradient, r.F, x, &Settings{ 112 | Formula: test.formula, 113 | OriginKnown: true, 114 | OriginValue: r.F(x), 115 | }) 116 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 117 | t.Errorf("Case %v: gradient mismatch with known origin in serial. Want: %v, Got: %v.", i, trueGradient, gradient) 118 | } 119 | 120 | // Try with concurrent evaluation. 121 | for i := range gradient { 122 | gradient[i] = rand.Float64() 123 | } 124 | Gradient(gradient, r.F, x, &Settings{ 125 | Formula: test.formula, 126 | Concurrent: true, 127 | }) 128 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 129 | t.Errorf("Case %v: gradient mismatch with unknown origin in parallel. Want: %v, Got: %v.", i, trueGradient, gradient) 130 | } 131 | if !floats.Equal(x, xcopy) { 132 | t.Errorf("Case %v: x modified during call to gradient in parallel", i) 133 | } 134 | 135 | // Try with concurrent evaluation with origin known. 136 | for i := range gradient { 137 | gradient[i] = rand.Float64() 138 | } 139 | Gradient(gradient, r.F, x, &Settings{ 140 | Formula: test.formula, 141 | Concurrent: true, 142 | OriginKnown: true, 143 | OriginValue: r.F(x), 144 | }) 145 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 146 | t.Errorf("Case %v: gradient mismatch with known origin in parallel. Want: %v, Got: %v.", i, trueGradient, gradient) 147 | } 148 | 149 | // Try with nil settings. 150 | for i := range gradient { 151 | gradient[i] = rand.Float64() 152 | } 153 | Gradient(gradient, r.F, x, nil) 154 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 155 | t.Errorf("Case %v: gradient mismatch with default settings. Want: %v, Got: %v.", i, trueGradient, gradient) 156 | } 157 | 158 | // Try with zero-valued settings. 159 | for i := range gradient { 160 | gradient[i] = rand.Float64() 161 | } 162 | Gradient(gradient, r.F, x, &Settings{}) 163 | if !floats.EqualApprox(gradient, trueGradient, test.tol) { 164 | t.Errorf("Case %v: gradient mismatch with zero settings. Want: %v, Got: %v.", i, trueGradient, gradient) 165 | } 166 | } 167 | } 168 | 169 | func Panics(fun func()) (b bool) { 170 | defer func() { 171 | err := recover() 172 | if err != nil { 173 | b = true 174 | } 175 | }() 176 | fun() 177 | return 178 | } 179 | 180 | func TestGradientPanics(t *testing.T) { 181 | // Test that it panics 182 | if !Panics(func() { 183 | Gradient([]float64{0.0}, func(x []float64) float64 { return x[0] * x[0] }, []float64{0.0, 0.0}, nil) 184 | }) { 185 | t.Errorf("Gradient did not panic with length mismatch") 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /fd/jacobian.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fd 6 | 7 | import ( 8 | "runtime" 9 | "sync" 10 | 11 | "github.com/gonum/floats" 12 | "github.com/gonum/matrix/mat64" 13 | ) 14 | 15 | type JacobianSettings struct { 16 | Formula Formula 17 | OriginValue []float64 18 | Step float64 19 | Concurrent bool 20 | } 21 | 22 | // Jacobian approximates the Jacobian matrix of a vector-valued function f at 23 | // the location x and stores the result in-place into dst. 24 | // 25 | // Finite difference formula and other options are specified by settings. If 26 | // settings is nil, the Jacobian will be estimated using the Forward formula and 27 | // a default step size. 28 | // 29 | // The Jacobian matrix J is the matrix of all first-order partial derivatives of f. 30 | // If f maps an n-dimensional vector x to an m-dimensional vector y = f(x), J is 31 | // an m×n matrix whose elements are given as 32 | // J_{i,j} = ∂f_i/∂x_j, 33 | // or expanded out 34 | // [ ∂f_1/∂x_1 ... ∂f_1/∂x_n ] 35 | // [ . . . ] 36 | // J = [ . . . ] 37 | // [ . . . ] 38 | // [ ∂f_m/∂x_1 ... ∂f_m/∂x_n ] 39 | // 40 | // dst must be non-nil, the number of its columns must equal the length of x, and 41 | // the derivative order of the formula must be 1, otherwise Jacobian will panic. 42 | func Jacobian(dst *mat64.Dense, f func(y, x []float64), x []float64, settings *JacobianSettings) { 43 | n := len(x) 44 | if n == 0 { 45 | panic("jacobian: x has zero length") 46 | } 47 | m, c := dst.Dims() 48 | if c != n { 49 | panic("jacobian: mismatched matrix size") 50 | } 51 | 52 | if settings == nil { 53 | settings = &JacobianSettings{} 54 | } 55 | if settings.OriginValue != nil && len(settings.OriginValue) != m { 56 | panic("jacobian: mismatched OriginValue slice length") 57 | } 58 | 59 | formula := settings.Formula 60 | if formula.isZero() { 61 | formula = Forward 62 | } 63 | if formula.Derivative == 0 || formula.Stencil == nil || formula.Step == 0 { 64 | panic("jacobian: bad formula") 65 | } 66 | if formula.Derivative != 1 { 67 | panic("jacobian: invalid derivative order") 68 | } 69 | 70 | step := settings.Step 71 | if step == 0 { 72 | step = formula.Step 73 | } 74 | 75 | evals := n * len(formula.Stencil) 76 | for _, pt := range formula.Stencil { 77 | if pt.Loc == 0 { 78 | evals -= n - 1 79 | break 80 | } 81 | } 82 | nWorkers := 1 83 | if settings.Concurrent { 84 | nWorkers = runtime.GOMAXPROCS(0) 85 | if nWorkers > evals { 86 | nWorkers = evals 87 | } 88 | } 89 | if nWorkers == 1 { 90 | jacobianSerial(dst, f, x, settings.OriginValue, formula, step) 91 | } else { 92 | jacobianConcurrent(dst, f, x, settings.OriginValue, formula, step, nWorkers) 93 | } 94 | } 95 | 96 | func jacobianSerial(dst *mat64.Dense, f func([]float64, []float64), x, origin []float64, formula Formula, step float64) { 97 | m, n := dst.Dims() 98 | xcopy := make([]float64, n) 99 | y := make([]float64, m) 100 | col := make([]float64, m) 101 | for j := 0; j < n; j++ { 102 | for i := range col { 103 | col[i] = 0 104 | } 105 | for _, pt := range formula.Stencil { 106 | if pt.Loc == 0 { 107 | if origin == nil { 108 | origin = make([]float64, m) 109 | copy(xcopy, x) 110 | f(origin, xcopy) 111 | } 112 | floats.AddScaled(col, pt.Coeff, origin) 113 | } else { 114 | copy(xcopy, x) 115 | xcopy[j] += pt.Loc * step 116 | f(y, xcopy) 117 | floats.AddScaled(col, pt.Coeff, y) 118 | } 119 | } 120 | dst.SetCol(j, col) 121 | } 122 | dst.Scale(1/step, dst) 123 | } 124 | 125 | func jacobianConcurrent(dst *mat64.Dense, f func([]float64, []float64), x, origin []float64, formula Formula, step float64, nWorkers int) { 126 | m, n := dst.Dims() 127 | for i := 0; i < m; i++ { 128 | for j := 0; j < n; j++ { 129 | dst.Set(i, j, 0) 130 | } 131 | } 132 | 133 | var ( 134 | wg sync.WaitGroup 135 | mu = make([]sync.Mutex, n) // Guard access to individual columns. 136 | ) 137 | worker := func(jobs <-chan jacJob) { 138 | defer wg.Done() 139 | xcopy := make([]float64, n) 140 | y := make([]float64, m) 141 | yVec := mat64.NewVector(m, y) 142 | for job := range jobs { 143 | copy(xcopy, x) 144 | xcopy[job.j] += job.pt.Loc * step 145 | f(y, xcopy) 146 | col := dst.ColView(job.j) 147 | mu[job.j].Lock() 148 | col.AddScaledVec(col, job.pt.Coeff, yVec) 149 | mu[job.j].Unlock() 150 | } 151 | } 152 | jobs := make(chan jacJob, nWorkers) 153 | for i := 0; i < nWorkers; i++ { 154 | wg.Add(1) 155 | go worker(jobs) 156 | } 157 | var hasOrigin bool 158 | for _, pt := range formula.Stencil { 159 | if pt.Loc == 0 { 160 | hasOrigin = true 161 | continue 162 | } 163 | for j := 0; j < n; j++ { 164 | jobs <- jacJob{j, pt} 165 | } 166 | } 167 | close(jobs) 168 | if hasOrigin && origin == nil { 169 | wg.Add(1) 170 | go func() { 171 | defer wg.Done() 172 | origin = make([]float64, m) 173 | xcopy := make([]float64, n) 174 | copy(xcopy, x) 175 | f(origin, xcopy) 176 | }() 177 | } 178 | wg.Wait() 179 | 180 | if hasOrigin { 181 | // The formula evaluated at x, we need to add scaled origin to 182 | // all columns of dst. Iterate again over all Formula points 183 | // because we don't forbid repeated locations. 184 | 185 | originVec := mat64.NewVector(m, origin) 186 | for _, pt := range formula.Stencil { 187 | if pt.Loc != 0 { 188 | continue 189 | } 190 | for j := 0; j < n; j++ { 191 | col := dst.ColView(j) 192 | col.AddScaledVec(col, pt.Coeff, originVec) 193 | } 194 | } 195 | } 196 | 197 | dst.Scale(1/step, dst) 198 | } 199 | 200 | type jacJob struct { 201 | j int 202 | pt Point 203 | } 204 | -------------------------------------------------------------------------------- /fd/jacobian_test.go: -------------------------------------------------------------------------------- 1 | // Copyright ©2016 The gonum Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fd 6 | 7 | import ( 8 | "math" 9 | "math/rand" 10 | "testing" 11 | 12 | "github.com/gonum/floats" 13 | "github.com/gonum/matrix/mat64" 14 | ) 15 | 16 | func vecFunc13(y, x []float64) { 17 | y[0] = 5*x[0] + x[2]*math.Sin(x[1]) + 1 18 | } 19 | func vecFunc13Jac(jac *mat64.Dense, x []float64) { 20 | jac.Set(0, 0, 5) 21 | jac.Set(0, 1, x[2]*math.Cos(x[1])) 22 | jac.Set(0, 2, math.Sin(x[1])) 23 | } 24 | 25 | func vecFunc22(y, x []float64) { 26 | y[0] = x[0]*x[0]*x[1] + 1 27 | y[1] = 5*x[0] + math.Sin(x[1]) + 1 28 | } 29 | func vecFunc22Jac(jac *mat64.Dense, x []float64) { 30 | jac.Set(0, 0, 2*x[0]*x[1]) 31 | jac.Set(0, 1, x[0]*x[0]) 32 | jac.Set(1, 0, 5) 33 | jac.Set(1, 1, math.Cos(x[1])) 34 | } 35 | 36 | func vecFunc43(y, x []float64) { 37 | y[0] = x[0] + 1 38 | y[1] = 5*x[2] + 1 39 | y[2] = 4*x[1]*x[1] - 2*x[2] + 1 40 | y[3] = x[2]*math.Sin(x[0]) + 1 41 | } 42 | func vecFunc43Jac(jac *mat64.Dense, x []float64) { 43 | jac.Set(0, 0, 1) 44 | jac.Set(0, 1, 0) 45 | jac.Set(0, 2, 0) 46 | jac.Set(1, 0, 0) 47 | jac.Set(1, 1, 0) 48 | jac.Set(1, 2, 5) 49 | jac.Set(2, 0, 0) 50 | jac.Set(2, 1, 8*x[1]) 51 | jac.Set(2, 2, -2) 52 | jac.Set(3, 0, x[2]*math.Cos(x[0])) 53 | jac.Set(3, 1, 0) 54 | jac.Set(3, 2, math.Sin(x[0])) 55 | } 56 | 57 | func TestJacobian(t *testing.T) { 58 | rand.Seed(1) 59 | 60 | // Test with default settings. 61 | for tc, test := range []struct { 62 | m, n int 63 | f func([]float64, []float64) 64 | jac func(*mat64.Dense, []float64) 65 | }{ 66 | { 67 | m: 1, 68 | n: 3, 69 | f: vecFunc13, 70 | jac: vecFunc13Jac, 71 | }, 72 | { 73 | m: 2, 74 | n: 2, 75 | f: vecFunc22, 76 | jac: vecFunc22Jac, 77 | }, 78 | { 79 | m: 4, 80 | n: 3, 81 | f: vecFunc43, 82 | jac: vecFunc43Jac, 83 | }, 84 | } { 85 | const tol = 1e-6 86 | 87 | x := randomSlice(test.n, 10) 88 | xcopy := make([]float64, test.n) 89 | copy(xcopy, x) 90 | 91 | want := mat64.NewDense(test.m, test.n, nil) 92 | test.jac(want, x) 93 | 94 | got := mat64.NewDense(test.m, test.n, nil) 95 | fillNaNDense(got) 96 | Jacobian(got, test.f, x, nil) 97 | if !mat64.EqualApprox(want, got, tol) { 98 | t.Errorf("Case %d (default settings): unexpected Jacobian.\nwant: %v\ngot: %v", 99 | tc, mat64.Formatted(want, mat64.Prefix(" ")), mat64.Formatted(got, mat64.Prefix(" "))) 100 | } 101 | if !floats.Equal(x, xcopy) { 102 | t.Errorf("Case %d (default settings): x modified", tc) 103 | } 104 | } 105 | 106 | // Test with non-default settings. 107 | for tc, test := range []struct { 108 | m, n int 109 | f func([]float64, []float64) 110 | jac func(*mat64.Dense, []float64) 111 | tol float64 112 | formula Formula 113 | }{ 114 | { 115 | m: 1, 116 | n: 3, 117 | f: vecFunc13, 118 | jac: vecFunc13Jac, 119 | tol: 1e-6, 120 | formula: Forward, 121 | }, 122 | { 123 | m: 1, 124 | n: 3, 125 | f: vecFunc13, 126 | jac: vecFunc13Jac, 127 | tol: 1e-6, 128 | formula: Backward, 129 | }, 130 | { 131 | m: 1, 132 | n: 3, 133 | f: vecFunc13, 134 | jac: vecFunc13Jac, 135 | tol: 1e-9, 136 | formula: Central, 137 | }, 138 | { 139 | m: 2, 140 | n: 2, 141 | f: vecFunc22, 142 | jac: vecFunc22Jac, 143 | tol: 1e-6, 144 | formula: Forward, 145 | }, 146 | { 147 | m: 2, 148 | n: 2, 149 | f: vecFunc22, 150 | jac: vecFunc22Jac, 151 | tol: 1e-6, 152 | formula: Backward, 153 | }, 154 | { 155 | m: 2, 156 | n: 2, 157 | f: vecFunc22, 158 | jac: vecFunc22Jac, 159 | tol: 1e-9, 160 | formula: Central, 161 | }, 162 | { 163 | m: 4, 164 | n: 3, 165 | f: vecFunc43, 166 | jac: vecFunc43Jac, 167 | tol: 1e-6, 168 | formula: Forward, 169 | }, 170 | { 171 | m: 4, 172 | n: 3, 173 | f: vecFunc43, 174 | jac: vecFunc43Jac, 175 | tol: 1e-6, 176 | formula: Backward, 177 | }, 178 | { 179 | m: 4, 180 | n: 3, 181 | f: vecFunc43, 182 | jac: vecFunc43Jac, 183 | tol: 1e-9, 184 | formula: Central, 185 | }, 186 | } { 187 | x := randomSlice(test.n, 10) 188 | xcopy := make([]float64, test.n) 189 | copy(xcopy, x) 190 | 191 | want := mat64.NewDense(test.m, test.n, nil) 192 | test.jac(want, x) 193 | 194 | got := mat64.NewDense(test.m, test.n, nil) 195 | fillNaNDense(got) 196 | Jacobian(got, test.f, x, &JacobianSettings{ 197 | Formula: test.formula, 198 | }) 199 | if !mat64.EqualApprox(want, got, test.tol) { 200 | t.Errorf("Case %d: unexpected Jacobian.\nwant: %v\ngot: %v", 201 | tc, mat64.Formatted(want, mat64.Prefix(" ")), mat64.Formatted(got, mat64.Prefix(" "))) 202 | } 203 | if !floats.Equal(x, xcopy) { 204 | t.Errorf("Case %d: x modified", tc) 205 | } 206 | 207 | fillNaNDense(got) 208 | Jacobian(got, test.f, x, &JacobianSettings{ 209 | Formula: test.formula, 210 | Concurrent: true, 211 | }) 212 | if !mat64.EqualApprox(want, got, test.tol) { 213 | t.Errorf("Case %d (concurrent): unexpected Jacobian.\nwant: %v\ngot: %v", 214 | tc, mat64.Formatted(want, mat64.Prefix(" ")), mat64.Formatted(got, mat64.Prefix(" "))) 215 | } 216 | if !floats.Equal(x, xcopy) { 217 | t.Errorf("Case %d (concurrent): x modified", tc) 218 | } 219 | 220 | fillNaNDense(got) 221 | origin := make([]float64, test.m) 222 | test.f(origin, x) 223 | Jacobian(got, test.f, x, &JacobianSettings{ 224 | Formula: test.formula, 225 | OriginValue: origin, 226 | }) 227 | if !mat64.EqualApprox(want, got, test.tol) { 228 | t.Errorf("Case %d (origin): unexpected Jacobian.\nwant: %v\ngot: %v", 229 | tc, mat64.Formatted(want, mat64.Prefix(" ")), mat64.Formatted(got, mat64.Prefix(" "))) 230 | } 231 | if !floats.Equal(x, xcopy) { 232 | t.Errorf("Case %d (origin): x modified", tc) 233 | } 234 | 235 | fillNaNDense(got) 236 | Jacobian(got, test.f, x, &JacobianSettings{ 237 | Formula: test.formula, 238 | OriginValue: origin, 239 | Concurrent: true, 240 | }) 241 | if !mat64.EqualApprox(want, got, test.tol) { 242 | t.Errorf("Case %d (concurrent, origin): unexpected Jacobian.\nwant: %v\ngot: %v", 243 | tc, mat64.Formatted(want, mat64.Prefix(" ")), mat64.Formatted(got, mat64.Prefix(" "))) 244 | } 245 | if !floats.Equal(x, xcopy) { 246 | t.Errorf("Case %d (concurrent, origin): x modified", tc) 247 | } 248 | } 249 | } 250 | 251 | // randomSlice returns a slice of n elements from the interval [-bound,bound). 252 | func randomSlice(n int, bound float64) []float64 { 253 | x := make([]float64, n) 254 | for i := range x { 255 | x[i] = 2*bound*rand.Float64() - bound 256 | } 257 | return x 258 | } 259 | 260 | // fillNaNDense fills the matrix m with NaN values. 261 | func fillNaNDense(m *mat64.Dense) { 262 | r, c := m.Dims() 263 | for i := 0; i < r; i++ { 264 | for j := 0; j < c; j++ { 265 | m.Set(i, j, math.NaN()) 266 | } 267 | } 268 | } 269 | --------------------------------------------------------------------------------