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