gonum.org/v1/gonum@v0.14.0/optimize/functions/minsurf.go (about)

     1  // Copyright ©2015 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 functions
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  )
    11  
    12  // MinimalSurface implements a finite element approximation to a minimal
    13  // surface problem: determine the surface with minimal area and given boundary
    14  // values in a unit square centered at the origin.
    15  //
    16  // References:
    17  //
    18  //	Averick, M.B., Carter, R.G., Moré, J.J., Xue, G.-L.: The Minpack-2 Test
    19  //	Problem Collection. Preprint MCS-P153-0692, Argonne National Laboratory (1992)
    20  type MinimalSurface struct {
    21  	bottom, top  []float64
    22  	left, right  []float64
    23  	origin, step [2]float64
    24  }
    25  
    26  // NewMinimalSurface creates a new discrete minimal surface problem and
    27  // precomputes its boundary values. The problem is discretized on a rectilinear
    28  // grid with nx×ny nodes which means that the problem dimension is (nx-2)(ny-2).
    29  func NewMinimalSurface(nx, ny int) *MinimalSurface {
    30  	ms := &MinimalSurface{
    31  		bottom: make([]float64, nx),
    32  		top:    make([]float64, nx),
    33  		left:   make([]float64, ny),
    34  		right:  make([]float64, ny),
    35  		origin: [2]float64{-0.5, -0.5},
    36  		step:   [2]float64{1 / float64(nx-1), 1 / float64(ny-1)},
    37  	}
    38  
    39  	ms.initBoundary(ms.bottom, ms.origin[0], ms.origin[1], ms.step[0], 0)
    40  	startY := ms.origin[1] + float64(ny-1)*ms.step[1]
    41  	ms.initBoundary(ms.top, ms.origin[0], startY, ms.step[0], 0)
    42  	ms.initBoundary(ms.left, ms.origin[0], ms.origin[1], 0, ms.step[1])
    43  	startX := ms.origin[0] + float64(nx-1)*ms.step[0]
    44  	ms.initBoundary(ms.right, startX, ms.origin[1], 0, ms.step[1])
    45  
    46  	return ms
    47  }
    48  
    49  // Func returns the area of the surface represented by the vector x.
    50  func (ms *MinimalSurface) Func(x []float64) (area float64) {
    51  	nx, ny := ms.Dims()
    52  	if len(x) != (nx-2)*(ny-2) {
    53  		panic("functions: problem size mismatch")
    54  	}
    55  
    56  	hx, hy := ms.Steps()
    57  	for j := 0; j < ny-1; j++ {
    58  		for i := 0; i < nx-1; i++ {
    59  			vLL := ms.at(i, j, x)
    60  			vLR := ms.at(i+1, j, x)
    61  			vUL := ms.at(i, j+1, x)
    62  			vUR := ms.at(i+1, j+1, x)
    63  
    64  			dvLdx := (vLR - vLL) / hx
    65  			dvLdy := (vUL - vLL) / hy
    66  			dvUdx := (vUR - vUL) / hx
    67  			dvUdy := (vUR - vLR) / hy
    68  
    69  			fL := math.Sqrt(1 + dvLdx*dvLdx + dvLdy*dvLdy)
    70  			fU := math.Sqrt(1 + dvUdx*dvUdx + dvUdy*dvUdy)
    71  			area += fL + fU
    72  		}
    73  	}
    74  	area *= 0.5 * hx * hy
    75  	return area
    76  }
    77  
    78  // Grad evaluates the area gradient of the surface represented by the vector.
    79  func (ms *MinimalSurface) Grad(grad, x []float64) []float64 {
    80  	nx, ny := ms.Dims()
    81  	if len(x) != (nx-2)*(ny-2) {
    82  		panic("functions: problem size mismatch")
    83  	}
    84  	if grad == nil {
    85  		grad = make([]float64, len(x))
    86  	}
    87  	if len(x) != len(grad) {
    88  		panic("functions: unexpected size mismatch")
    89  	}
    90  
    91  	for i := range grad {
    92  		grad[i] = 0
    93  	}
    94  	hx, hy := ms.Steps()
    95  	for j := 0; j < ny-1; j++ {
    96  		for i := 0; i < nx-1; i++ {
    97  			vLL := ms.at(i, j, x)
    98  			vLR := ms.at(i+1, j, x)
    99  			vUL := ms.at(i, j+1, x)
   100  			vUR := ms.at(i+1, j+1, x)
   101  
   102  			dvLdx := (vLR - vLL) / hx
   103  			dvLdy := (vUL - vLL) / hy
   104  			dvUdx := (vUR - vUL) / hx
   105  			dvUdy := (vUR - vLR) / hy
   106  
   107  			fL := math.Sqrt(1 + dvLdx*dvLdx + dvLdy*dvLdy)
   108  			fU := math.Sqrt(1 + dvUdx*dvUdx + dvUdy*dvUdy)
   109  
   110  			if grad != nil {
   111  				if i > 0 {
   112  					if j > 0 {
   113  						grad[ms.index(i, j)] -= (dvLdx/hx + dvLdy/hy) / fL
   114  					}
   115  					if j < ny-2 {
   116  						grad[ms.index(i, j+1)] += (dvLdy/hy)/fL - (dvUdx/hx)/fU
   117  					}
   118  				}
   119  				if i < nx-2 {
   120  					if j > 0 {
   121  						grad[ms.index(i+1, j)] += (dvLdx/hx)/fL - (dvUdy/hy)/fU
   122  					}
   123  					if j < ny-2 {
   124  						grad[ms.index(i+1, j+1)] += (dvUdx/hx + dvUdy/hy) / fU
   125  					}
   126  				}
   127  			}
   128  		}
   129  
   130  	}
   131  	cellSize := 0.5 * hx * hy
   132  	for i := range grad {
   133  		grad[i] *= cellSize
   134  	}
   135  	return grad
   136  }
   137  
   138  // InitX returns a starting location for the minimization problem. Length of
   139  // the returned slice is (nx-2)(ny-2).
   140  func (ms *MinimalSurface) InitX() []float64 {
   141  	nx, ny := ms.Dims()
   142  	x := make([]float64, (nx-2)*(ny-2))
   143  	for j := 1; j < ny-1; j++ {
   144  		for i := 1; i < nx-1; i++ {
   145  			x[ms.index(i, j)] = (ms.left[j] + ms.bottom[i]) / 2
   146  		}
   147  	}
   148  	return x
   149  }
   150  
   151  // ExactX returns the exact solution to the _continuous_ minimization problem
   152  // projected on the interior nodes of the grid. Length of the returned slice is
   153  // (nx-2)(ny-2).
   154  func (ms *MinimalSurface) ExactX() []float64 {
   155  	nx, ny := ms.Dims()
   156  	v := make([]float64, (nx-2)*(ny-2))
   157  	for j := 1; j < ny-1; j++ {
   158  		for i := 1; i < nx-1; i++ {
   159  			v[ms.index(i, j)] = ms.ExactSolution(ms.x(i), ms.y(j))
   160  		}
   161  	}
   162  	return v
   163  }
   164  
   165  // ExactSolution returns the value of the exact solution to the minimal surface
   166  // problem at (x,y). The exact solution is
   167  //
   168  //	F_exact(x,y) = U^2(x,y) - V^2(x,y),
   169  //
   170  // where U and V are the unique solutions to the equations
   171  //
   172  //	x =  u + uv^2 - u^3/3,
   173  //	y = -v - u^2v + v^3/3.
   174  func (ms *MinimalSurface) ExactSolution(x, y float64) float64 {
   175  	var u = [2]float64{x, -y}
   176  	var f [2]float64
   177  	var jac [2][2]float64
   178  	for k := 0; k < 100; k++ {
   179  		f[0] = u[0] + u[0]*u[1]*u[1] - u[0]*u[0]*u[0]/3 - x
   180  		f[1] = -u[1] - u[0]*u[0]*u[1] + u[1]*u[1]*u[1]/3 - y
   181  		fNorm := math.Hypot(f[0], f[1])
   182  		if fNorm < 1e-13 {
   183  			break
   184  		}
   185  		jac[0][0] = 1 + u[1]*u[1] - u[0]*u[0]
   186  		jac[0][1] = 2 * u[0] * u[1]
   187  		jac[1][0] = -2 * u[0] * u[1]
   188  		jac[1][1] = -1 - u[0]*u[0] + u[1]*u[1]
   189  		det := jac[0][0]*jac[1][1] - jac[0][1]*jac[1][0]
   190  		u[0] -= (jac[1][1]*f[0] - jac[0][1]*f[1]) / det
   191  		u[1] -= (jac[0][0]*f[1] - jac[1][0]*f[0]) / det
   192  	}
   193  	return u[0]*u[0] - u[1]*u[1]
   194  }
   195  
   196  // Dims returns the size of the underlying rectilinear grid.
   197  func (ms *MinimalSurface) Dims() (nx, ny int) {
   198  	return len(ms.bottom), len(ms.left)
   199  }
   200  
   201  // Steps returns the spatial step sizes of the underlying rectilinear grid.
   202  func (ms *MinimalSurface) Steps() (hx, hy float64) {
   203  	return ms.step[0], ms.step[1]
   204  }
   205  
   206  func (ms *MinimalSurface) x(i int) float64 {
   207  	return ms.origin[0] + float64(i)*ms.step[0]
   208  }
   209  
   210  func (ms *MinimalSurface) y(j int) float64 {
   211  	return ms.origin[1] + float64(j)*ms.step[1]
   212  }
   213  
   214  func (ms *MinimalSurface) at(i, j int, x []float64) float64 {
   215  	nx, ny := ms.Dims()
   216  	if i < 0 || i >= nx {
   217  		panic(fmt.Sprintf("node [%v,%v] not on grid", i, j))
   218  	}
   219  	if j < 0 || j >= ny {
   220  		panic(fmt.Sprintf("node [%v,%v] not on grid", i, j))
   221  	}
   222  
   223  	if i == 0 {
   224  		return ms.left[j]
   225  	}
   226  	if j == 0 {
   227  		return ms.bottom[i]
   228  	}
   229  	if i == nx-1 {
   230  		return ms.right[j]
   231  	}
   232  	if j == ny-1 {
   233  		return ms.top[i]
   234  	}
   235  	return x[ms.index(i, j)]
   236  }
   237  
   238  // index maps an interior grid node (i, j) to a one-dimensional index and
   239  // returns it.
   240  func (ms *MinimalSurface) index(i, j int) int {
   241  	nx, ny := ms.Dims()
   242  	if i <= 0 || i >= nx-1 {
   243  		panic(fmt.Sprintf("[%v,%v] is not an interior node", i, j))
   244  	}
   245  	if j <= 0 || j >= ny-1 {
   246  		panic(fmt.Sprintf("[%v,%v] is not an interior node", i, j))
   247  	}
   248  
   249  	return i - 1 + (j-1)*(nx-2)
   250  }
   251  
   252  // initBoundary initializes with the exact solution the boundary b whose i-th
   253  // element b[i] is located at [startX+i×hx, startY+i×hy].
   254  func (ms *MinimalSurface) initBoundary(b []float64, startX, startY, hx, hy float64) {
   255  	for i := range b {
   256  		x := startX + float64(i)*hx
   257  		y := startY + float64(i)*hy
   258  		b[i] = ms.ExactSolution(x, y)
   259  	}
   260  }