mirror of
https://github.com/gonum/gonum.git
synced 2025-09-28 20:12:08 +08:00

Changes made in dsp/fourier/internal/fftpack break the formatting used there, so these are reverted. There will be complaints in CI. [git-generate] gofmt -w . go generate gonum.org/v1/gonum/blas go generate gonum.org/v1/gonum/blas/gonum go generate gonum.org/v1/gonum/unit go generate gonum.org/v1/gonum/unit/constant go generate gonum.org/v1/gonum/graph/formats/dot go generate gonum.org/v1/gonum/graph/formats/rdf go generate gonum.org/v1/gonum/stat/card git checkout -- dsp/fourier/internal/fftpack
261 lines
6.8 KiB
Go
261 lines
6.8 KiB
Go
// Copyright ©2015 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 functions
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
)
|
||
|
||
// MinimalSurface implements a finite element approximation to a minimal
|
||
// surface problem: determine the surface with minimal area and given boundary
|
||
// values in a unit square centered at the origin.
|
||
//
|
||
// References:
|
||
//
|
||
// Averick, M.B., Carter, R.G., Moré, J.J., Xue, G.-L.: The Minpack-2 Test
|
||
// Problem Collection. Preprint MCS-P153-0692, Argonne National Laboratory (1992)
|
||
type MinimalSurface struct {
|
||
bottom, top []float64
|
||
left, right []float64
|
||
origin, step [2]float64
|
||
}
|
||
|
||
// NewMinimalSurface creates a new discrete minimal surface problem and
|
||
// precomputes its boundary values. The problem is discretized on a rectilinear
|
||
// grid with nx×ny nodes which means that the problem dimension is (nx-2)(ny-2).
|
||
func NewMinimalSurface(nx, ny int) *MinimalSurface {
|
||
ms := &MinimalSurface{
|
||
bottom: make([]float64, nx),
|
||
top: make([]float64, nx),
|
||
left: make([]float64, ny),
|
||
right: make([]float64, ny),
|
||
origin: [2]float64{-0.5, -0.5},
|
||
step: [2]float64{1 / float64(nx-1), 1 / float64(ny-1)},
|
||
}
|
||
|
||
ms.initBoundary(ms.bottom, ms.origin[0], ms.origin[1], ms.step[0], 0)
|
||
startY := ms.origin[1] + float64(ny-1)*ms.step[1]
|
||
ms.initBoundary(ms.top, ms.origin[0], startY, ms.step[0], 0)
|
||
ms.initBoundary(ms.left, ms.origin[0], ms.origin[1], 0, ms.step[1])
|
||
startX := ms.origin[0] + float64(nx-1)*ms.step[0]
|
||
ms.initBoundary(ms.right, startX, ms.origin[1], 0, ms.step[1])
|
||
|
||
return ms
|
||
}
|
||
|
||
// Func returns the area of the surface represented by the vector x.
|
||
func (ms *MinimalSurface) Func(x []float64) (area float64) {
|
||
nx, ny := ms.Dims()
|
||
if len(x) != (nx-2)*(ny-2) {
|
||
panic("functions: problem size mismatch")
|
||
}
|
||
|
||
hx, hy := ms.Steps()
|
||
for j := 0; j < ny-1; j++ {
|
||
for i := 0; i < nx-1; i++ {
|
||
vLL := ms.at(i, j, x)
|
||
vLR := ms.at(i+1, j, x)
|
||
vUL := ms.at(i, j+1, x)
|
||
vUR := ms.at(i+1, j+1, x)
|
||
|
||
dvLdx := (vLR - vLL) / hx
|
||
dvLdy := (vUL - vLL) / hy
|
||
dvUdx := (vUR - vUL) / hx
|
||
dvUdy := (vUR - vLR) / hy
|
||
|
||
fL := math.Sqrt(1 + dvLdx*dvLdx + dvLdy*dvLdy)
|
||
fU := math.Sqrt(1 + dvUdx*dvUdx + dvUdy*dvUdy)
|
||
area += fL + fU
|
||
}
|
||
}
|
||
area *= 0.5 * hx * hy
|
||
return area
|
||
}
|
||
|
||
// Grad evaluates the area gradient of the surface represented by the vector.
|
||
func (ms *MinimalSurface) Grad(grad, x []float64) []float64 {
|
||
nx, ny := ms.Dims()
|
||
if len(x) != (nx-2)*(ny-2) {
|
||
panic("functions: problem size mismatch")
|
||
}
|
||
if grad == nil {
|
||
grad = make([]float64, len(x))
|
||
}
|
||
if len(x) != len(grad) {
|
||
panic("functions: unexpected size mismatch")
|
||
}
|
||
|
||
for i := range grad {
|
||
grad[i] = 0
|
||
}
|
||
hx, hy := ms.Steps()
|
||
for j := 0; j < ny-1; j++ {
|
||
for i := 0; i < nx-1; i++ {
|
||
vLL := ms.at(i, j, x)
|
||
vLR := ms.at(i+1, j, x)
|
||
vUL := ms.at(i, j+1, x)
|
||
vUR := ms.at(i+1, j+1, x)
|
||
|
||
dvLdx := (vLR - vLL) / hx
|
||
dvLdy := (vUL - vLL) / hy
|
||
dvUdx := (vUR - vUL) / hx
|
||
dvUdy := (vUR - vLR) / hy
|
||
|
||
fL := math.Sqrt(1 + dvLdx*dvLdx + dvLdy*dvLdy)
|
||
fU := math.Sqrt(1 + dvUdx*dvUdx + dvUdy*dvUdy)
|
||
|
||
if grad != nil {
|
||
if i > 0 {
|
||
if j > 0 {
|
||
grad[ms.index(i, j)] -= (dvLdx/hx + dvLdy/hy) / fL
|
||
}
|
||
if j < ny-2 {
|
||
grad[ms.index(i, j+1)] += (dvLdy/hy)/fL - (dvUdx/hx)/fU
|
||
}
|
||
}
|
||
if i < nx-2 {
|
||
if j > 0 {
|
||
grad[ms.index(i+1, j)] += (dvLdx/hx)/fL - (dvUdy/hy)/fU
|
||
}
|
||
if j < ny-2 {
|
||
grad[ms.index(i+1, j+1)] += (dvUdx/hx + dvUdy/hy) / fU
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
cellSize := 0.5 * hx * hy
|
||
for i := range grad {
|
||
grad[i] *= cellSize
|
||
}
|
||
return grad
|
||
}
|
||
|
||
// InitX returns a starting location for the minimization problem. Length of
|
||
// the returned slice is (nx-2)(ny-2).
|
||
func (ms *MinimalSurface) InitX() []float64 {
|
||
nx, ny := ms.Dims()
|
||
x := make([]float64, (nx-2)*(ny-2))
|
||
for j := 1; j < ny-1; j++ {
|
||
for i := 1; i < nx-1; i++ {
|
||
x[ms.index(i, j)] = (ms.left[j] + ms.bottom[i]) / 2
|
||
}
|
||
}
|
||
return x
|
||
}
|
||
|
||
// ExactX returns the exact solution to the _continuous_ minimization problem
|
||
// projected on the interior nodes of the grid. Length of the returned slice is
|
||
// (nx-2)(ny-2).
|
||
func (ms *MinimalSurface) ExactX() []float64 {
|
||
nx, ny := ms.Dims()
|
||
v := make([]float64, (nx-2)*(ny-2))
|
||
for j := 1; j < ny-1; j++ {
|
||
for i := 1; i < nx-1; i++ {
|
||
v[ms.index(i, j)] = ms.ExactSolution(ms.x(i), ms.y(j))
|
||
}
|
||
}
|
||
return v
|
||
}
|
||
|
||
// ExactSolution returns the value of the exact solution to the minimal surface
|
||
// problem at (x,y). The exact solution is
|
||
//
|
||
// F_exact(x,y) = U^2(x,y) - V^2(x,y),
|
||
//
|
||
// where U and V are the unique solutions to the equations
|
||
//
|
||
// x = u + uv^2 - u^3/3,
|
||
// y = -v - u^2v + v^3/3.
|
||
func (ms *MinimalSurface) ExactSolution(x, y float64) float64 {
|
||
var u = [2]float64{x, -y}
|
||
var f [2]float64
|
||
var jac [2][2]float64
|
||
for k := 0; k < 100; k++ {
|
||
f[0] = u[0] + u[0]*u[1]*u[1] - u[0]*u[0]*u[0]/3 - x
|
||
f[1] = -u[1] - u[0]*u[0]*u[1] + u[1]*u[1]*u[1]/3 - y
|
||
fNorm := math.Hypot(f[0], f[1])
|
||
if fNorm < 1e-13 {
|
||
break
|
||
}
|
||
jac[0][0] = 1 + u[1]*u[1] - u[0]*u[0]
|
||
jac[0][1] = 2 * u[0] * u[1]
|
||
jac[1][0] = -2 * u[0] * u[1]
|
||
jac[1][1] = -1 - u[0]*u[0] + u[1]*u[1]
|
||
det := jac[0][0]*jac[1][1] - jac[0][1]*jac[1][0]
|
||
u[0] -= (jac[1][1]*f[0] - jac[0][1]*f[1]) / det
|
||
u[1] -= (jac[0][0]*f[1] - jac[1][0]*f[0]) / det
|
||
}
|
||
return u[0]*u[0] - u[1]*u[1]
|
||
}
|
||
|
||
// Dims returns the size of the underlying rectilinear grid.
|
||
func (ms *MinimalSurface) Dims() (nx, ny int) {
|
||
return len(ms.bottom), len(ms.left)
|
||
}
|
||
|
||
// Steps returns the spatial step sizes of the underlying rectilinear grid.
|
||
func (ms *MinimalSurface) Steps() (hx, hy float64) {
|
||
return ms.step[0], ms.step[1]
|
||
}
|
||
|
||
func (ms *MinimalSurface) x(i int) float64 {
|
||
return ms.origin[0] + float64(i)*ms.step[0]
|
||
}
|
||
|
||
func (ms *MinimalSurface) y(j int) float64 {
|
||
return ms.origin[1] + float64(j)*ms.step[1]
|
||
}
|
||
|
||
func (ms *MinimalSurface) at(i, j int, x []float64) float64 {
|
||
nx, ny := ms.Dims()
|
||
if i < 0 || i >= nx {
|
||
panic(fmt.Sprintf("node [%v,%v] not on grid", i, j))
|
||
}
|
||
if j < 0 || j >= ny {
|
||
panic(fmt.Sprintf("node [%v,%v] not on grid", i, j))
|
||
}
|
||
|
||
if i == 0 {
|
||
return ms.left[j]
|
||
}
|
||
if j == 0 {
|
||
return ms.bottom[i]
|
||
}
|
||
if i == nx-1 {
|
||
return ms.right[j]
|
||
}
|
||
if j == ny-1 {
|
||
return ms.top[i]
|
||
}
|
||
return x[ms.index(i, j)]
|
||
}
|
||
|
||
// index maps an interior grid node (i, j) to a one-dimensional index and
|
||
// returns it.
|
||
func (ms *MinimalSurface) index(i, j int) int {
|
||
nx, ny := ms.Dims()
|
||
if i <= 0 || i >= nx-1 {
|
||
panic(fmt.Sprintf("[%v,%v] is not an interior node", i, j))
|
||
}
|
||
if j <= 0 || j >= ny-1 {
|
||
panic(fmt.Sprintf("[%v,%v] is not an interior node", i, j))
|
||
}
|
||
|
||
return i - 1 + (j-1)*(nx-2)
|
||
}
|
||
|
||
// initBoundary initializes with the exact solution the boundary b whose i-th
|
||
// element b[i] is located at [startX+i×hx, startY+i×hy].
|
||
func (ms *MinimalSurface) initBoundary(b []float64, startX, startY, hx, hy float64) {
|
||
for i := range b {
|
||
x := startX + float64(i)*hx
|
||
y := startY + float64(i)*hy
|
||
b[i] = ms.ExactSolution(x, y)
|
||
}
|
||
}
|