// Copyright ©2017 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 "sync" // Laplacian computes the Laplacian of the multivariate function f at the location // x. That is, Laplacian returns // ∆ f(x) = ∇ · ∇ f(x) = \sum_i ∂^2 f(x)/∂x_i^2 // The finite difference formula and other options are specified by settings. // The order of the difference formula must be 2 or Laplacian will panic. func Laplacian(f func(x []float64) float64, x []float64, settings *Settings) float64 { n := len(x) if n == 0 { panic("laplacian: x has zero length") } // Default settings. formula := Central2nd step := formula.Step var originValue float64 var originKnown, concurrent bool // Use user settings if provided. if settings != nil { if !settings.Formula.isZero() { formula = settings.Formula step = formula.Step checkFormula(formula) if formula.Derivative != 2 { panic(badDerivOrder) } } if settings.Step != 0 { if settings.Step < 0 { panic(negativeStep) } step = settings.Step } originKnown = settings.OriginKnown originValue = settings.OriginValue concurrent = settings.Concurrent } evals := n * len(formula.Stencil) if usesOrigin(formula.Stencil) { evals -= n } nWorkers := computeWorkers(concurrent, evals) if nWorkers == 1 { return laplacianSerial(f, x, formula.Stencil, step, originKnown, originValue) } return laplacianConcurrent(nWorkers, evals, f, x, formula.Stencil, step, originKnown, originValue) } func laplacianSerial(f func(x []float64) float64, x []float64, stencil []Point, step float64, originKnown bool, originValue float64) float64 { n := len(x) xCopy := make([]float64, n) fo := func() float64 { // Copy x in case it is modified during the call. copy(xCopy, x) return f(x) } is2 := 1 / (step * step) origin := getOrigin(originKnown, originValue, fo, stencil) var laplacian float64 for i := 0; i < n; i++ { for _, pt := range stencil { var v float64 if pt.Loc == 0 { v = origin } else { // Copying the data anew has two benefits. First, it // avoids floating point issues where adding and then // subtracting the step don't return to the exact same // location. Secondly, it protects against the function // modifying the input data. copy(xCopy, x) xCopy[i] += pt.Loc * step v = f(xCopy) } laplacian += v * pt.Coeff * is2 } } return laplacian } func laplacianConcurrent(nWorkers, evals int, f func(x []float64) float64, x []float64, stencil []Point, step float64, originKnown bool, originValue float64) float64 { type run struct { i int idx int result float64 } n := len(x) send := make(chan run, evals) ans := make(chan run, evals) var originWG sync.WaitGroup hasOrigin := usesOrigin(stencil) if hasOrigin { originWG.Add(1) // Launch worker to compute the origin. go func() { defer originWG.Done() xCopy := make([]float64, len(x)) copy(xCopy, x) originValue = f(xCopy) }() } var workerWG sync.WaitGroup // Launch workers. for i := 0; i < nWorkers; i++ { workerWG.Add(1) go func(send <-chan run, ans chan<- run) { defer workerWG.Done() xCopy := make([]float64, len(x)) for r := range send { if stencil[r.idx].Loc == 0 { originWG.Wait() r.result = originValue } else { // See laplacianSerial for comment on the copy. copy(xCopy, x) xCopy[r.i] += stencil[r.idx].Loc * step r.result = f(xCopy) } ans <- r } }(send, ans) } // Launch the distributor, which sends all of runs. go func(send chan<- run) { for i := 0; i < n; i++ { for idx := range stencil { send <- run{ i: i, idx: idx, } } } close(send) // Wait for all the workers to quit, then close the ans channel. workerWG.Wait() close(ans) }(send) // Read in the results. is2 := 1 / (step * step) var laplacian float64 for r := range ans { laplacian += r.result * stencil[r.idx].Coeff * is2 } return laplacian }