gonum.org/v1/gonum@v0.14.0/optimize/functions/validate.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  	"math"
     9  	"testing"
    10  
    11  	"gonum.org/v1/gonum/diff/fd"
    12  	"gonum.org/v1/gonum/floats"
    13  )
    14  
    15  // function represents an objective function.
    16  type function interface {
    17  	Func(x []float64) float64
    18  }
    19  
    20  type gradient interface {
    21  	Grad(grad, x []float64) []float64
    22  }
    23  
    24  // minimumer is an objective function that can also provide information about
    25  // its minima.
    26  type minimumer interface {
    27  	function
    28  
    29  	// Minima returns _known_ minima of the function.
    30  	Minima() []Minimum
    31  }
    32  
    33  // Minimum represents information about an optimal location of a function.
    34  type Minimum struct {
    35  	// X is the location of the minimum. X may not be nil.
    36  	X []float64
    37  	// F is the value of the objective function at X.
    38  	F float64
    39  	// Global indicates if the location is a global minimum.
    40  	Global bool
    41  }
    42  
    43  type funcTest struct {
    44  	X []float64
    45  
    46  	// F is the expected function value at X.
    47  	F float64
    48  	// Gradient is the expected gradient at X. If nil, it is not evaluated.
    49  	Gradient []float64
    50  }
    51  
    52  // TODO(vladimir-ch): Decide and implement an exported testing function:
    53  // func Test(f Function, ??? ) ??? {
    54  // }
    55  
    56  const (
    57  	defaultTol       = 1e-12
    58  	defaultGradTol   = 1e-9
    59  	defaultFDGradTol = 1e-5
    60  )
    61  
    62  // testFunction checks that the function can evaluate itself (and its gradient)
    63  // correctly.
    64  func testFunction(f function, ftests []funcTest, t *testing.T) {
    65  	// Make a copy of tests because we may append to the slice.
    66  	tests := make([]funcTest, len(ftests))
    67  	copy(tests, ftests)
    68  
    69  	// Get information about the function.
    70  	fMinima, isMinimumer := f.(minimumer)
    71  	fGradient, isGradient := f.(gradient)
    72  
    73  	// If the function is a Minimumer, append its minima to the tests.
    74  	if isMinimumer {
    75  		for _, minimum := range fMinima.Minima() {
    76  			// Allocate gradient only if the function can evaluate it.
    77  			var grad []float64
    78  			if isGradient {
    79  				grad = make([]float64, len(minimum.X))
    80  			}
    81  			tests = append(tests, funcTest{
    82  				X:        minimum.X,
    83  				F:        minimum.F,
    84  				Gradient: grad,
    85  			})
    86  		}
    87  	}
    88  
    89  	for i, test := range tests {
    90  		F := f.Func(test.X)
    91  
    92  		// Check that the function value is as expected.
    93  		if math.Abs(F-test.F) > defaultTol {
    94  			t.Errorf("Test #%d: function value given by Func is incorrect. Want: %v, Got: %v",
    95  				i, test.F, F)
    96  		}
    97  
    98  		if test.Gradient == nil {
    99  			continue
   100  		}
   101  
   102  		// Evaluate the finite difference gradient.
   103  		fdGrad := fd.Gradient(nil, f.Func, test.X, &fd.Settings{
   104  			Formula: fd.Central,
   105  			Step:    1e-6,
   106  		})
   107  
   108  		// Check that the finite difference and expected gradients match.
   109  		if !floats.EqualApprox(fdGrad, test.Gradient, defaultFDGradTol) {
   110  			dist := floats.Distance(fdGrad, test.Gradient, math.Inf(1))
   111  			t.Errorf("Test #%d: numerical and expected gradients do not match. |fdGrad - WantGrad|_∞ = %v",
   112  				i, dist)
   113  		}
   114  
   115  		// If the function is a Gradient, check that it computes the gradient correctly.
   116  		if isGradient {
   117  			grad := make([]float64, len(test.Gradient))
   118  			fGradient.Grad(grad, test.X)
   119  
   120  			if !floats.EqualApprox(grad, test.Gradient, defaultGradTol) {
   121  				dist := floats.Distance(grad, test.Gradient, math.Inf(1))
   122  				t.Errorf("Test #%d: gradient given by Grad is incorrect. |grad - WantGrad|_∞ = %v",
   123  					i, dist)
   124  			}
   125  		}
   126  	}
   127  }