mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00
Add Laplacian and CrossLaplacian difference functions (#154)
* Add Laplacian and CrossLaplacian difference functions * use usesOrigin
This commit is contained in:
158
diff/fd/laplacian.go
Normal file
158
diff/fd/laplacian.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// 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
|
||||
}
|
Reference in New Issue
Block a user