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 }