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  }