optimize: unify Gradient and Hessian API behaviour
MODIFY
optimize/functions/functions.go
optimize/functions/functions.go
@@ -36,13 +36,10 @@ func (Beale) Func(x []float64) float64 {
return f1*f1 + f2*f2 + f3*f3
}
-func (Beale) Grad(grad, x []float64) []float64 {
+func (Beale) Grad(grad, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -57,16 +54,13 @@ func (Beale) Grad(grad, x []float64) []float64 {
grad[0] = -2 * (f1*t1 + f2*t2 + f3*t3)
grad[1] = 2 * x[0] * (f1 + 2*f2*x[1] + 3*f3*x[1]*x[1])
- return grad
}
func (Beale) Hess(dst *mat.SymDense, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -119,13 +113,10 @@ func (BiggsEXP2) Func(x []float64) (sum float64) {
return sum
}
-func (BiggsEXP2) Grad(grad, x []float64) []float64 {
+func (BiggsEXP2) Grad(grad, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -144,7 +135,6 @@ func (BiggsEXP2) Grad(grad, x []float64) []float64 {
grad[0] += 2 * f * dfdx0
grad[1] += 2 * f * dfdx1
}
- return grad
}
func (BiggsEXP2) Minima() []Minimum {
@@ -181,13 +171,10 @@ func (BiggsEXP3) Func(x []float64) (sum float64) {
return sum
}
-func (BiggsEXP3) Grad(grad, x []float64) []float64 {
+func (BiggsEXP3) Grad(grad, x []float64) {
if len(x) != 3 {
panic("dimension of the problem must be 3")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -208,7 +195,6 @@ func (BiggsEXP3) Grad(grad, x []float64) []float64 {
grad[1] += 2 * f * dfdx1
grad[2] += 2 * f * dfdx2
}
- return grad
}
func (BiggsEXP3) Minima() []Minimum {
@@ -245,13 +231,10 @@ func (BiggsEXP4) Func(x []float64) (sum float64) {
return sum
}
-func (BiggsEXP4) Grad(grad, x []float64) []float64 {
+func (BiggsEXP4) Grad(grad, x []float64) {
if len(x) != 4 {
panic("dimension of the problem must be 4")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -274,7 +257,6 @@ func (BiggsEXP4) Grad(grad, x []float64) []float64 {
grad[2] += 2 * f * dfdx2
grad[3] += 2 * f * dfdx3
}
- return grad
}
func (BiggsEXP4) Minima() []Minimum {
@@ -311,13 +293,10 @@ func (BiggsEXP5) Func(x []float64) (sum float64) {
return sum
}
-func (BiggsEXP5) Grad(grad, x []float64) []float64 {
+func (BiggsEXP5) Grad(grad, x []float64) {
if len(x) != 5 {
panic("dimension of the problem must be 5")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -342,7 +321,6 @@ func (BiggsEXP5) Grad(grad, x []float64) []float64 {
grad[3] += 2 * f * dfdx3
grad[4] += 2 * f * dfdx4
}
- return grad
}
func (BiggsEXP5) Minima() []Minimum {
@@ -382,13 +360,10 @@ func (BiggsEXP6) Func(x []float64) (sum float64) {
return sum
}
-func (BiggsEXP6) Grad(grad, x []float64) []float64 {
+func (BiggsEXP6) Grad(grad, x []float64) {
if len(x) != 6 {
panic("dimension of the problem must be 6")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -415,7 +390,6 @@ func (BiggsEXP6) Grad(grad, x []float64) []float64 {
grad[4] += 2 * f * dfdx4
grad[5] += 2 * f * dfdx5
}
- return grad
}
func (BiggsEXP6) Minima() []Minimum {
@@ -466,13 +440,10 @@ func (Box3D) Func(x []float64) (sum float64) {
return sum
}
-func (Box3D) Grad(grad, x []float64) []float64 {
+func (Box3D) Grad(grad, x []float64) {
if len(x) != 3 {
panic("dimension of the problem must be 3")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -488,7 +459,6 @@ func (Box3D) Grad(grad, x []float64) []float64 {
grad[1] += -2 * f * c * math.Exp(c*x[1])
grad[2] += -2 * f * y
}
- return grad
}
func (Box3D) Minima() []Minimum {
@@ -573,13 +543,10 @@ func (BrownBadlyScaled) Func(x []float64) float64 {
return f1*f1 + f2*f2 + f3*f3
}
-func (BrownBadlyScaled) Grad(grad, x []float64) []float64 {
+func (BrownBadlyScaled) Grad(grad, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -589,16 +556,13 @@ func (BrownBadlyScaled) Grad(grad, x []float64) []float64 {
f3 := x[0]*x[1] - 2
grad[0] = 2*f1 + 2*f3*x[1]
grad[1] = 2*f2 + 2*f3*x[0]
- return grad
}
func (BrownBadlyScaled) Hess(dst *mat.SymDense, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -648,13 +612,10 @@ func (BrownAndDennis) Func(x []float64) (sum float64) {
return sum
}
-func (BrownAndDennis) Grad(grad, x []float64) []float64 {
+func (BrownAndDennis) Grad(grad, x []float64) {
if len(x) != 4 {
panic("dimension of the problem must be 4")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -672,16 +633,13 @@ func (BrownAndDennis) Grad(grad, x []float64) []float64 {
grad[2] += 4 * f * f2
grad[3] += 4 * f * f2 * math.Sin(c)
}
- return grad
}
func (BrownAndDennis) Hess(dst *mat.SymDense, x []float64) {
if len(x) != 4 {
panic("dimension of the problem must be 4")
}
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -758,13 +716,10 @@ func (ExtendedPowellSingular) Func(x []float64) (sum float64) {
return sum
}
-func (ExtendedPowellSingular) Grad(grad, x []float64) []float64 {
+func (ExtendedPowellSingular) Grad(grad, x []float64) {
if len(x)%4 != 0 {
panic("dimension of the problem must be a multiple of 4")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -781,7 +736,6 @@ func (ExtendedPowellSingular) Grad(grad, x []float64) []float64 {
grad[i+2] = 10*f2 - 8*f3*t1
grad[i+3] = -10*f2 - 40*f4*t2
}
- return grad
}
func (ExtendedPowellSingular) Minima() []Minimum {
@@ -826,10 +780,7 @@ func (ExtendedRosenbrock) Func(x []float64) (sum float64) {
return sum
}
-func (ExtendedRosenbrock) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (ExtendedRosenbrock) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -845,7 +796,6 @@ func (ExtendedRosenbrock) Grad(grad, x []float64) []float64 {
for i := 1; i < dim; i++ {
grad[i] += 200 * (x[i] - x[i-1]*x[i-1])
}
- return grad
}
func (ExtendedRosenbrock) Minima() []Minimum {
@@ -952,13 +902,10 @@ func (g Gaussian) Func(x []float64) (sum float64) {
return sum
}
-func (g Gaussian) Grad(grad, x []float64) []float64 {
+func (g Gaussian) Grad(grad, x []float64) {
if len(x) != 3 {
panic("dimension of the problem must be 3")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -976,7 +923,6 @@ func (g Gaussian) Grad(grad, x []float64) []float64 {
grad[1] -= f * e * d * x[0]
grad[2] += 2 * f * e * x[0] * x[1] * b
}
- return grad
}
func (Gaussian) Minima() []Minimum {
@@ -1018,13 +964,10 @@ func (GulfResearchAndDevelopment) Func(x []float64) (sum float64) {
return sum
}
-func (GulfResearchAndDevelopment) Grad(grad, x []float64) []float64 {
+func (GulfResearchAndDevelopment) Grad(grad, x []float64) {
if len(x) != 3 {
panic("dimension of the problem must be 3")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1046,7 +989,6 @@ func (GulfResearchAndDevelopment) Grad(grad, x []float64) []float64 {
grad[0] *= 2 / x[0]
grad[1] *= 2 * x[2]
grad[2] *= 2
- return grad
}
func (GulfResearchAndDevelopment) Minima() []Minimum {
@@ -1100,13 +1042,10 @@ func (HelicalValley) Func(x []float64) float64 {
return f1*f1 + f2*f2 + f3*f3
}
-func (HelicalValley) Grad(grad, x []float64) []float64 {
+func (HelicalValley) Grad(grad, x []float64) {
if len(x) != 3 {
panic("dimension of the problem must be 3")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1125,7 +1064,6 @@ func (HelicalValley) Grad(grad, x []float64) []float64 {
grad[0] = 200 * (5*s*q*x[1] + (h-1)*r*x[0])
grad[1] = 200 * (-5*s*q*x[0] + (h-1)*r*x[1])
grad[2] = 2 * (100*s + x[2])
- return grad
}
func (HelicalValley) Minima() []Minimum {
@@ -1183,10 +1121,7 @@ func (PenaltyI) Func(x []float64) (sum float64) {
return sum
}
-func (PenaltyI) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (PenaltyI) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1198,7 +1133,6 @@ func (PenaltyI) Grad(grad, x []float64) []float64 {
for i, v := range x {
grad[i] = 2 * (2*s*v + 1e-5*(v-1))
}
- return grad
}
func (PenaltyI) Minima() []Minimum {
@@ -1252,10 +1186,7 @@ func (PenaltyII) Func(x []float64) (sum float64) {
return sum
}
-func (PenaltyII) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (PenaltyII) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1279,7 +1210,6 @@ func (PenaltyII) Grad(grad, x []float64) []float64 {
grad[i] += 1e-5 * f * math.Exp(x[i]/10) / 5
}
grad[0] += 2 * (x[0] - 0.2)
- return grad
}
func (PenaltyII) Minima() []Minimum {
@@ -1325,13 +1255,10 @@ func (PowellBadlyScaled) Func(x []float64) float64 {
return f1*f1 + f2*f2
}
-func (PowellBadlyScaled) Grad(grad, x []float64) []float64 {
+func (PowellBadlyScaled) Grad(grad, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1340,16 +1267,13 @@ func (PowellBadlyScaled) Grad(grad, x []float64) []float64 {
f2 := math.Exp(-x[0]) + math.Exp(-x[1]) - 1.0001
grad[0] = 2 * (1e4*f1*x[1] - f2*math.Exp(-x[0]))
grad[1] = 2 * (1e4*f1*x[0] - f2*math.Exp(-x[1]))
- return grad
}
func (PowellBadlyScaled) Hess(dst *mat.SymDense, x []float64) {
if len(x) != 2 {
panic("dimension of the problem must be 2")
}
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -1401,10 +1325,7 @@ func (Trigonometric) Func(x []float64) (sum float64) {
return sum
}
-func (Trigonometric) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (Trigonometric) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1424,7 +1345,6 @@ func (Trigonometric) Grad(grad, x []float64) []float64 {
for i, v := range x {
grad[i] += 2 * s2 * math.Sin(v)
}
- return grad
}
func (Trigonometric) Minima() []Minimum {
@@ -1477,10 +1397,7 @@ func (VariablyDimensioned) Func(x []float64) (sum float64) {
return sum
}
-func (VariablyDimensioned) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (VariablyDimensioned) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1492,7 +1409,6 @@ func (VariablyDimensioned) Grad(grad, x []float64) []float64 {
for i, v := range x {
grad[i] = 2 * (v - 1 + s*float64(i+1)*(1+2*s*s))
}
- return grad
}
func (VariablyDimensioned) Minima() []Minimum {
@@ -1565,10 +1481,7 @@ func (Watson) Func(x []float64) (sum float64) {
return sum
}
-func (Watson) Grad(grad, x []float64) []float64 {
- if grad == nil {
- grad = make([]float64, len(x))
- }
+func (Watson) Grad(grad, x []float64) {
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1604,14 +1517,11 @@ func (Watson) Grad(grad, x []float64) []float64 {
t := x[1] - x[0]*x[0] - 1
grad[0] += x[0] * (2 - 4*t)
grad[1] += 2 * t
- return grad
}
func (Watson) Hess(dst *mat.SymDense, x []float64) {
dim := len(x)
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -1709,13 +1619,10 @@ func (Wood) Func(x []float64) (sum float64) {
return 100*f1*f1 + f2*f2 + 90*f3*f3 + f4*f4 + 10*f5*f5 + 0.1*f6*f6
}
-func (Wood) Grad(grad, x []float64) []float64 {
+func (Wood) Grad(grad, x []float64) {
if len(x) != 4 {
panic("dimension of the problem must be 4")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1730,16 +1637,13 @@ func (Wood) Grad(grad, x []float64) []float64 {
grad[1] = 2 * (100*f1 + 10*f5 + 0.1*f6)
grad[2] = -2 * (180*f3*x[2] + f4)
grad[3] = 2 * (90*f3 + 10*f5 - 0.1*f6)
- return grad
}
func (Wood) Hess(dst *mat.SymDense, x []float64) {
if len(x) != 4 {
panic("dimension of the problem must be 4")
}
- if dst.IsZero() {
- *dst = *(dst.GrowSym(len(x)).(*mat.SymDense))
- } else if len(x) != dst.Symmetric() {
+ if len(x) != dst.Symmetric() {
panic("incorrect size of the Hessian")
}
@@ -1780,7 +1684,7 @@ func (ConcaveRight) Func(x []float64) float64 {
return -x[0] / (x[0]*x[0] + 2)
}
-func (ConcaveRight) Grad(grad, x []float64) []float64 {
+func (ConcaveRight) Grad(grad, x []float64) {
if len(x) != 1 {
panic("dimension of the problem must be 1")
}
@@ -1789,7 +1693,6 @@ func (ConcaveRight) Grad(grad, x []float64) []float64 {
}
xSqr := x[0] * x[0]
grad[0] = (xSqr - 2) / (xSqr + 2) / (xSqr + 2)
- return grad
}
// ConcaveLeft implements an univariate function that is concave to the left of
@@ -1807,18 +1710,14 @@ func (ConcaveLeft) Func(x []float64) float64 {
return math.Pow(x[0]+0.004, 4) * (x[0] - 1.996)
}
-func (ConcaveLeft) Grad(grad, x []float64) []float64 {
+func (ConcaveLeft) Grad(grad, x []float64) {
if len(x) != 1 {
panic("dimension of the problem must be 1")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
grad[0] = math.Pow(x[0]+0.004, 3) * (5*x[0] - 7.98)
- return grad
}
// Plassmann implements an univariate oscillatory function where the value of L
@@ -1855,13 +1754,10 @@ func (f Plassmann) Func(x []float64) float64 {
return r
}
-func (f Plassmann) Grad(grad, x []float64) []float64 {
+func (f Plassmann) Grad(grad, x []float64) {
if len(x) != 1 {
panic("dimension of the problem must be 1")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1877,7 +1773,6 @@ func (f Plassmann) Grad(grad, x []float64) []float64 {
default: // a > 1+b
grad[0]++
}
- return grad
}
// YanaiOzawaKaneko is an univariate convex function where the values of Beta1
@@ -1908,13 +1803,10 @@ func (f YanaiOzawaKaneko) Func(x []float64) float64 {
return g1*math.Sqrt((a-1)*(a-1)+b2*b2) + g2*math.Sqrt(a*a+b1*b1)
}
-func (f YanaiOzawaKaneko) Grad(grad, x []float64) []float64 {
+func (f YanaiOzawaKaneko) Grad(grad, x []float64) {
if len(x) != 1 {
panic("dimension of the problem must be 1")
}
- if grad == nil {
- grad = make([]float64, len(x))
- }
if len(x) != len(grad) {
panic("incorrect size of the gradient")
}
@@ -1924,5 +1816,4 @@ func (f YanaiOzawaKaneko) Grad(grad, x []float64) []float64 {
g1 := math.Sqrt(1+b1*b1) - b1
g2 := math.Sqrt(1+b2*b2) - b2
grad[0] = g1*(a-1)/math.Sqrt(b2*b2+(a-1)*(a-1)) + g2*a/math.Sqrt(b1*b1+a*a)
- return grad
}

MODIFY
optimize/linesearcher_test.go
optimize/linesearcher_test.go
@@ -41,7 +41,7 @@ func TestBacktracking(t *testing.T) {
type funcGrader interface {
Func([]float64) float64
- Grad([]float64, []float64) []float64
+ Grad([]float64, []float64)
}
type linesearcherTest struct {

MODIFY
optimize/minimize.go
optimize/minimize.go
@@ -477,12 +477,20 @@ func evaluate(p *Problem, loc *Location, op Operation, x []float64) {
loc.F = p.Func(x)
}
if op&GradEvaluation != 0 {
- loc.Gradient = p.Grad(loc.Gradient, x)
+ // Make sure we have a destination in which to place the gradient.
+ // TODO(kortschak): Consider making this a check of len(loc.Gradient) != 0
+ // to allow reuse of the slice.
+ if loc.Gradient == nil {
+ loc.Gradient = make([]float64, len(x))
+ }
+ p.Grad(loc.Gradient, x)
}
if op&HessEvaluation != 0 {
// Make sure we have a destination in which to place the Hessian.
+ // TODO(kortschak): Consider making this a check of loc.Hessian.IsZero()
+ // to allow reuse of the matrix.
if loc.Hessian == nil {
- loc.Hessian = &mat.SymDense{}
+ loc.Hessian = mat.NewSymDense(len(x), nil)
}
p.Hess(loc.Hessian, x)
}

MODIFY
optimize/types.go
optimize/types.go
@@ -124,13 +124,13 @@ type Problem struct {
Func func(x []float64) float64
// Grad evaluates the gradient at x and returns the result. Grad may use
- // (and return) the provided slice if it is non-nil, or must allocate a new
- // slice otherwise. Grad must not modify x.
- Grad func(grad []float64, x []float64) []float64
+ // the provided slice which will be the same length as x. Grad must not
+ // modify x.
+ Grad func(grad, x []float64)
// Hess evaluates the Hessian at x and stores the result in-place in hess.
- // Hess must not modify x. Hess must use the provided mat.SymDense, and
- // must resize it if it is zero-sized.
+ // Hess must not modify x. Hess may use the provided mat.SymDense which
+ // will have dimensions matching the length of x.
Hess func(hess *mat.SymDense, x []float64)
// Status reports the status of the objective function being optimized and any

optimize: unify Gradient and Hessian API behaviour