github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/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 "github.com/jingcheng-WU/gonum/floats" 11 "github.com/jingcheng-WU/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 // J_{i,j} = ∂f_i/∂x_j, 32 // or expanded out 33 // [ ∂f_1/∂x_1 ... ∂f_1/∂x_n ] 34 // [ . . . ] 35 // J = [ . . . ] 36 // [ . . . ] 37 // [ ∂f_m/∂x_1 ... ∂f_m/∂x_n ] 38 // 39 // dst must be non-nil, the number of its columns must equal the length of x, and 40 // the derivative order of the formula must be 1, otherwise Jacobian will panic. 41 func Jacobian(dst *mat.Dense, f func(y, x []float64), x []float64, settings *JacobianSettings) { 42 n := len(x) 43 if n == 0 { 44 panic("jacobian: x has zero length") 45 } 46 m, c := dst.Dims() 47 if c != n { 48 panic("jacobian: mismatched matrix size") 49 } 50 51 // Default settings. 52 formula := Forward 53 step := formula.Step 54 var originValue []float64 55 var concurrent bool 56 57 // Use user settings if provided. 58 if settings != nil { 59 if !settings.Formula.isZero() { 60 formula = settings.Formula 61 step = formula.Step 62 checkFormula(formula) 63 if formula.Derivative != 1 { 64 panic(badDerivOrder) 65 } 66 } 67 if settings.Step != 0 { 68 step = settings.Step 69 } 70 originValue = settings.OriginValue 71 if originValue != nil && len(originValue) != m { 72 panic("jacobian: mismatched OriginValue slice length") 73 } 74 concurrent = settings.Concurrent 75 } 76 77 evals := n * len(formula.Stencil) 78 for _, pt := range formula.Stencil { 79 if pt.Loc == 0 { 80 evals -= n - 1 81 break 82 } 83 } 84 85 nWorkers := computeWorkers(concurrent, evals) 86 if nWorkers == 1 { 87 jacobianSerial(dst, f, x, originValue, formula, step) 88 return 89 } 90 jacobianConcurrent(dst, f, x, originValue, formula, step, nWorkers) 91 } 92 93 func jacobianSerial(dst *mat.Dense, f func([]float64, []float64), x, origin []float64, formula Formula, step float64) { 94 m, n := dst.Dims() 95 xcopy := make([]float64, n) 96 y := make([]float64, m) 97 col := make([]float64, m) 98 for j := 0; j < n; j++ { 99 for i := range col { 100 col[i] = 0 101 } 102 for _, pt := range formula.Stencil { 103 if pt.Loc == 0 { 104 if origin == nil { 105 origin = make([]float64, m) 106 copy(xcopy, x) 107 f(origin, xcopy) 108 } 109 floats.AddScaled(col, pt.Coeff, origin) 110 } else { 111 copy(xcopy, x) 112 xcopy[j] += pt.Loc * step 113 f(y, xcopy) 114 floats.AddScaled(col, pt.Coeff, y) 115 } 116 } 117 dst.SetCol(j, col) 118 } 119 dst.Scale(1/step, dst) 120 } 121 122 func jacobianConcurrent(dst *mat.Dense, f func([]float64, []float64), x, origin []float64, formula Formula, step float64, nWorkers int) { 123 m, n := dst.Dims() 124 for i := 0; i < m; i++ { 125 for j := 0; j < n; j++ { 126 dst.Set(i, j, 0) 127 } 128 } 129 130 var ( 131 wg sync.WaitGroup 132 mu = make([]sync.Mutex, n) // Guard access to individual columns. 133 ) 134 worker := func(jobs <-chan jacJob) { 135 defer wg.Done() 136 xcopy := make([]float64, n) 137 y := make([]float64, m) 138 yVec := mat.NewVecDense(m, y) 139 var col mat.VecDense 140 for job := range jobs { 141 copy(xcopy, x) 142 xcopy[job.j] += job.pt.Loc * step 143 f(y, xcopy) 144 col.ColViewOf(dst, job.j) 145 mu[job.j].Lock() 146 col.AddScaledVec(&col, job.pt.Coeff, yVec) 147 mu[job.j].Unlock() 148 } 149 } 150 jobs := make(chan jacJob, nWorkers) 151 for i := 0; i < nWorkers; i++ { 152 wg.Add(1) 153 go worker(jobs) 154 } 155 var hasOrigin bool 156 for _, pt := range formula.Stencil { 157 if pt.Loc == 0 { 158 hasOrigin = true 159 continue 160 } 161 for j := 0; j < n; j++ { 162 jobs <- jacJob{j, pt} 163 } 164 } 165 close(jobs) 166 if hasOrigin && origin == nil { 167 wg.Add(1) 168 go func() { 169 defer wg.Done() 170 origin = make([]float64, m) 171 xcopy := make([]float64, n) 172 copy(xcopy, x) 173 f(origin, xcopy) 174 }() 175 } 176 wg.Wait() 177 178 if hasOrigin { 179 // The formula evaluated at x, we need to add scaled origin to 180 // all columns of dst. Iterate again over all Formula points 181 // because we don't forbid repeated locations. 182 183 originVec := mat.NewVecDense(m, origin) 184 for _, pt := range formula.Stencil { 185 if pt.Loc != 0 { 186 continue 187 } 188 var col mat.VecDense 189 for j := 0; j < n; j++ { 190 col.ColViewOf(dst, j) 191 col.AddScaledVec(&col, pt.Coeff, originVec) 192 } 193 } 194 } 195 196 dst.Scale(1/step, dst) 197 } 198 199 type jacJob struct { 200 j int 201 pt Point 202 }