chumpy-fixed 0.71__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chumpy/__init__.py +117 -0
- chumpy/api_compatibility.py +534 -0
- chumpy/ch.py +1367 -0
- chumpy/ch_ops.py +814 -0
- chumpy/ch_random.py +32 -0
- chumpy/extras.py +72 -0
- chumpy/linalg.py +306 -0
- chumpy/logic.py +39 -0
- chumpy/monitor.py +149 -0
- chumpy/np_tensordot.py +228 -0
- chumpy/optimization.py +161 -0
- chumpy/optimization_internal.py +455 -0
- chumpy/reordering.py +454 -0
- chumpy/test_ch.py +621 -0
- chumpy/test_inner_composition.py +80 -0
- chumpy/test_linalg.py +272 -0
- chumpy/test_optimization.py +204 -0
- chumpy/testing.py +21 -0
- chumpy/utils.py +93 -0
- chumpy/version.py +3 -0
- chumpy_fixed-0.71.dist-info/METADATA +31 -0
- chumpy_fixed-0.71.dist-info/RECORD +25 -0
- chumpy_fixed-0.71.dist-info/WHEEL +5 -0
- chumpy_fixed-0.71.dist-info/licenses/LICENSE.txt +22 -0
- chumpy_fixed-0.71.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import warnings
|
|
3
|
+
import numpy as np
|
|
4
|
+
import scipy.sparse as sp
|
|
5
|
+
from . import ch, utils
|
|
6
|
+
from .ch import pif
|
|
7
|
+
from .utils import timer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def clear_cache_single(node):
|
|
11
|
+
node._cache['drs'].clear()
|
|
12
|
+
if hasattr(node, 'dr_cached'):
|
|
13
|
+
node.dr_cached.clear()
|
|
14
|
+
|
|
15
|
+
def vstack(x):
|
|
16
|
+
x = [a if not isinstance(a, sp.linalg.interface.LinearOperator) else a.dot(np.eye(a.shape[1])) for a in x]
|
|
17
|
+
return sp.vstack(x, format='csc') if any([sp.issparse(a) for a in x]) else np.vstack(x)
|
|
18
|
+
def hstack(x):
|
|
19
|
+
x = [a if not isinstance(a, sp.linalg.interface.LinearOperator) else a.dot(np.eye(a.shape[1])) for a in x]
|
|
20
|
+
return sp.hstack(x, format='csc') if any([sp.issparse(a) for a in x]) else np.hstack(x)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_giter = 0
|
|
24
|
+
class ChInputsStacked(ch.Ch):
|
|
25
|
+
dterms = 'x', 'obj'
|
|
26
|
+
terms = 'free_variables'
|
|
27
|
+
|
|
28
|
+
def compute_r(self):
|
|
29
|
+
if not hasattr(self, 'fevals'):
|
|
30
|
+
self.fevals = 0
|
|
31
|
+
self.fevals += 1
|
|
32
|
+
return self.obj.r.ravel()
|
|
33
|
+
|
|
34
|
+
def dr_wrt(self, wrt, profiler=None):
|
|
35
|
+
'''
|
|
36
|
+
Loop over free variables and delete cache for the whole tree after finished each one
|
|
37
|
+
'''
|
|
38
|
+
if wrt is self.x:
|
|
39
|
+
jacs = []
|
|
40
|
+
for fvi, freevar in enumerate(self.free_variables):
|
|
41
|
+
tm = timer()
|
|
42
|
+
if isinstance(freevar, ch.Select):
|
|
43
|
+
new_jac = self.obj.dr_wrt(freevar.a, profiler=profiler)
|
|
44
|
+
try:
|
|
45
|
+
new_jac = new_jac[:, freevar.idxs]
|
|
46
|
+
except:
|
|
47
|
+
# non-csc sparse matrices may not support column-wise indexing
|
|
48
|
+
new_jac = new_jac.tocsc()[:, freevar.idxs]
|
|
49
|
+
else:
|
|
50
|
+
new_jac = self.obj.dr_wrt(freevar, profiler=profiler)
|
|
51
|
+
|
|
52
|
+
pif('dx wrt {} in {}sec, sparse: {}'.format(freevar.short_name, tm(), sp.issparse(new_jac)))
|
|
53
|
+
|
|
54
|
+
if self._make_dense and sp.issparse(new_jac):
|
|
55
|
+
new_jac = new_jac.todense()
|
|
56
|
+
if self._make_sparse and not sp.issparse(new_jac):
|
|
57
|
+
new_jac = sp.csc_matrix(new_jac)
|
|
58
|
+
|
|
59
|
+
if new_jac is None:
|
|
60
|
+
raise Exception(
|
|
61
|
+
'Objective has no derivative wrt free variable {}. '
|
|
62
|
+
'You should likely remove it.'.format(fvi))
|
|
63
|
+
|
|
64
|
+
jacs.append(new_jac)
|
|
65
|
+
tm = timer()
|
|
66
|
+
utils.dfs_do_func_on_graph(self.obj, clear_cache_single)
|
|
67
|
+
pif('dfs_do_func_on_graph in {}sec'.format(tm()))
|
|
68
|
+
tm = timer()
|
|
69
|
+
J = hstack(jacs)
|
|
70
|
+
pif('hstack in {}sec'.format(tm()))
|
|
71
|
+
return J
|
|
72
|
+
|
|
73
|
+
def on_changed(self, which):
|
|
74
|
+
global _giter
|
|
75
|
+
_giter += 1
|
|
76
|
+
if 'x' in which:
|
|
77
|
+
pos = 0
|
|
78
|
+
for idx, freevar in enumerate(self.free_variables):
|
|
79
|
+
sz = freevar.r.size
|
|
80
|
+
rng = np.arange(pos, pos+sz)
|
|
81
|
+
if isinstance(self.free_variables[idx], ch.Select):
|
|
82
|
+
# Deal with nested selects
|
|
83
|
+
selects = []
|
|
84
|
+
a = self.free_variables[idx]
|
|
85
|
+
while isinstance(a, ch.Select):
|
|
86
|
+
selects.append(a.idxs)
|
|
87
|
+
a = a.a
|
|
88
|
+
newv = a.x.copy()
|
|
89
|
+
idxs = selects.pop()
|
|
90
|
+
while len(selects) > 0:
|
|
91
|
+
idxs = idxs[selects.pop()]
|
|
92
|
+
newv.ravel()[idxs] = self.x.r.ravel()[rng]
|
|
93
|
+
a.__setattr__('x', newv, _giter)
|
|
94
|
+
elif isinstance(self.free_variables[idx].x, np.ndarray):
|
|
95
|
+
self.free_variables[idx].__setattr__('x', self.x.r[rng].copy().reshape(self.free_variables[idx].x.shape), _giter)
|
|
96
|
+
else: # a number
|
|
97
|
+
self.free_variables[idx].__setattr__('x', self.x.r[rng], _giter)
|
|
98
|
+
pos += sz
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def J(self):
|
|
102
|
+
'''
|
|
103
|
+
Compute Jacobian. Analyze dr graph first to disable unnecessary caching
|
|
104
|
+
'''
|
|
105
|
+
result = self.dr_wrt(self.x, profiler=self.profiler).copy()
|
|
106
|
+
if self.profiler:
|
|
107
|
+
self.profiler.harvest()
|
|
108
|
+
return np.atleast_2d(result) if not sp.issparse(result) else result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def setup_sparse_solver(sparse_solver):
|
|
112
|
+
_solver_fns = {
|
|
113
|
+
'cg': lambda A, x, M=None : sp.linalg.cg(A, x, M=M, tol=1e-10)[0],
|
|
114
|
+
'spsolve': lambda A, x : sp.linalg.spsolve(A, x)
|
|
115
|
+
}
|
|
116
|
+
if callable(sparse_solver):
|
|
117
|
+
return sparse_solver
|
|
118
|
+
elif isinstance(sparse_solver, str) and sparse_solver in list(_solver_fns.keys()):
|
|
119
|
+
return _solver_fns[sparse_solver]
|
|
120
|
+
else:
|
|
121
|
+
raise Exception('sparse_solver argument must be either a string in the set (%s) or have the api of scipy.sparse.linalg.spsolve.' % ', '.join(list(_solver_fns.keys())))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def setup_objective(obj, free_variables, on_step=None, disp=True, make_dense=False):
|
|
125
|
+
'''
|
|
126
|
+
obj here can be a list of ch objects or a dict of label: ch objects. Either way, the ch
|
|
127
|
+
objects will be merged into one objective using a ChInputsStacked. The labels are just used
|
|
128
|
+
for printing out values per objective with each iteration. If make_dense is True, the
|
|
129
|
+
resulting object with return a desne Jacobian
|
|
130
|
+
'''
|
|
131
|
+
# Validate free variables
|
|
132
|
+
num_unique_ids = len(np.unique(np.array([id(freevar) for freevar in free_variables])))
|
|
133
|
+
if num_unique_ids != len(free_variables):
|
|
134
|
+
raise Exception('The "free_variables" param contains duplicate variables.')
|
|
135
|
+
# Extract labels
|
|
136
|
+
labels = {}
|
|
137
|
+
if isinstance(obj, list) or isinstance(obj, tuple):
|
|
138
|
+
obj = ch.concatenate([f.ravel() for f in obj])
|
|
139
|
+
elif isinstance(obj, dict):
|
|
140
|
+
labels = obj
|
|
141
|
+
obj = ch.concatenate([f.ravel() for f in list(obj.values())])
|
|
142
|
+
# build objective
|
|
143
|
+
x = np.concatenate([freevar.r.ravel() for freevar in free_variables])
|
|
144
|
+
obj = ChInputsStacked(obj=obj, free_variables=free_variables, x=x, make_dense=make_dense)
|
|
145
|
+
# build callback
|
|
146
|
+
def callback():
|
|
147
|
+
if on_step is not None:
|
|
148
|
+
on_step(obj)
|
|
149
|
+
if disp:
|
|
150
|
+
report_line = ['%.2e' % (np.sum(obj.r**2),)]
|
|
151
|
+
for label, objective in sorted(list(labels.items()), key=lambda x: x[0]):
|
|
152
|
+
report_line.append('%s: %.2e' % (label, np.sum(objective.r**2)))
|
|
153
|
+
report_line = " | ".join(report_line) + '\n'
|
|
154
|
+
sys.stderr.write(report_line)
|
|
155
|
+
return obj, callback
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class DoglegState(object):
|
|
159
|
+
'''
|
|
160
|
+
Dogleg preserves a great deal of state from iteration to iteration. Many of the things
|
|
161
|
+
that we need to calculate are dependent only on this state (e.g. the various trust region
|
|
162
|
+
steps, the current jacobian and the A & g that depends on it, etc.). Holding the state and
|
|
163
|
+
the various methods based on that state here allows us to seperate a lot of the jacobian
|
|
164
|
+
based calculation from the flow control of the optmization.
|
|
165
|
+
|
|
166
|
+
There will be once instance of DoglegState per invocation of minimize_dogleg.
|
|
167
|
+
'''
|
|
168
|
+
def __init__(self, delta, solve):
|
|
169
|
+
self.iteration = 0
|
|
170
|
+
self._d_gn = None # gauss-newton
|
|
171
|
+
self._d_sd = None # steepest descent
|
|
172
|
+
self._d_dl = None # dogleg
|
|
173
|
+
self.J = None
|
|
174
|
+
self.A = None
|
|
175
|
+
self.g = None
|
|
176
|
+
self._p = None
|
|
177
|
+
self.delta = delta
|
|
178
|
+
self.solve = solve
|
|
179
|
+
self._r = None
|
|
180
|
+
self.rho = None
|
|
181
|
+
self.done = False
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def p(self):
|
|
185
|
+
'''p is the current proposed input vector'''
|
|
186
|
+
return self._p
|
|
187
|
+
@p.setter
|
|
188
|
+
def p(self, val):
|
|
189
|
+
self._p = val.reshape((-1, 1))
|
|
190
|
+
|
|
191
|
+
# induce some certainty about what the shape of the steps are
|
|
192
|
+
@property
|
|
193
|
+
def d_gn(self):
|
|
194
|
+
return self._d_gn
|
|
195
|
+
@d_gn.setter
|
|
196
|
+
def d_gn(self, val):
|
|
197
|
+
if val is not None:
|
|
198
|
+
val = val.reshape((-1, 1))
|
|
199
|
+
self._d_gn = val
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def d_sd(self):
|
|
203
|
+
return self._d_sd
|
|
204
|
+
@d_sd.setter
|
|
205
|
+
def d_sd(self, val):
|
|
206
|
+
if val is not None:
|
|
207
|
+
val = val.reshape((-1, 1))
|
|
208
|
+
self._d_sd = val
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def d_dl(self):
|
|
212
|
+
return self._d_dl
|
|
213
|
+
@d_dl.setter
|
|
214
|
+
def d_dl(self, val):
|
|
215
|
+
if val is not None:
|
|
216
|
+
val = val.reshape((-1, 1))
|
|
217
|
+
self._d_dl = val
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def step(self):
|
|
221
|
+
return self.d_dl.reshape((-1, 1))
|
|
222
|
+
@property
|
|
223
|
+
def step_size(self):
|
|
224
|
+
return np.linalg.norm(self.d_dl)
|
|
225
|
+
|
|
226
|
+
def start_iteration(self):
|
|
227
|
+
self.iteration += 1
|
|
228
|
+
pif('beginning iteration %d' % (self.iteration,))
|
|
229
|
+
self.d_sd = (np.linalg.norm(self.g)**2 / np.linalg.norm(self.J.dot(self.g))**2 * self.g).ravel()
|
|
230
|
+
self.d_gn = None
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def r(self):
|
|
234
|
+
'''r is the residual at the current p'''
|
|
235
|
+
return self._r
|
|
236
|
+
@r.setter
|
|
237
|
+
def r(self, val):
|
|
238
|
+
self._r = val.copy().reshape((-1, 1))
|
|
239
|
+
self.updateAg()
|
|
240
|
+
|
|
241
|
+
def updateAg(self):
|
|
242
|
+
tm = timer()
|
|
243
|
+
pif('updating A and g...')
|
|
244
|
+
JT = self.J.T
|
|
245
|
+
self.A = JT.dot(self.J)
|
|
246
|
+
self.g = JT.dot(-self.r).reshape((-1, 1))
|
|
247
|
+
pif('A and g updated in %.2fs' % tm())
|
|
248
|
+
|
|
249
|
+
def update_step(self):
|
|
250
|
+
# if the Cauchy point is outside the trust region,
|
|
251
|
+
# take that direction but only to the edge of the trust region
|
|
252
|
+
if self.delta is not None and np.linalg.norm(self.d_sd) >= self.delta:
|
|
253
|
+
pif('PROGRESS: Using stunted cauchy')
|
|
254
|
+
self.d_dl = np.array(self.delta/np.linalg.norm(self.d_sd) * self.d_sd).ravel()
|
|
255
|
+
else:
|
|
256
|
+
if self.d_gn is None:
|
|
257
|
+
# We only need to compute this once per iteration
|
|
258
|
+
self.updateGN()
|
|
259
|
+
# if the gauss-newton solution is within the trust region, use it
|
|
260
|
+
if self.delta is None or np.linalg.norm(self.d_gn) <= self.delta:
|
|
261
|
+
pif('PROGRESS: Using gauss-newton solution')
|
|
262
|
+
self.d_dl = np.array(self.d_gn).ravel()
|
|
263
|
+
if self.delta is None:
|
|
264
|
+
self.delta = np.linalg.norm(self.d_gn)
|
|
265
|
+
else: # between cauchy step and gauss-newton step
|
|
266
|
+
pif('PROGRESS: between cauchy and gauss-newton')
|
|
267
|
+
# apply step
|
|
268
|
+
self.d_dl = self.d_sd + self.beta_multiplier * (self.d_gn - self.d_sd)
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def beta_multiplier(self):
|
|
272
|
+
delta_sq = self.delta**2
|
|
273
|
+
diff = self.d_gn - self.d_sd
|
|
274
|
+
sqnorm_sd = np.linalg.norm(self.d_sd)**2
|
|
275
|
+
pnow = diff.T.dot(diff)*delta_sq + self.d_gn.T.dot(self.d_sd)**2 - np.linalg.norm(self.d_gn)**2 * sqnorm_sd
|
|
276
|
+
return float(delta_sq - sqnorm_sd) / float((diff).T.dot(self.d_sd) + np.sqrt(pnow))
|
|
277
|
+
|
|
278
|
+
def updateGN(self):
|
|
279
|
+
tm = timer()
|
|
280
|
+
if sp.issparse(self.A):
|
|
281
|
+
self.A.eliminate_zeros()
|
|
282
|
+
pif('sparse solve...sparsity infill is %.3f%% (hessian %dx%d)' % (100. * self.A.nnz / (self.A.shape[0] * self.A.shape[1]), self.A.shape[0], self.A.shape[1]))
|
|
283
|
+
if self.g.size > 1:
|
|
284
|
+
self.d_gn = self.solve(self.A, self.g).ravel()
|
|
285
|
+
if np.any(np.isnan(self.d_gn)) or np.any(np.isinf(self.d_gn)):
|
|
286
|
+
from scipy.sparse.linalg import lsqr
|
|
287
|
+
warnings.warn("sparse solve failed, falling back to lsqr")
|
|
288
|
+
self.d_gn = lsqr(self.A, self.g)[0].ravel()
|
|
289
|
+
else:
|
|
290
|
+
self.d_gn = np.atleast_1d(self.g.ravel()[0]/self.A[0,0])
|
|
291
|
+
pif('sparse solve...done in %.2fs' % tm())
|
|
292
|
+
else:
|
|
293
|
+
pif('dense solve...')
|
|
294
|
+
try:
|
|
295
|
+
self.d_gn = np.linalg.solve(self.A, self.g).ravel()
|
|
296
|
+
except Exception:
|
|
297
|
+
warnings.warn("dense solve failed, falling back to lsqr")
|
|
298
|
+
self.d_gn = np.linalg.lstsq(self.A, self.g)[0].ravel()
|
|
299
|
+
pif('dense solve...done in %.2fs' % tm())
|
|
300
|
+
|
|
301
|
+
def updateJ(self, obj):
|
|
302
|
+
tm = timer()
|
|
303
|
+
pif('computing Jacobian...')
|
|
304
|
+
self.J = obj.J
|
|
305
|
+
if self.J is None:
|
|
306
|
+
raise Exception("Computing Jacobian failed!")
|
|
307
|
+
if sp.issparse(self.J):
|
|
308
|
+
tm2 = timer()
|
|
309
|
+
self.J = self.J.tocsr()
|
|
310
|
+
pif('converted to csr in {}secs'.format(tm2()))
|
|
311
|
+
assert(self.J.nnz > 0)
|
|
312
|
+
elif ch.VERBOSE:
|
|
313
|
+
nonzero = np.count_nonzero(self.J)
|
|
314
|
+
pif('Jacobian dense with sparsity %.3f' % (nonzero/self.J.size))
|
|
315
|
+
pif('Jacobian (%dx%d) computed in %.2fs' % (self.J.shape[0], self.J.shape[1], tm()))
|
|
316
|
+
if self.J.shape[1] != self.p.size:
|
|
317
|
+
raise Exception('Jacobian size mismatch with objective input')
|
|
318
|
+
return self.J
|
|
319
|
+
|
|
320
|
+
class Trial(object):
|
|
321
|
+
'''
|
|
322
|
+
Inside each iteration of dogleg we propose a step and check to see if it's actually
|
|
323
|
+
an improvement before we accept it. This class encapsulates that trial and the
|
|
324
|
+
testing to see if it is actually an improvement.
|
|
325
|
+
|
|
326
|
+
There will be one instance of Trial per iteration in dogleg.
|
|
327
|
+
'''
|
|
328
|
+
def __init__(self, proposed_r, state):
|
|
329
|
+
self.r = proposed_r
|
|
330
|
+
self.state = state
|
|
331
|
+
# rho is the ratio of...
|
|
332
|
+
# (improvement in SSE) / (predicted improvement in SSE)
|
|
333
|
+
self.rho = np.linalg.norm(state.r)**2 - np.linalg.norm(proposed_r)**2
|
|
334
|
+
if self.rho > 0:
|
|
335
|
+
with warnings.catch_warnings():
|
|
336
|
+
warnings.filterwarnings('ignore',category=RuntimeWarning)
|
|
337
|
+
predicted_improvement = 2. * state.g.T.dot(state.d_dl) - state.d_dl.T.dot(state.A.dot(state.d_dl))
|
|
338
|
+
self.rho /= predicted_improvement
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def is_improvement(self):
|
|
342
|
+
return self.rho > 0
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def improvement(self):
|
|
346
|
+
return (np.linalg.norm(self.state.r)**2 - np.linalg.norm(self.r)**2) / np.linalg.norm(self.state.r)**2
|
|
347
|
+
|
|
348
|
+
def trial_r(self, proposed_r):
|
|
349
|
+
return self.Trial(proposed_r, self)
|
|
350
|
+
|
|
351
|
+
def updateRadius(self, rho, lb=.05, ub=.9):
|
|
352
|
+
if rho > ub:
|
|
353
|
+
self.delta = max(self.delta, 2.5*np.linalg.norm(self.d_dl))
|
|
354
|
+
elif rho < lb:
|
|
355
|
+
self.delta *= .25
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def minimize_dogleg(obj, free_variables, on_step=None,
|
|
359
|
+
maxiter=200, max_fevals=np.inf, sparse_solver='spsolve',
|
|
360
|
+
disp=True, e_1=1e-15, e_2=1e-15, e_3=0., delta_0=None,
|
|
361
|
+
treat_as_dense=False):
|
|
362
|
+
""""Nonlinear optimization using Powell's dogleg method.
|
|
363
|
+
See Lourakis et al, 2005, ICCV '05, "Is Levenberg-Marquardt the
|
|
364
|
+
Most Efficient Optimization for Implementing Bundle Adjustment?":
|
|
365
|
+
http://www.ics.forth.gr/cvrl/publications/conferences/0201-P0401-lourakis-levenberg.pdf
|
|
366
|
+
|
|
367
|
+
e_N are stopping conditions:
|
|
368
|
+
e_1 is gradient magnatude threshold
|
|
369
|
+
e_2 is step size magnatude threshold
|
|
370
|
+
e_3 is improvement threshold (as a ratio; 0.1 means it must improve by 10%% at each step)
|
|
371
|
+
|
|
372
|
+
maxiter and max_fevals are also stopping conditions. Note that they're not quite the same,
|
|
373
|
+
as an iteration may evaluate the function more than once.
|
|
374
|
+
|
|
375
|
+
sparse_solver is the solver to use to calculate the Gauss-Newton step in the common case
|
|
376
|
+
that the Jacobian is sparse. It can be 'spsolve' (in which case scipy.sparse.linalg.spsolve
|
|
377
|
+
will be used), 'cg' (in which case scipy.sparse.linalg.cg will be used), or any callable
|
|
378
|
+
that matches the api of scipy.sparse.linalg.spsolve to solve `A x = b` for x where A is sparse.
|
|
379
|
+
|
|
380
|
+
cg, uses a Conjugate Gradient method, and will be faster if A is sparse but x is dense.
|
|
381
|
+
spsolve will be faster if x is also sparse.
|
|
382
|
+
|
|
383
|
+
delta_0 defines the initial trust region. Generally speaking, if this is set too low then
|
|
384
|
+
the optimization will never really go anywhere (to small a trust region to make any real
|
|
385
|
+
progress before running out of iterations) and if it's set too high then the optimization
|
|
386
|
+
will diverge immidiately and go wild (such a large trust region that the initial step so
|
|
387
|
+
far overshoots that it can't recover). If it's left as None, it will be automatically
|
|
388
|
+
estimated on the first iteration; it's always updated at each iteration, so this is treated
|
|
389
|
+
only as an initialization.
|
|
390
|
+
|
|
391
|
+
handle_as_dense explicitly converts all Jacobians of obj to dense matrices
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
solve = setup_sparse_solver(sparse_solver)
|
|
396
|
+
obj, callback = setup_objective(obj, free_variables, on_step=on_step, disp=disp,
|
|
397
|
+
make_dense=treat_as_dense)
|
|
398
|
+
|
|
399
|
+
state = DoglegState(delta=delta_0, solve=solve)
|
|
400
|
+
state.p = obj.x.r
|
|
401
|
+
|
|
402
|
+
#inject profiler if in DEBUG mode
|
|
403
|
+
if ch.DEBUG:
|
|
404
|
+
from .monitor import DrWrtProfiler
|
|
405
|
+
obj.profiler = DrWrtProfiler(obj)
|
|
406
|
+
|
|
407
|
+
callback()
|
|
408
|
+
state.updateJ(obj)
|
|
409
|
+
state.r = obj.r
|
|
410
|
+
|
|
411
|
+
def stop(msg):
|
|
412
|
+
if not state.done:
|
|
413
|
+
pif(msg)
|
|
414
|
+
state.done = True
|
|
415
|
+
|
|
416
|
+
if np.linalg.norm(state.g, np.inf) < e_1:
|
|
417
|
+
stop('stopping because norm(g, np.inf) < %.2e' % e_1)
|
|
418
|
+
while not state.done:
|
|
419
|
+
state.start_iteration()
|
|
420
|
+
while True:
|
|
421
|
+
state.update_step()
|
|
422
|
+
if state.step_size <= e_2 * np.linalg.norm(state.p):
|
|
423
|
+
stop('stopping because of small step size (norm_dl < %.2e)' % (e_2 * np.linalg.norm(state.p)))
|
|
424
|
+
else:
|
|
425
|
+
tm = timer()
|
|
426
|
+
obj.x = state.p + state.step
|
|
427
|
+
trial = state.trial_r(obj.r)
|
|
428
|
+
pif('Residuals computed in %.2fs' % tm())
|
|
429
|
+
# if the objective function improved, update input parameter estimate.
|
|
430
|
+
# Note that the obj.x already has the new parms,
|
|
431
|
+
# and we should not set them again to the same (or we'll bust the cache)
|
|
432
|
+
if trial.is_improvement:
|
|
433
|
+
state.p = state.p + state.step
|
|
434
|
+
callback()
|
|
435
|
+
if e_3 > 0. and trial.improvement < e_3:
|
|
436
|
+
stop('stopping because improvement < %.1e%%' % (100*e_3))
|
|
437
|
+
else:
|
|
438
|
+
state.updateJ(obj)
|
|
439
|
+
state.r = trial.r
|
|
440
|
+
if np.linalg.norm(state.g, np.inf) < e_1:
|
|
441
|
+
stop('stopping because norm(g, np.inf) < %.2e' % e_1)
|
|
442
|
+
else: # Put the old parms back
|
|
443
|
+
obj.x = ch.Ch(state.p)
|
|
444
|
+
obj.on_changed('x') # copies from flat vector to free variables
|
|
445
|
+
# update our trust region
|
|
446
|
+
state.updateRadius(trial.rho)
|
|
447
|
+
if state.delta <= e_2*np.linalg.norm(state.p):
|
|
448
|
+
stop('stopping because trust region is too small')
|
|
449
|
+
if state.done or trial.is_improvement or (obj.fevals >= max_fevals):
|
|
450
|
+
break
|
|
451
|
+
if state.iteration >= maxiter:
|
|
452
|
+
stop('stopping because max number of user-specified iterations (%d) has been met' % maxiter)
|
|
453
|
+
elif obj.fevals >= max_fevals:
|
|
454
|
+
stop('stopping because max number of user-specified func evals (%d) has been met' % max_fevals)
|
|
455
|
+
return obj.free_variables
|