gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/diff/fd/jacobian.go (about) 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 "sync" 9 10 "gonum.org/v1/gonum/floats" 11 "gonum.org/v1/gonum/mat" 12 ) 13 14 type JacobianSettings struct { 15 Formula Formula 16 OriginValue []float64 17 Step float64 18 Concurrent bool 19 } 20 21 // Jacobian approximates the Jacobian matrix of a vector-valued function f at 22 // the location x and stores the result in-place into dst. 23 // 24 // Finite difference formula and other options are specified by settings. If 25 // settings is nil, the Jacobian will be estimated using the Forward formula and 26 // a default step size. 27 // 28 // The Jacobian matrix J is the matrix of all first-order partial derivatives of f. 29 // If f maps an n-dimensional vector x to an m-dimensional vector y = f(x), J is 30 // an m×n matrix whose elements are given as 31 // 32 // J_{i,j} = ∂f_i/∂x_j, 33 // 34 // or expanded out 35 // 36 // [ ∂f_1/∂x_1 ... ∂f_1/∂x_n ] 37 // [ . . . ] 38 // J = [ . . . ] 39 // [ . . . ] 40 // [ ∂f_m/∂x_1 ... ∂f_m/∂x_n ] 41 // 42 // dst must be non-nil, the number of its columns must equal the length of x, and 43 // the derivative order of the formula must be 1, otherwise Jacobian will panic. 44 func Jacobian(dst *mat.Dense, f func(y, x []float64), x []float64, settings *JacobianSettings) { 45 n := len(x) 46 if n == 0 { 47 panic("jacobian: x has zero length") 48 } 49 m, c := dst.Dims() 50 if c != n { 51 panic("jacobian: mismatched matrix size") 52 } 53 54 // Default settings. 55 formula := Forward 56 step := formula.Step 57 var originValue []float64 58 var concurrent bool 59 60 // Use user settings if provided. 61 if settings != nil { 62 if !settings.Formula.isZero() { 63 formula = settings.Formula 64 step = formula.Step 65 checkFormula(formula) 66 if formula.Derivative != 1 { 67 panic(badDerivOrder) 68 } 69 } 70 if settings.Step != 0 { 71 step = settings.Step 72 } 73 originValue = settings.OriginValue 74 if originValue != nil && len(originValue) != m { 75 panic("jacobian: mismatched OriginValue slice length") 76 } 77 concurrent = settings.Concurrent 78 } 79 80 evals := n * len(formula.Stencil) 81 for _, pt := range formula.Stencil { 82 if pt.Loc == 0 { 83 evals -= n - 1 84 break 85 } 86 } 87 88 nWorkers := computeWorkers(concurrent, evals) 89 if nWorkers == 1 { 90 jacobianSerial(dst, f, x, originValue, formula, step) 91 return 92 } 93 jacobianConcurrent(dst, f, x, originValue, formula, step, nWorkers) 94 } 95 96 func jacobianSerial(dst *mat.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 *mat.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 := mat.NewVecDense(m, y) 142 var col mat.VecDense 143 for job := range jobs { 144 copy(xcopy, x) 145 xcopy[job.j] += job.pt.Loc * step 146 f(y, xcopy) 147 col.ColViewOf(dst, job.j) 148 mu[job.j].Lock() 149 col.AddScaledVec(&col, job.pt.Coeff, yVec) 150 mu[job.j].Unlock() 151 } 152 } 153 jobs := make(chan jacJob, nWorkers) 154 for i := 0; i < nWorkers; i++ { 155 wg.Add(1) 156 go worker(jobs) 157 } 158 var hasOrigin bool 159 for _, pt := range formula.Stencil { 160 if pt.Loc == 0 { 161 hasOrigin = true 162 continue 163 } 164 for j := 0; j < n; j++ { 165 jobs <- jacJob{j, pt} 166 } 167 } 168 close(jobs) 169 if hasOrigin && origin == nil { 170 wg.Add(1) 171 go func() { 172 defer wg.Done() 173 origin = make([]float64, m) 174 xcopy := make([]float64, n) 175 copy(xcopy, x) 176 f(origin, xcopy) 177 }() 178 } 179 wg.Wait() 180 181 if hasOrigin { 182 // The formula evaluated at x, we need to add scaled origin to 183 // all columns of dst. Iterate again over all Formula points 184 // because we don't forbid repeated locations. 185 186 originVec := mat.NewVecDense(m, origin) 187 for _, pt := range formula.Stencil { 188 if pt.Loc != 0 { 189 continue 190 } 191 var col mat.VecDense 192 for j := 0; j < n; j++ { 193 col.ColViewOf(dst, j) 194 col.AddScaledVec(&col, pt.Coeff, originVec) 195 } 196 } 197 } 198 199 dst.Scale(1/step, dst) 200 } 201 202 type jacJob struct { 203 j int 204 pt Point 205 }