forked from gonum/gonum
/
local.go
146 lines (134 loc) · 4.38 KB
/
local.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright ©2014 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package optimize
import (
"math"
"github.com/ArkaGPL/gonum/floats"
)
// localOptimizer is a helper type for running an optimization using a LocalMethod.
type localOptimizer struct{}
// run controls the optimization run for a localMethod. The calling method
// must close the operation channel at the conclusion of the optimization. This
// provides a happens before relationship between the return of status and the
// closure of operation, and thus a call to method.Status (if necessary).
func (l localOptimizer) run(method localMethod, gradThresh float64, operation chan<- Task, result <-chan Task, tasks []Task) (Status, error) {
// Local methods start with a fully-specified initial location.
task := tasks[0]
task = l.initialLocation(operation, result, task, method)
if task.Op == PostIteration {
l.finish(operation, result)
return NotTerminated, nil
}
status, err := l.checkStartingLocation(task, gradThresh)
if err != nil {
l.finishMethodDone(operation, result, task)
return status, err
}
// Send a major iteration with the starting location.
task.Op = MajorIteration
operation <- task
task = <-result
if task.Op == PostIteration {
l.finish(operation, result)
return NotTerminated, nil
}
op, err := method.initLocal(task.Location)
if err != nil {
l.finishMethodDone(operation, result, task)
return Failure, err
}
task.Op = op
operation <- task
Loop:
for {
r := <-result
switch r.Op {
case PostIteration:
break Loop
case MajorIteration:
// The last operation was a MajorIteration. Check if the gradient
// is below the threshold.
if status := l.checkGradientConvergence(r.Gradient, gradThresh); status != NotTerminated {
l.finishMethodDone(operation, result, task)
return GradientThreshold, nil
}
fallthrough
default:
op, err := method.iterateLocal(r.Location)
if err != nil {
l.finishMethodDone(operation, result, r)
return Failure, err
}
r.Op = op
operation <- r
}
}
l.finish(operation, result)
return NotTerminated, nil
}
// initialOperation returns the Operation needed to fill the initial location
// based on the needs of the method and the values already supplied.
func (localOptimizer) initialOperation(task Task, n needser) Operation {
var newOp Operation
op := task.Op
if op&FuncEvaluation == 0 {
newOp |= FuncEvaluation
}
needs := n.needs()
if needs.Gradient && op&GradEvaluation == 0 {
newOp |= GradEvaluation
}
if needs.Hessian && op&HessEvaluation == 0 {
newOp |= HessEvaluation
}
return newOp
}
// initialLocation fills the initial location based on the needs of the method.
// The task passed to initialLocation should be the first task sent in RunGlobal.
func (l localOptimizer) initialLocation(operation chan<- Task, result <-chan Task, task Task, needs needser) Task {
task.Op = l.initialOperation(task, needs)
operation <- task
return <-result
}
func (l localOptimizer) checkStartingLocation(task Task, gradThresh float64) (Status, error) {
if math.IsInf(task.F, 1) || math.IsNaN(task.F) {
return Failure, ErrFunc(task.F)
}
for i, v := range task.Gradient {
if math.IsInf(v, 0) || math.IsNaN(v) {
return Failure, ErrGrad{Grad: v, Index: i}
}
}
status := l.checkGradientConvergence(task.Gradient, gradThresh)
return status, nil
}
func (localOptimizer) checkGradientConvergence(gradient []float64, gradThresh float64) Status {
if gradient == nil || math.IsNaN(gradThresh) {
return NotTerminated
}
if gradThresh == 0 {
gradThresh = defaultGradientAbsTol
}
if norm := floats.Norm(gradient, math.Inf(1)); norm < gradThresh {
return GradientThreshold
}
return NotTerminated
}
// finish completes the channel operations to finish an optimization.
func (localOptimizer) finish(operation chan<- Task, result <-chan Task) {
// Guarantee that result is closed before operation is closed.
for range result {
}
}
// finishMethodDone sends a MethodDone signal on operation, reads the result,
// and completes the channel operations to finish an optimization.
func (l localOptimizer) finishMethodDone(operation chan<- Task, result <-chan Task, task Task) {
task.Op = MethodDone
operation <- task
task = <-result
if task.Op != PostIteration {
panic("optimize: task should have returned post iteration")
}
l.finish(operation, result)
}