github.com/gopherd/gonum@v0.0.4/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/gopherd/gonum/floats"
    11  	"github.com/gopherd/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  }