DFO-LS 1.4.1__py3-none-any.whl → 1.5.1__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.
Potentially problematic release.
This version of DFO-LS might be problematic. Click here for more details.
- {DFO_LS-1.4.1.dist-info → DFO_LS-1.5.1.dist-info}/METADATA +14 -34
- DFO_LS-1.5.1.dist-info/RECORD +14 -0
- {DFO_LS-1.4.1.dist-info → DFO_LS-1.5.1.dist-info}/WHEEL +1 -1
- dfols/__init__.py +1 -1
- dfols/controller.py +236 -113
- dfols/model.py +61 -33
- dfols/params.py +18 -2
- dfols/solver.py +95 -61
- dfols/trust_region.py +86 -7
- dfols/util.py +20 -9
- DFO_LS-1.4.1.dist-info/RECORD +0 -14
- {DFO_LS-1.4.1.dist-info → DFO_LS-1.5.1.dist-info}/LICENSE.txt +0 -0
- {DFO_LS-1.4.1.dist-info → DFO_LS-1.5.1.dist-info}/top_level.txt +0 -0
dfols/model.py
CHANGED
|
@@ -36,7 +36,7 @@ import numpy as np
|
|
|
36
36
|
import scipy.linalg as LA
|
|
37
37
|
|
|
38
38
|
from .trust_region import trsbox_geometry
|
|
39
|
-
from .util import sumsq, dykstra
|
|
39
|
+
from .util import sumsq, dykstra, remove_scaling
|
|
40
40
|
|
|
41
41
|
__all__ = ['Model']
|
|
42
42
|
|
|
@@ -44,8 +44,8 @@ module_logger = logging.getLogger(__name__)
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class Model(object):
|
|
47
|
-
def __init__(self, npt, x0, r0, xl, xu, projections, r0_nsamples, n=None, m=None, abs_tol=1e-12, rel_tol=1e-20, precondition=True,
|
|
48
|
-
do_logging=True):
|
|
47
|
+
def __init__(self, npt, x0, r0, xl, xu, projections, r0_nsamples, h=None, argsh=(), n=None, m=None, abs_tol=1e-12, rel_tol=1e-20, precondition=True,
|
|
48
|
+
do_logging=True, scaling_changes=None):
|
|
49
49
|
if n is None:
|
|
50
50
|
n = len(x0)
|
|
51
51
|
if m is None:
|
|
@@ -56,11 +56,15 @@ class Model(object):
|
|
|
56
56
|
assert xu.shape == (n,), "xu has wrong shape (got %s, expect (%g,))" % (str(xu.shape), n)
|
|
57
57
|
assert r0.shape == (m,), "r0 has wrong shape (got %s, expect (%g,))" % (str(r0.shape), m)
|
|
58
58
|
self.do_logging = do_logging
|
|
59
|
+
self.scaling_changes = scaling_changes
|
|
59
60
|
self.dim = n
|
|
60
61
|
self.resid_dim = m
|
|
61
62
|
self.num_pts = npt
|
|
62
63
|
self.npt_so_far = 1 # number of points added so far (with function values)
|
|
63
64
|
|
|
65
|
+
self.h = h
|
|
66
|
+
self.argsh = argsh
|
|
67
|
+
|
|
64
68
|
# Initialise to blank some useful stuff
|
|
65
69
|
# Interpolation points
|
|
66
70
|
self.xbase = x0.copy()
|
|
@@ -72,12 +76,17 @@ class Model(object):
|
|
|
72
76
|
# Function values
|
|
73
77
|
self.fval_v = np.inf * np.ones((npt, m)) # residuals for each xpt
|
|
74
78
|
self.fval_v[0, :] = r0.copy()
|
|
75
|
-
|
|
76
|
-
self.
|
|
79
|
+
|
|
80
|
+
self.objval = np.inf * np.ones((npt, )) # overall objective value for each xpt
|
|
81
|
+
self.objval[0] = sumsq(r0)
|
|
82
|
+
if h is not None:
|
|
83
|
+
self.objval[0] += h(remove_scaling(x0, self.scaling_changes), *argsh)
|
|
77
84
|
self.kopt = 0 # index of current iterate (should be best value so far)
|
|
78
85
|
self.nsamples = np.zeros((npt,), dtype=int) # number of samples used to evaluate objective at each point
|
|
79
86
|
self.nsamples[0] = r0_nsamples
|
|
80
|
-
self.
|
|
87
|
+
self.objbeg = self.objval[0] # f(x0), saved to check for sufficient reduction
|
|
88
|
+
self.eval_num = np.zeros((npt,), dtype=int) # which evaluation number (1-indexed, nx not nf) is currently stored self.points[k,:]
|
|
89
|
+
self.eval_num[0] = 1
|
|
81
90
|
|
|
82
91
|
# Termination criteria
|
|
83
92
|
self.abs_tol = abs_tol
|
|
@@ -86,13 +95,16 @@ class Model(object):
|
|
|
86
95
|
# Model information
|
|
87
96
|
self.model_const = np.zeros((m, )) # constant term for model m(s) = c + J*s
|
|
88
97
|
self.model_jac = np.zeros((m, n)) # Jacobian term for model m(s) = c + J*s
|
|
98
|
+
self.model_jac_eval_nums = None # which evaluation numbers (1-indexed, nx not nf) were used to build model_jac
|
|
89
99
|
|
|
90
100
|
# Saved point (in absolute coordinates) - always check this value before quitting solver
|
|
91
101
|
self.xsave = None
|
|
92
102
|
self.rsave = None
|
|
93
|
-
self.
|
|
103
|
+
self.objsave = None
|
|
94
104
|
self.jacsave = None
|
|
95
105
|
self.nsamples_save = None
|
|
106
|
+
self.eval_num_save = None
|
|
107
|
+
self.jacsave_eval_nums = None
|
|
96
108
|
|
|
97
109
|
# Factorisation of interpolation matrix
|
|
98
110
|
self.factorisation_current = False
|
|
@@ -118,8 +130,8 @@ class Model(object):
|
|
|
118
130
|
def ropt(self):
|
|
119
131
|
return self.fval_v[self.kopt, :] # residuals for current iterate
|
|
120
132
|
|
|
121
|
-
def
|
|
122
|
-
return self.
|
|
133
|
+
def objopt(self):
|
|
134
|
+
return self.objval[self.kopt]
|
|
123
135
|
|
|
124
136
|
def xpt(self, k, abs_coordinates=False):
|
|
125
137
|
assert 0 <= k < self.npt(), "Invalid index %g" % k
|
|
@@ -135,9 +147,9 @@ class Model(object):
|
|
|
135
147
|
assert 0 <= k < self.npt(), "Invalid index %g" % k
|
|
136
148
|
return self.fval_v[k, :]
|
|
137
149
|
|
|
138
|
-
def
|
|
150
|
+
def objval(self, k):
|
|
139
151
|
assert 0 <= k < self.npt(), "Invalid index %g" % k
|
|
140
|
-
return self.
|
|
152
|
+
return self.objval[k]
|
|
141
153
|
|
|
142
154
|
def as_absolute_coordinates(self, x, full_dykstra=False):
|
|
143
155
|
# If x were an interpolation point, get the absolute coordinates of x
|
|
@@ -167,7 +179,7 @@ class Model(object):
|
|
|
167
179
|
sq_distances[k] = sumsq(self.points[k, :] - xopt)
|
|
168
180
|
return sq_distances
|
|
169
181
|
|
|
170
|
-
def change_point(self, k, x, rvec, allow_kopt_update=True):
|
|
182
|
+
def change_point(self, k, x, rvec, eval_num, allow_kopt_update=True):
|
|
171
183
|
# Update point k to x (w.r.t. xbase), with residual values fvec
|
|
172
184
|
if k >= self.npt_so_far and self.npt_so_far < self.num_pts:
|
|
173
185
|
assert k == self.npt_so_far, "Growing: updating wrong point"
|
|
@@ -177,18 +189,22 @@ class Model(object):
|
|
|
177
189
|
|
|
178
190
|
self.points[k, :] = x.copy()
|
|
179
191
|
self.fval_v[k, :] = rvec.copy()
|
|
180
|
-
self.
|
|
192
|
+
self.objval[k] = sumsq(rvec)
|
|
193
|
+
if self.h is not None:
|
|
194
|
+
self.objval[k] += self.h(remove_scaling(self.xbase + x, self.scaling_changes), *self.argsh)
|
|
181
195
|
self.nsamples[k] = 1
|
|
196
|
+
self.eval_num[k] = eval_num
|
|
182
197
|
self.factorisation_current = False
|
|
183
198
|
|
|
184
|
-
if allow_kopt_update and self.
|
|
199
|
+
if allow_kopt_update and self.objval[k] < self.objopt():
|
|
185
200
|
self.kopt = k
|
|
186
201
|
return
|
|
187
202
|
|
|
188
203
|
def swap_points(self, k1, k2):
|
|
189
204
|
self.points[[k1, k2], :] = self.points[[k2, k1], :]
|
|
190
205
|
self.fval_v[[k1, k2], :] = self.fval_v[[k2, k1], :]
|
|
191
|
-
self.
|
|
206
|
+
self.objval[[k1, k2]] = self.objval[[k2, k1]]
|
|
207
|
+
self.eval_num[[k1, k2]] = self.eval_num[[k2, k1]]
|
|
192
208
|
if self.kopt == k1:
|
|
193
209
|
self.kopt = k2
|
|
194
210
|
elif self.kopt == k2:
|
|
@@ -201,22 +217,28 @@ class Model(object):
|
|
|
201
217
|
assert 0 <= k < self.npt(), "Invalid index %g" % k
|
|
202
218
|
t = float(self.nsamples[k]) / float(self.nsamples[k] + 1)
|
|
203
219
|
self.fval_v[k, :] = t * self.fval_v[k, :] + (1 - t) * rvec_extra
|
|
204
|
-
|
|
220
|
+
# NOTE: how to sample when we have h? still at xpt(k), then add h(xpt(k)). Modify test if incorrect!
|
|
221
|
+
self.objval[k] = sumsq(self.fval_v[k, :])
|
|
222
|
+
if self.h is not None:
|
|
223
|
+
self.objval[k] += self.h(remove_scaling(self.xbase + self.points[k, :], self.scaling_changes), *self.argsh)
|
|
205
224
|
self.nsamples[k] += 1
|
|
206
225
|
|
|
207
|
-
self.kopt = np.argmin(self.
|
|
226
|
+
self.kopt = np.argmin(self.objval[:self.npt()]) # make sure kopt is always the best value we have
|
|
208
227
|
return
|
|
209
228
|
|
|
210
|
-
def add_new_point(self, x, rvec):
|
|
229
|
+
def add_new_point(self, x, rvec, eval_num):
|
|
211
230
|
self.points = np.append(self.points, x.reshape((1, self.n())), axis=0) # append row to xpt
|
|
212
231
|
self.fval_v = np.append(self.fval_v, rvec.reshape((1, self.m())), axis=0) # append row to fval_v
|
|
213
|
-
|
|
214
|
-
|
|
232
|
+
obj = sumsq(rvec)
|
|
233
|
+
if self.h is not None:
|
|
234
|
+
obj += self.h(remove_scaling(self.xbase + x, self.scaling_changes), *self.argsh)
|
|
235
|
+
self.objval = np.append(self.objval, obj) # append entry to fval
|
|
215
236
|
self.nsamples = np.append(self.nsamples, 1) # add new sample number
|
|
237
|
+
self.eval_num = np.append(self.eval_num, eval_num) # add new evaluation number
|
|
216
238
|
self.num_pts += 1 # make sure npt is updated
|
|
217
239
|
self.npt_so_far += 1
|
|
218
240
|
|
|
219
|
-
if
|
|
241
|
+
if obj < self.objopt():
|
|
220
242
|
self.kopt = self.npt() - 1
|
|
221
243
|
|
|
222
244
|
self.factorisation_current = False
|
|
@@ -235,28 +257,33 @@ class Model(object):
|
|
|
235
257
|
self.model_const += np.dot(self.model_jac, xbase_shift)
|
|
236
258
|
return
|
|
237
259
|
|
|
238
|
-
def save_point(self, x, rvec, nsamples, x_in_abs_coords=True):
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
def save_point(self, x, rvec, nsamples, eval_num, x_in_abs_coords=True):
|
|
261
|
+
xabs = x.copy() if x_in_abs_coords else self.as_absolute_coordinates(x)
|
|
262
|
+
obj = sumsq(rvec)
|
|
263
|
+
if self.h is not None:
|
|
264
|
+
obj += self.h(remove_scaling(xabs, self.scaling_changes), *self.argsh)
|
|
265
|
+
if self.objsave is None or obj <= self.objsave:
|
|
266
|
+
self.xsave = xabs
|
|
242
267
|
self.rsave = rvec.copy()
|
|
243
|
-
self.
|
|
244
|
-
self.jacsave = self.model_jac.copy()
|
|
268
|
+
self.objsave = obj
|
|
269
|
+
self.jacsave = self.model_jac.copy() if self.model_jac is not None else None
|
|
245
270
|
self.nsamples_save = nsamples
|
|
271
|
+
self.eval_num_save = eval_num
|
|
272
|
+
self.jacsave_eval_nums = self.model_jac_eval_nums.copy() if self.model_jac_eval_nums is not None else None
|
|
246
273
|
return True
|
|
247
274
|
else:
|
|
248
275
|
return False # this value is worse than what we have already - didn't save
|
|
249
276
|
|
|
250
277
|
def get_final_results(self):
|
|
251
|
-
# Return x and
|
|
252
|
-
if self.
|
|
253
|
-
return self.xopt(abs_coordinates=True).copy(), self.ropt().copy(), self.
|
|
278
|
+
# Return x and objval for optimal point (either from xsave+objsave or kopt)
|
|
279
|
+
if self.objsave is None or self.objopt() <= self.objsave: # optimal has changed since xsave+objsave were last set
|
|
280
|
+
return self.xopt(abs_coordinates=True).copy(), self.ropt().copy(), self.objopt(), self.model_jac.copy(), self.nsamples[self.kopt], self.eval_num[self.kopt], self.model_jac_eval_nums
|
|
254
281
|
else:
|
|
255
|
-
return self.xsave.copy(), self.rsave.copy(), self.
|
|
282
|
+
return self.xsave.copy(), self.rsave.copy(), self.objsave, self.jacsave, self.nsamples_save, self.eval_num_save, self.jacsave_eval_nums
|
|
256
283
|
|
|
257
284
|
def min_objective_value(self):
|
|
258
285
|
# Get termination criterion for f small: f <= abs_tol or f <= rel_tol * f0
|
|
259
|
-
return max(self.abs_tol, self.rel_tol * self.
|
|
286
|
+
return max(self.abs_tol, self.rel_tol * self.objbeg)
|
|
260
287
|
|
|
261
288
|
def model_value(self, d, d_based_at_xopt=True, with_const_term=False):
|
|
262
289
|
if d_based_at_xopt:
|
|
@@ -350,6 +377,7 @@ class Model(object):
|
|
|
350
377
|
J_old = self.model_jac.copy()
|
|
351
378
|
self.model_jac = dg[1:,:].T
|
|
352
379
|
self.model_const = dg[0,:] - np.dot(self.model_jac, xopt) # shift base to xbase
|
|
380
|
+
self.model_jac_eval_nums = self.eval_num.copy()
|
|
353
381
|
if verbose or get_chg_J:
|
|
354
382
|
norm_J_error = np.linalg.norm(self.model_jac - J_old, ord='fro')**2
|
|
355
383
|
linalg_resid = np.linalg.norm(W.dot(dg) - rhs)**2
|
|
@@ -375,7 +403,7 @@ class Model(object):
|
|
|
375
403
|
return True, interp_error, sqrt(norm_J_error), linalg_resid, ls_interp_cond_num # flag ok
|
|
376
404
|
|
|
377
405
|
def build_full_model(self):
|
|
378
|
-
# Build full least squares
|
|
406
|
+
# Build full least squares model from mini-models
|
|
379
407
|
# Centred around xopt
|
|
380
408
|
r = self.model_const + np.dot(self.model_jac, self.xopt()) # constant term (for inexact interpolation)
|
|
381
409
|
J = self.model_jac
|
dfols/params.py
CHANGED
|
@@ -82,7 +82,7 @@ class ParameterList(object):
|
|
|
82
82
|
self.params["restarts.use_soft_restarts"] = True
|
|
83
83
|
self.params["restarts.soft.num_geom_steps"] = 3
|
|
84
84
|
self.params["restarts.soft.move_xk"] = True
|
|
85
|
-
self.params["restarts.soft.max_fake_successful_steps"] = maxfun # number ratio>0 steps below
|
|
85
|
+
self.params["restarts.soft.max_fake_successful_steps"] = maxfun # number ratio>0 steps below objsave allowed
|
|
86
86
|
self.params["restarts.hard.use_old_rk"] = True # recycle r(xk) from previous run?
|
|
87
87
|
self.params["restarts.increase_npt"] = False
|
|
88
88
|
self.params["restarts.increase_npt_amt"] = 1
|
|
@@ -109,12 +109,20 @@ class ParameterList(object):
|
|
|
109
109
|
self.params["growing.full_rank.min_sing_val"] = 1e-6 # absolute floor on singular values
|
|
110
110
|
self.params["growing.full_rank.svd_max_jac_cond"] = 1e8 # maximum condition number of Jacobian
|
|
111
111
|
self.params["growing.perturb_trust_region_step"] = False # add random direction onto TRS solution?
|
|
112
|
+
|
|
112
113
|
# Dykstra's algorithm
|
|
113
114
|
self.params["dykstra.d_tol"] = 1e-10
|
|
114
115
|
self.params["dykstra.max_iters"] = 100
|
|
116
|
+
|
|
115
117
|
# Matrix rank algorithm
|
|
116
118
|
self.params["matrix_rank.r_tol"] = 1e-18
|
|
117
|
-
|
|
119
|
+
|
|
120
|
+
# Function tolerance when applying S-FISTA method
|
|
121
|
+
self.params["func_tol.criticality_measure"] = 1e-3
|
|
122
|
+
self.params["func_tol.tr_step"] = 1-1e-1
|
|
123
|
+
self.params["func_tol.max_iters"] = 500
|
|
124
|
+
self.params["sfista.max_iters_scaling"] = 2.0
|
|
125
|
+
|
|
118
126
|
self.params_changed = {}
|
|
119
127
|
for p in self.params:
|
|
120
128
|
self.params_changed[p] = False
|
|
@@ -268,6 +276,14 @@ class ParameterList(object):
|
|
|
268
276
|
type_str, nonetype_ok, lower, upper = 'int', False, 0, None
|
|
269
277
|
elif key == "matrix_rank.r_tol":
|
|
270
278
|
type_str, nonetype_ok, lower, upper = 'float', False, 0.0, None
|
|
279
|
+
elif key == "func_tol.criticality_measure":
|
|
280
|
+
type_str, nonetype_ok, lower, upper = 'float', False, 0.0, 1.0
|
|
281
|
+
elif key == "func_tol.tr_step":
|
|
282
|
+
type_str, nonetype_ok, lower, upper = 'float', False, 0.0, 1.0
|
|
283
|
+
elif key == "func_tol.max_iters":
|
|
284
|
+
type_str, nonetype_ok, lower, upper = 'int', False, 0, None
|
|
285
|
+
elif key == "sfista.max_iters_scaling":
|
|
286
|
+
type_str, nonetype_ok, lower, upper = 'float', False, 1.0, None
|
|
271
287
|
else:
|
|
272
288
|
assert False, "ParameterList.param_type() has unknown key: %s" % key
|
|
273
289
|
return type_str, nonetype_ok, lower, upper
|