1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """Dogleg trust region optimization.
25
26 This file is part of the U{minfx optimisation library<https://sourceforge.net/projects/minfx>}.
27 """
28
29
30 from numpy import dot, float64, identity, outer, sort, sqrt
31 from numpy.linalg import inv
32 from re import match
33
34
35 from minfx.base_classes import Hessian_mods, Min, Trust_region
36 from minfx.bfgs import Bfgs
37 from minfx.newton import Newton
38
39
40 -def dogleg(func=None, dfunc=None, d2func=None, args=(), x0=None, min_options=(), func_tol=1e-25, grad_tol=None, maxiter=1e6, delta_max=1e10, delta0=1e5, eta=0.0001, mach_acc=1e-16, full_output=0, print_flag=0, print_prefix=""):
41 """Dogleg trust region algorithm.
42
43 Page 71 from 'Numerical Optimization' by Jorge Nocedal and Stephen J. Wright, 1999, 2nd ed. The dogleg method is defined by the trajectory p(tau)::
44
45 / tau . pU 0 <= tau <= 1,
46 p(tau) = <
47 \ pU + (tau - 1)(pB - pU), 1 <= tau <= 2.
48
49 where:
50
51 - tau is in [0, 2]
52 - pU is the unconstrained minimiser along the steepest descent direction.
53 - pB is the full step.
54
55 pU is defined by the formula::
56
57 gT.g
58 pU = - ------ g
59 gT.B.g
60
61 and pB by the formula::
62
63 pB = - B^(-1).g
64
65 If the full step is within the trust region it is taken. Otherwise the point at which the dogleg trajectory intersects the trust region is taken. This point can be found by solving the scalar quadratic equation::
66
67 ||pU + (tau - 1)(pB - pU)||^2 = delta^2
68 """
69
70 if print_flag:
71 if print_flag >= 2:
72 print(print_prefix)
73 print(print_prefix)
74 print(print_prefix + "Dogleg minimisation")
75 print(print_prefix + "~~~~~~~~~~~~~~~~~~~")
76 min = Dogleg(func, dfunc, d2func, args, x0, min_options, func_tol, grad_tol, maxiter, delta_max, delta0, eta, mach_acc, full_output, print_flag, print_prefix)
77 if min.init_failure:
78 print(print_prefix + "Initialisation of minimisation has failed.")
79 return None
80 results = min.minimise()
81 return results
82
83
84 -class Dogleg(Trust_region, Bfgs, Newton):
85 - def __init__(self, func, dfunc, d2func, args, x0, min_options, func_tol, grad_tol, maxiter, delta_max, delta0, eta, mach_acc, full_output, print_flag, print_prefix):
86 """Class for Dogleg trust region minimisation specific functions.
87
88 Unless you know what you are doing, you should call the function 'dogleg' rather than using this class.
89 """
90
91
92 self.func = func
93 self.dfunc = dfunc
94 self.d2func = d2func
95 self.args = args
96 self.xk = x0
97 self.func_tol = func_tol
98 self.grad_tol = grad_tol
99 self.maxiter = maxiter
100 self.full_output = full_output
101 self.print_flag = print_flag
102 self.print_prefix = print_prefix
103 self.mach_acc = mach_acc
104 self.delta_max = delta_max
105 self.delta = delta0
106 self.eta = eta
107
108
109 self.init_failure = 0
110
111
112 self.hessian_type_and_mod(min_options)
113 self.setup_hessian_mod()
114
115
116 self.f_count = 0
117 self.g_count = 0
118 self.h_count = 0
119
120
121 self.warning = None
122
123
124 self.n = len(self.xk)
125 self.I = identity(len(self.xk))
126
127
128 self.setup_conv_tests()
129
130
131 if self.hessian_type and match('[Bb][Ff][Gg][Ss]', self.hessian_type):
132 self.setup_bfgs()
133 self.specific_update = self.update_bfgs
134 self.hessian_update = self.hessian_update_bfgs
135 self.d2fk = inv(self.Hk)
136 elif self.hessian_type and match('[Nn]ewton', self.hessian_type):
137 self.setup_newton()
138 self.specific_update = self.update_newton
139 self.hessian_update = self.hessian_update_newton
140
141
143 """The dogleg algorithm."""
144
145
146 try:
147 pB = -dot(self.Hk, self.dfk)
148 except AttributeError:
149
150 d2fk_backup = 1.0 * self.d2fk
151
152
153 pB = self.get_pk()
154
155
156 self.d2fk = d2fk_backup
157 norm_pB = sqrt(dot(pB, pB))
158
159
160 if norm_pB <= self.delta:
161 return pB
162
163
164 curv = dot(self.dfk, dot(self.d2fk, self.dfk))
165 pU = - dot(self.dfk, self.dfk) / curv * self.dfk
166 dot_pU = dot(pU, pU)
167 norm_pU = sqrt(dot_pU)
168
169
170 if norm_pU >= self.delta:
171 return self.delta * pU / norm_pU
172
173
174 pB_pU = pB - pU
175 dot_pB_pU = dot(pB_pU, pB_pU)
176 dot_pU_pB_pU = dot(pU, pB_pU)
177 fact = dot_pU_pB_pU**2 - dot_pB_pU * (dot_pU - self.delta**2)
178 tau = (-dot_pU_pB_pU + sqrt(fact)) / dot_pB_pU
179
180
181 return pU + tau * pB_pU
182
183
185 """BFGS Hessian update."""
186
187
188 sk = self.xk_new - self.xk
189 yk = self.dfk_new - self.dfk
190 if dot(yk, sk) == 0:
191 self.warning = "The BFGS matrix is indefinite. This should not occur."
192 rk = 1e99
193 else:
194 rk = 1.0 / dot(yk, sk)
195
196 if self.k == 0:
197 self.Hk = dot(yk, sk) / dot(yk, yk) * self.I
198
199 a = self.I - rk*outer(sk, yk)
200 b = self.I - rk*outer(yk, sk)
201 c = rk*outer(sk, sk)
202 matrix = dot(dot(a, self.Hk), b) + c
203 self.Hk = matrix
204
205
206 self.d2fk = inv(self.Hk)
207
208
210 """Empty function."""
211
212 pass
213
214
216 """Find the dogleg minimiser."""
217
218 self.pk = self.dogleg()
219 self.xk_new = self.xk + self.pk
220 self.fk_new, self.f_count = self.func(*(self.xk_new,)+self.args), self.f_count + 1
221 self.dfk_new, self.g_count = self.dfunc(*(self.xk_new,)+self.args), self.g_count + 1
222
223 if self.print_flag >= 2:
224 print(self.print_prefix + "Fin.")
225 print(self.print_prefix + " pk: " + repr(self.pk))
226 print(self.print_prefix + " xk: " + repr(self.xk))
227 print(self.print_prefix + " xk_new: " + repr(self.xk_new))
228 print(self.print_prefix + " fk: " + repr(self.fk))
229 print(self.print_prefix + " fk_new: " + repr(self.fk_new))
230
231
233 """Update function.
234
235 Run the trust region update. If this update decides to shift xk+1 to xk, then run the BFGS or Newton updates.
236 """
237
238 self.trust_region_update()
239
240 if not self.shift_flag:
241 self.hessian_update()
242 else:
243 self.specific_update()
244