DFO-LS 1.2.1__py3-none-any.whl → 1.5.0__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.2.1.dist-info → DFO_LS-1.5.0.dist-info}/METADATA +66 -60
- DFO_LS-1.5.0.dist-info/RECORD +14 -0
- {DFO_LS-1.2.1.dist-info → DFO_LS-1.5.0.dist-info}/WHEEL +1 -1
- {DFO_LS-1.2.1.dist-info → DFO_LS-1.5.0.dist-info}/top_level.txt +0 -0
- dfols/__init__.py +4 -5
- dfols/controller.py +274 -59
- dfols/hessian.py +1 -1
- dfols/model.py +64 -33
- dfols/params.py +32 -2
- dfols/solver.py +156 -91
- dfols/trust_region.py +237 -7
- dfols/util.py +71 -10
- DFO_LS-1.2.1.dist-info/RECORD +0 -16
- DFO_LS-1.2.1.dist-info/zip-safe +0 -1
- dfols/version.py +0 -25
- {DFO_LS-1.2.1.dist-info → DFO_LS-1.5.0.dist-info}/LICENSE.txt +0 -0
dfols/solver.py
CHANGED
|
@@ -43,13 +43,15 @@ from .util import *
|
|
|
43
43
|
|
|
44
44
|
__all__ = ['solve']
|
|
45
45
|
|
|
46
|
+
module_logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
46
48
|
|
|
47
49
|
# A container for the results of the optimization routine
|
|
48
50
|
class OptimResults(object):
|
|
49
|
-
def __init__(self, xmin, rmin,
|
|
51
|
+
def __init__(self, xmin, rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg):
|
|
50
52
|
self.x = xmin
|
|
51
53
|
self.resid = rmin
|
|
52
|
-
self.
|
|
54
|
+
self.obj = objmin
|
|
53
55
|
self.jacobian = jacmin
|
|
54
56
|
self.nf = nf
|
|
55
57
|
self.nx = nx
|
|
@@ -75,7 +77,7 @@ class OptimResults(object):
|
|
|
75
77
|
output += "Residual vector = %s\n" % str(self.resid)
|
|
76
78
|
else:
|
|
77
79
|
output += "Not showing residual vector because it is too long; check self.resid\n"
|
|
78
|
-
output += "Objective value f(xmin) = %.10g\n" % self.
|
|
80
|
+
output += "Objective value f(xmin) = %.10g\n" % self.obj
|
|
79
81
|
output += "Needed %g objective evaluations (at %g points)\n" % (self.nf, self.nx)
|
|
80
82
|
if self.nruns > 1:
|
|
81
83
|
output += "Did a total of %g runs\n" % self.nruns
|
|
@@ -93,8 +95,8 @@ class OptimResults(object):
|
|
|
93
95
|
return output
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
def solve_main(objfun, x0,
|
|
97
|
-
diagnostic_info, scaling_changes, r0_avg_old=None, r0_nsamples_old=None, default_growing_method_set_by_user=None,
|
|
98
|
+
def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns_so_far, nf_so_far, nx_so_far, nsamples, params,
|
|
99
|
+
diagnostic_info, scaling_changes, h=None, lh=None, argsh=(), prox_uh=None, argsprox=None, r0_avg_old=None, r0_nsamples_old=None, default_growing_method_set_by_user=None,
|
|
98
100
|
do_logging=True, print_progress=False):
|
|
99
101
|
# Evaluate at x0 (keep nf, nx correct and check for f < 1e-12)
|
|
100
102
|
# The hard bit is determining what m = len(r0) should be, and allocating memory appropriately
|
|
@@ -103,18 +105,17 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
103
105
|
# Evaluate the first time...
|
|
104
106
|
nf = nf_so_far + 1
|
|
105
107
|
nx = nx_so_far + 1
|
|
106
|
-
r0,
|
|
107
|
-
|
|
108
|
+
r0, obj0 = eval_least_squares_with_regularisation(objfun, remove_scaling(x0, scaling_changes), h,
|
|
109
|
+
argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
|
|
108
110
|
full_x_thresh=params("logging.n_to_print_whole_x_vector"),
|
|
109
|
-
check_for_overflow=params("general.check_objfun_for_overflow")
|
|
110
|
-
verbose=do_logging)
|
|
111
|
+
check_for_overflow=params("general.check_objfun_for_overflow"))
|
|
111
112
|
m = len(r0)
|
|
112
113
|
|
|
113
114
|
# Now we have m, we can evaluate the rest of the times
|
|
114
115
|
rvec_list = np.zeros((number_of_samples, m))
|
|
115
|
-
|
|
116
|
+
obj_list = np.zeros((number_of_samples,))
|
|
116
117
|
rvec_list[0, :] = r0
|
|
117
|
-
|
|
118
|
+
obj_list[0] = obj0
|
|
118
119
|
num_samples_run = 1
|
|
119
120
|
exit_info = None
|
|
120
121
|
|
|
@@ -126,15 +127,20 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
126
127
|
|
|
127
128
|
nf += 1
|
|
128
129
|
# Don't increment nx for x0 - we did this earlier
|
|
129
|
-
rvec_list[i, :],
|
|
130
|
+
rvec_list[i, :], obj_list[i] = eval_least_squares_with_regularisation(objfun, remove_scaling(x0, scaling_changes), h,
|
|
131
|
+
argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
|
|
130
132
|
full_x_thresh=params("logging.n_to_print_whole_x_vector"),
|
|
131
|
-
check_for_overflow=params("general.check_objfun_for_overflow")
|
|
132
|
-
verbose=do_logging)
|
|
133
|
+
check_for_overflow=params("general.check_objfun_for_overflow"))
|
|
133
134
|
num_samples_run += 1
|
|
134
135
|
|
|
135
136
|
r0_avg = np.mean(rvec_list[:num_samples_run, :], axis=0)
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
# NOTE: modify objvalue here
|
|
138
|
+
if h is None:
|
|
139
|
+
if sumsq(r0_avg) <= params("model.abs_tol"):
|
|
140
|
+
exit_info = ExitInformation(EXIT_SUCCESS, "Objective is sufficiently small")
|
|
141
|
+
else:
|
|
142
|
+
if sumsq(r0_avg) + h(remove_scaling(x0, scaling_changes), *argsh)<= params("model.abs_tol"):
|
|
143
|
+
exit_info = ExitInformation(EXIT_SUCCESS, "Objective is sufficiently small")
|
|
138
144
|
|
|
139
145
|
if exit_info is not None:
|
|
140
146
|
return x0, r0_avg, sumsq(r0_avg), None, num_samples_run, nf, nx, nruns_so_far+1, exit_info, diagnostic_info
|
|
@@ -153,15 +159,15 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
153
159
|
# However, this can fail for m<n, so need to use an alternative method (perturb_trust_region_step)
|
|
154
160
|
if m < len(x0):
|
|
155
161
|
if do_logging:
|
|
156
|
-
|
|
162
|
+
module_logger.debug("Inverse problem (m<n), switching default growing method")
|
|
157
163
|
params('growing.full_rank.use_full_rank_interp', new_value=False)
|
|
158
164
|
params('growing.perturb_trust_region_step', new_value=True)
|
|
159
165
|
if not params.params_changed['growing.delta_scale_new_dirns']:
|
|
160
166
|
params('growing.delta_scale_new_dirns', new_value=0.1)
|
|
161
167
|
|
|
162
168
|
# Initialise controller
|
|
163
|
-
control = Controller(objfun,
|
|
164
|
-
params, scaling_changes, do_logging)
|
|
169
|
+
control = Controller(objfun, argsf, x0, r0_avg, num_samples_run, xl, xu, projections, npt, rhobeg, rhoend, nf, nx, maxfun,
|
|
170
|
+
params, scaling_changes, do_logging, h=h, lh=lh, argsh=argsh, prox_uh=prox_uh, argsprox=argsprox)
|
|
165
171
|
|
|
166
172
|
# Initialise interpolation set
|
|
167
173
|
number_of_samples = max(nsamples(control.delta, control.rho, 0, nruns_so_far), 1)
|
|
@@ -169,15 +175,15 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
169
175
|
npt - 1) # cap at npt
|
|
170
176
|
if params("init.random_initial_directions"):
|
|
171
177
|
if do_logging:
|
|
172
|
-
|
|
178
|
+
module_logger.info("Initialising (random directions)")
|
|
173
179
|
exit_info = control.initialise_random_directions(number_of_samples, num_directions, params)
|
|
174
180
|
else:
|
|
175
181
|
if do_logging:
|
|
176
|
-
|
|
182
|
+
module_logger.info("Initialising (coordinate directions)")
|
|
177
183
|
exit_info = control.initialise_coordinate_directions(number_of_samples, num_directions, params)
|
|
178
184
|
if exit_info is not None:
|
|
179
|
-
x, rvec,
|
|
180
|
-
return x, rvec,
|
|
185
|
+
x, rvec, obj, jacmin, nsamples = control.model.get_final_results()
|
|
186
|
+
return x, rvec, obj, None, nsamples, control.nf, control.nx, nruns_so_far + 1, exit_info, diagnostic_info
|
|
181
187
|
|
|
182
188
|
finished_growing = (control.model.npt() >= control.model.num_pts) # have we finished growing the initial set yet?
|
|
183
189
|
|
|
@@ -195,18 +201,18 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
195
201
|
# ------------------------------------------
|
|
196
202
|
current_iter = -1
|
|
197
203
|
if do_logging:
|
|
198
|
-
|
|
204
|
+
module_logger.info("Beginning main loop")
|
|
199
205
|
if print_progress:
|
|
200
206
|
print("{:^5}{:^7}{:^10}{:^10}{:^10}{:^10}{:^7}".format("Run", "Iter", "Obj", "Grad", "Delta", "rho", "Evals"))
|
|
201
207
|
while True:
|
|
202
208
|
current_iter += 1
|
|
203
209
|
|
|
204
210
|
if do_logging:
|
|
205
|
-
|
|
211
|
+
module_logger.debug("*** Iter %g (delta = %g, rho = %g) ***" % (current_iter, control.delta, control.rho))
|
|
206
212
|
|
|
207
213
|
if (not finished_growing) and control.model.npt() >= control.model.num_pts:
|
|
208
214
|
if do_logging:
|
|
209
|
-
|
|
215
|
+
module_logger.info("Finished growing init set")
|
|
210
216
|
finished_growing = True
|
|
211
217
|
|
|
212
218
|
if params("growing.reset_delta"):
|
|
@@ -217,7 +223,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
217
223
|
|
|
218
224
|
if not finished_growing:
|
|
219
225
|
if do_logging:
|
|
220
|
-
|
|
226
|
+
module_logger.debug("Main loop: still growing (have %g of %g pts)" % (control.model.npt(), control.model.num_pts))
|
|
221
227
|
|
|
222
228
|
# Noise level exit check
|
|
223
229
|
if finished_growing and params("noise.quit_on_noise_level") and control.all_values_within_noise_level(params):
|
|
@@ -247,7 +253,8 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
247
253
|
min_sing_val=params("growing.full_rank.min_sing_val"),
|
|
248
254
|
sing_val_frac=params("growing.full_rank.svd_scale_factor"),
|
|
249
255
|
max_jac_cond=params("growing.full_rank.svd_max_jac_cond"),
|
|
250
|
-
get_chg_J=params("restarts.use_restarts") and params("restarts.auto_detect")
|
|
256
|
+
get_chg_J=params("restarts.use_restarts") and params("restarts.auto_detect"),
|
|
257
|
+
throw_error_on_nans=params("interpolation.throw_error_on_nans"))
|
|
251
258
|
if not interp_ok:
|
|
252
259
|
if params("restarts.use_restarts") and params("restarts.use_soft_restarts"):
|
|
253
260
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
@@ -268,16 +275,30 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
268
275
|
nruns_so_far += 1
|
|
269
276
|
break # quit
|
|
270
277
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
tau = 1.0 # ratio used in the safety phase
|
|
279
|
+
if h is None:
|
|
280
|
+
# Trust region step
|
|
281
|
+
d, gopt, H, gnew, crvmin = control.trust_region_step(params)
|
|
282
|
+
else:
|
|
283
|
+
# Calculate criticality measure
|
|
284
|
+
criticality_measure = control.evaluate_criticality_measure(params)
|
|
285
|
+
# Trust region step
|
|
286
|
+
d, gopt, H, gnew, crvmin = control.trust_region_step(params, criticality_measure)
|
|
287
|
+
try:
|
|
288
|
+
tau = min(criticality_measure/(LA.norm(gopt)+lh), 1.0)
|
|
289
|
+
except ValueError:
|
|
290
|
+
# In some instances, gopt can have nan/inf values -- this ultimately calls a safety step and is generally fine
|
|
291
|
+
# but we need to set a value for tau nonetheless
|
|
292
|
+
tau = 1.0
|
|
293
|
+
|
|
274
294
|
if do_logging:
|
|
275
|
-
|
|
295
|
+
module_logger.debug("Trust region step is d = " + str(d))
|
|
296
|
+
|
|
276
297
|
xnew = control.model.xopt() + d
|
|
277
298
|
dnorm = min(LA.norm(d), control.delta)
|
|
278
299
|
|
|
279
300
|
if print_progress:
|
|
280
|
-
print("{:^5}{:^7}{:^10.2e}{:^10.2e}{:^10.2e}{:^10.2e}{:^7}".format(nruns_so_far+1, current_iter+1, control.model.
|
|
301
|
+
print("{:^5}{:^7}{:^10.2e}{:^10.2e}{:^10.2e}{:^10.2e}{:^7}".format(nruns_so_far+1, current_iter+1, control.model.objopt(), np.linalg.norm(gopt), control.delta, control.rho, control.nf))
|
|
281
302
|
|
|
282
303
|
if params("logging.save_diagnostic_info"):
|
|
283
304
|
diagnostic_info.save_info_from_control(control, nruns_so_far, current_iter,
|
|
@@ -286,9 +307,9 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
286
307
|
diagnostic_info.update_interpolation_information(interp_error, ls_interp_cond_num, linalg_resid,
|
|
287
308
|
sqrt(norm_J_error), LA.norm(gopt), LA.norm(d))
|
|
288
309
|
|
|
289
|
-
if dnorm < params("general.safety_step_thresh") * control.rho and not finished_growing and params("growing.safety.do_safety_step"):
|
|
310
|
+
if dnorm < tau * params("general.safety_step_thresh") * control.rho and not finished_growing and params("growing.safety.do_safety_step"):
|
|
290
311
|
if do_logging:
|
|
291
|
-
|
|
312
|
+
module_logger.debug("Safety step during growing phase")
|
|
292
313
|
|
|
293
314
|
if params("logging.save_diagnostic_info"):
|
|
294
315
|
diagnostic_info.update_ratio(np.nan)
|
|
@@ -366,7 +387,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
366
387
|
elif dnorm < params("general.safety_step_thresh") * control.rho and finished_growing:
|
|
367
388
|
# (start safety step)
|
|
368
389
|
if do_logging:
|
|
369
|
-
|
|
390
|
+
module_logger.debug("Safety step (main phase)")
|
|
370
391
|
|
|
371
392
|
if params("logging.save_diagnostic_info"):
|
|
372
393
|
diagnostic_info.update_ratio(np.nan)
|
|
@@ -410,12 +431,12 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
410
431
|
# Reduce rho
|
|
411
432
|
control.reduce_rho(current_iter, params)
|
|
412
433
|
if do_logging:
|
|
413
|
-
|
|
434
|
+
module_logger.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
|
|
414
435
|
if control.n() < params("logging.n_to_print_whole_x_vector"):
|
|
415
|
-
|
|
436
|
+
module_logger.debug("Best so far: f = %.15g at x = " % (control.model.objopt())
|
|
416
437
|
+ str(control.model.xopt(abs_coordinates=True)))
|
|
417
438
|
else:
|
|
418
|
-
|
|
439
|
+
module_logger.debug("Best so far: f = %.15g at x = [...]" % (control.model.objopt()))
|
|
419
440
|
continue # next iteration
|
|
420
441
|
else:
|
|
421
442
|
# Quit on rho=rhoend
|
|
@@ -436,8 +457,9 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
436
457
|
else:
|
|
437
458
|
# Cannot reduce rho, so check xnew and quit
|
|
438
459
|
x = control.model.as_absolute_coordinates(xnew)
|
|
460
|
+
##print("x from xnew", x)
|
|
439
461
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
440
|
-
rvec_list,
|
|
462
|
+
rvec_list, obj_list, num_samples_run, exit_info = control.evaluate_objective(x, number_of_samples,
|
|
441
463
|
params)
|
|
442
464
|
|
|
443
465
|
if num_samples_run > 0:
|
|
@@ -455,18 +477,18 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
455
477
|
else:
|
|
456
478
|
# (start trust region step)
|
|
457
479
|
if do_logging:
|
|
458
|
-
|
|
480
|
+
module_logger.debug("Standard trust region step")
|
|
459
481
|
|
|
460
482
|
# If growing, optionally perturb the trust region step in a new direction
|
|
461
483
|
if not finished_growing and params("growing.perturb_trust_region_step"):
|
|
462
484
|
step_length = params('growing.delta_scale_new_dirns') * control.delta
|
|
463
485
|
dirn = control.get_new_direction_for_growing(step_length)
|
|
464
486
|
if do_logging:
|
|
465
|
-
|
|
487
|
+
module_logger.debug("Perturbing trust region with step = %s" % str(dirn))
|
|
466
488
|
d += dirn
|
|
467
489
|
xnew += dirn
|
|
468
490
|
if do_logging:
|
|
469
|
-
|
|
491
|
+
module_logger.debug("New trust region step = %s" % str(d))
|
|
470
492
|
|
|
471
493
|
# If finished growing, add chgJ and delta to restart auto-detect set
|
|
472
494
|
if finished_growing and params("restarts.use_restarts") and params("restarts.auto_detect"):
|
|
@@ -511,8 +533,20 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
511
533
|
|
|
512
534
|
# Evaluate new point
|
|
513
535
|
x = control.model.as_absolute_coordinates(xnew)
|
|
536
|
+
##print("x from xnew again", x)
|
|
514
537
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
515
|
-
rvec_list,
|
|
538
|
+
rvec_list, obj_list, num_samples_run, exit_info = control.evaluate_objective(x, number_of_samples, params)
|
|
539
|
+
if np.any(np.isnan(rvec_list)):
|
|
540
|
+
# Just exit without saving the current point
|
|
541
|
+
# We should be able to do a hard restart though, because it's unlikely
|
|
542
|
+
# that we will get the same trust-region step after expanding the radius/re-initialising
|
|
543
|
+
module_logger.warning("NaN encountered in evaluation of trust-region step")
|
|
544
|
+
if params("interpolation.throw_error_on_nans"):
|
|
545
|
+
raise np.linalg.LinAlgError("NaN encountered in objective evaluations")
|
|
546
|
+
|
|
547
|
+
exit_info = ExitInformation(EXIT_EVAL_ERROR, "NaN received from objective function evaluation")
|
|
548
|
+
nruns_so_far += 1
|
|
549
|
+
break # quit
|
|
516
550
|
if exit_info is not None:
|
|
517
551
|
if num_samples_run > 0:
|
|
518
552
|
control.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
|
|
@@ -521,7 +555,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
521
555
|
break # quit
|
|
522
556
|
|
|
523
557
|
# Estimate f in order to compute 'actual reduction'
|
|
524
|
-
ratio, exit_info = control.calculate_ratio(current_iter, rvec_list[:num_samples_run, :], d, gopt, H)
|
|
558
|
+
ratio, exit_info = control.calculate_ratio(control.model.xopt(abs_coordinates=True), current_iter, rvec_list[:num_samples_run, :], d, gopt, H)
|
|
525
559
|
if exit_info is not None:
|
|
526
560
|
if exit_info.able_to_do_restart() and params("restarts.use_restarts") and params(
|
|
527
561
|
"restarts.use_soft_restarts"):
|
|
@@ -545,15 +579,15 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
545
579
|
|
|
546
580
|
# Update delta
|
|
547
581
|
if do_logging:
|
|
548
|
-
|
|
582
|
+
module_logger.debug("Ratio = %g" % ratio)
|
|
549
583
|
if params("logging.save_diagnostic_info"):
|
|
550
584
|
diagnostic_info.update_ratio(ratio)
|
|
551
585
|
diagnostic_info.update_slow_iter(-1) # n/a, unless otherwise update
|
|
552
586
|
if ratio < params("tr_radius.eta1"): # ratio < 0.1
|
|
553
587
|
if finished_growing:
|
|
554
|
-
control.delta = min(params("tr_radius.gamma_dec") * control.delta, dnorm)
|
|
588
|
+
control.delta = min(params("tr_radius.gamma_dec") * control.delta, dnorm) / tau
|
|
555
589
|
else:
|
|
556
|
-
control.delta = min(params("growing.gamma_dec") * control.delta, dnorm) # different gamma_dec
|
|
590
|
+
control.delta = min(params("growing.gamma_dec") * control.delta, dnorm) / tau # different gamma_dec
|
|
557
591
|
if params("logging.save_diagnostic_info"):
|
|
558
592
|
diagnostic_info.update_iter_type(ITER_ACCEPTABLE_NO_GEOM if ratio > 0.0
|
|
559
593
|
else ITER_UNSUCCESSFUL_NO_GEOM) # we flag geom update below
|
|
@@ -603,7 +637,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
603
637
|
knew = control.model.npt()
|
|
604
638
|
|
|
605
639
|
if do_logging:
|
|
606
|
-
|
|
640
|
+
module_logger.debug("Updating with knew = %i" % knew)
|
|
607
641
|
control.model.change_point(knew, xnew, rvec_list[0, :]) # expect step, not absolute x
|
|
608
642
|
for i in range(1, num_samples_run):
|
|
609
643
|
control.model.add_new_sample(knew, rvec_extra=rvec_list[i, :])
|
|
@@ -615,7 +649,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
615
649
|
diagnostic_info.update_slow_iter(1 if this_iter_slow else 0)
|
|
616
650
|
if finished_growing and should_terminate:
|
|
617
651
|
if do_logging:
|
|
618
|
-
|
|
652
|
+
module_logger.info("Slow iteration - terminating/restarting")
|
|
619
653
|
if params("restarts.use_restarts") and params("restarts.use_soft_restarts"):
|
|
620
654
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
621
655
|
exit_info = control.soft_restart(number_of_samples, nruns_so_far, params,
|
|
@@ -637,7 +671,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
637
671
|
break # quit
|
|
638
672
|
|
|
639
673
|
# Update list of successful steps
|
|
640
|
-
this_step_was_not_improvement = control.model.
|
|
674
|
+
this_step_was_not_improvement = control.model.objsave is not None and control.model.objopt() > control.model.objsave
|
|
641
675
|
succ_steps_not_improvement.pop() # remove last item
|
|
642
676
|
succ_steps_not_improvement.insert(0, this_step_was_not_improvement) # add at beginning
|
|
643
677
|
# Terminate (not restart) if all are True
|
|
@@ -649,7 +683,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
649
683
|
# While growing, (optionally) add new directions
|
|
650
684
|
if not finished_growing and params("growing.num_new_dirns_each_iter") > 0:
|
|
651
685
|
if do_logging:
|
|
652
|
-
|
|
686
|
+
module_logger.debug("Still growing: adding %g new directions" % params("growing.num_new_dirns_each_iter"))
|
|
653
687
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
654
688
|
exit_info = control.add_new_direction_while_growing(number_of_samples, params)
|
|
655
689
|
if exit_info is not None:
|
|
@@ -682,7 +716,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
682
716
|
control.model.npt() - 1) # cap at number of points
|
|
683
717
|
if finished_growing and ratio > 0.0 and num_regression_steps > 0:
|
|
684
718
|
if do_logging:
|
|
685
|
-
|
|
719
|
+
module_logger.info("Regression: moving %g points" % num_regression_steps)
|
|
686
720
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
687
721
|
if params("regression.momentum_extra_steps"): # move points as random extra steps
|
|
688
722
|
exit_info = control.move_furthest_points_momentum(d, number_of_samples, num_regression_steps, params)
|
|
@@ -729,7 +763,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
729
763
|
np.log(np.maximum(restart_auto_detect_chgJ, 1e-15)))
|
|
730
764
|
|
|
731
765
|
if do_logging:
|
|
732
|
-
|
|
766
|
+
module_logger.debug("Iter %g: (slope, intercept, r_value) = (%g, %g, %g)" % (current_iter, slope, intercept, r_value))
|
|
733
767
|
if slope > params("restarts.auto_detect.min_chgJ_slope") \
|
|
734
768
|
and r_value > params("restarts.auto_detect.min_correl"):
|
|
735
769
|
# increasing trend, with at least some positive correlation
|
|
@@ -740,9 +774,9 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
740
774
|
# Data available (full NumPy vectors of fixed length): restart_auto_detect_delta, restart_auto_detect_chgJ
|
|
741
775
|
if do_restart and params("restarts.use_soft_restarts"):
|
|
742
776
|
if do_logging:
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
777
|
+
module_logger.info("Auto detection: need to do a restart")
|
|
778
|
+
module_logger.debug("delta history = %s" % str(restart_auto_detect_delta))
|
|
779
|
+
module_logger.debug("chgJ history = %s" % str(restart_auto_detect_chgJ))
|
|
746
780
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
747
781
|
exit_info = control.soft_restart(number_of_samples, nruns_so_far, params,
|
|
748
782
|
x_in_abs_coords_to_save=None, rvec_to_save=None,
|
|
@@ -759,7 +793,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
759
793
|
continue # next iteration
|
|
760
794
|
elif do_restart:
|
|
761
795
|
if do_logging:
|
|
762
|
-
|
|
796
|
+
module_logger.info("Auto detection: need to do a restart")
|
|
763
797
|
exit_info = ExitInformation(EXIT_AUTO_DETECT_RESTART_WARNING, "Auto-detected restart")
|
|
764
798
|
nruns_so_far += 1
|
|
765
799
|
break # quit
|
|
@@ -767,7 +801,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
767
801
|
|
|
768
802
|
# Otherwise (ratio < eta1 = 0.1), check & fix geometry
|
|
769
803
|
if do_logging:
|
|
770
|
-
|
|
804
|
+
module_logger.debug("Checking and possibly improving geometry (unsuccessful step)")
|
|
771
805
|
distsq = max((2.0 * control.delta) ** 2, (10.0 * control.rho) ** 2)
|
|
772
806
|
update_delta = False
|
|
773
807
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
@@ -812,12 +846,12 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
812
846
|
# Reduce rho
|
|
813
847
|
control.reduce_rho(current_iter, params)
|
|
814
848
|
if do_logging:
|
|
815
|
-
|
|
849
|
+
module_logger.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
|
|
816
850
|
if control.n() < params("logging.n_to_print_whole_x_vector"):
|
|
817
|
-
|
|
851
|
+
module_logger.debug("Best so far: f = %.15g at x = " % (control.model.objopt())
|
|
818
852
|
+ str(control.model.xopt(abs_coordinates=True)))
|
|
819
853
|
else:
|
|
820
|
-
|
|
854
|
+
module_logger.debug("Best so far: f = %.15g at x = [...]" % (control.model.objopt()))
|
|
821
855
|
continue # next iteration
|
|
822
856
|
else:
|
|
823
857
|
# Quit on rho=rhoend
|
|
@@ -843,16 +877,16 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
843
877
|
# (end main loop)
|
|
844
878
|
|
|
845
879
|
# Quit & return the important information
|
|
846
|
-
x, rvec,
|
|
880
|
+
x, rvec, obj, jacmin, nsamples = control.model.get_final_results()
|
|
847
881
|
if do_logging:
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return x, rvec,
|
|
882
|
+
module_logger.debug("At return from DFO-LS, number of function evals = %i" % nf)
|
|
883
|
+
module_logger.debug("Smallest objective value = %.15g at x = " % obj + str(x))
|
|
884
|
+
return x, rvec, obj, jacmin, nsamples, control.nf, control.nx, nruns_so_far, exit_info, diagnostic_info
|
|
851
885
|
|
|
852
886
|
|
|
853
|
-
def solve(objfun, x0,
|
|
887
|
+
def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argsprox=(), bounds=None, projections=[], npt=None, rhobeg=None, rhoend=1e-8, maxfun=None, nsamples=None, user_params=None,
|
|
854
888
|
objfun_has_noise=False, scaling_within_bounds=False, do_logging=True, print_progress=False):
|
|
855
|
-
x0 = x0.astype(
|
|
889
|
+
x0 = x0.astype(float)
|
|
856
890
|
n = len(x0)
|
|
857
891
|
|
|
858
892
|
# Set missing inputs (if not specified) to some sensible defaults
|
|
@@ -861,13 +895,17 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
861
895
|
xu = None
|
|
862
896
|
else:
|
|
863
897
|
assert len(bounds) == 2, "bounds must be a 2-tuple of (lower, upper), where both are arrays of size(x0)"
|
|
864
|
-
xl = bounds[0].astype(
|
|
865
|
-
xu = bounds[1].astype(
|
|
898
|
+
xl = bounds[0].astype(float) if bounds[0] is not None else None
|
|
899
|
+
xu = bounds[1].astype(float) if bounds[1] is not None else None
|
|
866
900
|
|
|
867
901
|
if (xl is None or xu is None) and scaling_within_bounds:
|
|
868
902
|
scaling_within_bounds = False
|
|
869
903
|
warnings.warn("Ignoring scaling_within_bounds=True for unconstrained problem/1-sided bounds", RuntimeWarning)
|
|
870
904
|
|
|
905
|
+
if projections and scaling_within_bounds:
|
|
906
|
+
scaling_within_bounds = False
|
|
907
|
+
warnings.warn("Ignoring scaling_within_bounds=True for problem with arbitrary constraints", RuntimeWarning)
|
|
908
|
+
|
|
871
909
|
if xl is None:
|
|
872
910
|
xl = -1e20 * np.ones((n,)) # unconstrained
|
|
873
911
|
if xu is None:
|
|
@@ -881,6 +919,18 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
881
919
|
if nsamples is None:
|
|
882
920
|
nsamples = lambda delta, rho, iter, nruns: 1 # no averaging
|
|
883
921
|
|
|
922
|
+
# If using arbitrary constraints, create projection from bounds
|
|
923
|
+
if projections:
|
|
924
|
+
xlb = xl.copy()
|
|
925
|
+
xub = xu.copy()
|
|
926
|
+
bproj = lambda w: pbox(w,xlb,xub)
|
|
927
|
+
projections = list(projections)
|
|
928
|
+
projections.append(bproj)
|
|
929
|
+
|
|
930
|
+
# since using arbitrary constraints, don't constrain otherwise
|
|
931
|
+
xl = -1e20 * np.ones((n,))
|
|
932
|
+
xu = 1e20 * np.ones((n,))
|
|
933
|
+
|
|
884
934
|
# Set parameters
|
|
885
935
|
params = ParameterList(int(n), int(npt), int(maxfun), objfun_has_noise=objfun_has_noise) # make sure int, not np.int
|
|
886
936
|
if user_params is not None:
|
|
@@ -904,13 +954,21 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
904
954
|
|
|
905
955
|
exit_info = None
|
|
906
956
|
# Input & parameter checks
|
|
957
|
+
if exit_info is None and h is not None:
|
|
958
|
+
if prox_uh is None:
|
|
959
|
+
exit_info = ExitInformation(EXIT_INPUT_ERROR, "Must provide prox_uh input if h is not None")
|
|
960
|
+
elif lh is None:
|
|
961
|
+
exit_info = ExitInformation(EXIT_INPUT_ERROR, "Must provide lh input if h is not None")
|
|
962
|
+
elif lh <= 0.0:
|
|
963
|
+
exit_info = ExitInformation(EXIT_INPUT_ERROR, "lh must be strictly positive")
|
|
964
|
+
|
|
907
965
|
if exit_info is None and npt < n + 1:
|
|
908
966
|
exit_info = ExitInformation(EXIT_INPUT_ERROR, "npt must be >= n+1 for linear models with inexact interpolation")
|
|
909
967
|
|
|
910
|
-
if exit_info is None and rhobeg
|
|
968
|
+
if exit_info is None and rhobeg <= 0.0:
|
|
911
969
|
exit_info = ExitInformation(EXIT_INPUT_ERROR, "rhobeg must be strictly positive")
|
|
912
970
|
|
|
913
|
-
if exit_info is None and rhoend
|
|
971
|
+
if exit_info is None and rhoend <= 0.0:
|
|
914
972
|
exit_info = ExitInformation(EXIT_INPUT_ERROR, "rhoend must be strictly positive")
|
|
915
973
|
|
|
916
974
|
if exit_info is None and rhobeg <= rhoend:
|
|
@@ -975,13 +1033,20 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
975
1033
|
results = OptimResults(None, None, None, None, 0, 0, 0, exit_flag, exit_msg)
|
|
976
1034
|
return results
|
|
977
1035
|
|
|
1036
|
+
# Enforce arbitrary constraint bounds on x0
|
|
1037
|
+
if projections:
|
|
1038
|
+
xp = dykstra(projections,x0,max_iter=params("dykstra.max_iters"),tol=params("dykstra.d_tol"))
|
|
1039
|
+
if not np.allclose(xp,x0):
|
|
1040
|
+
warnings.warn("x0 not feasible w.r.t given constraints, adjusting", RuntimeWarning)
|
|
1041
|
+
x0 = xp.copy()
|
|
1042
|
+
|
|
978
1043
|
# Enforce lower & upper bounds on x0
|
|
979
|
-
idx = (x0
|
|
1044
|
+
idx = (x0 < xl)
|
|
980
1045
|
if np.any(idx):
|
|
981
1046
|
warnings.warn("x0 below lower bound, adjusting", RuntimeWarning)
|
|
982
1047
|
x0[idx] = xl[idx]
|
|
983
1048
|
|
|
984
|
-
idx = (x0
|
|
1049
|
+
idx = (x0 > xu)
|
|
985
1050
|
if np.any(idx):
|
|
986
1051
|
warnings.warn("x0 above upper bound, adjusting", RuntimeWarning)
|
|
987
1052
|
x0[idx] = xu[idx]
|
|
@@ -991,9 +1056,9 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
991
1056
|
nruns = 0
|
|
992
1057
|
nf = 0
|
|
993
1058
|
nx = 0
|
|
994
|
-
xmin, rmin,
|
|
995
|
-
solve_main(objfun, x0,
|
|
996
|
-
diagnostic_info, scaling_changes, default_growing_method_set_by_user=default_growing_method_set_by_user,
|
|
1059
|
+
xmin, rmin, objmin, jacmin, nsamples_min, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
1060
|
+
solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1061
|
+
diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, default_growing_method_set_by_user=default_growing_method_set_by_user,
|
|
997
1062
|
do_logging=do_logging, print_progress=print_progress)
|
|
998
1063
|
|
|
999
1064
|
# Hard restarts loop
|
|
@@ -1007,28 +1072,28 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
1007
1072
|
npt = min(npt, params("restarts.max_npt"))
|
|
1008
1073
|
|
|
1009
1074
|
if do_logging:
|
|
1010
|
-
|
|
1011
|
-
% (
|
|
1075
|
+
module_logger.info("Restarting from finish point (f = %g) after %g function evals; using rhobeg = %g and rhoend = %g"
|
|
1076
|
+
% (objmin, nf, rhobeg, rhoend))
|
|
1012
1077
|
if params("restarts.hard.use_old_rk"):
|
|
1013
|
-
xmin2, rmin2,
|
|
1014
|
-
solve_main(objfun, xmin,
|
|
1015
|
-
diagnostic_info, scaling_changes, r0_avg_old=rmin, r0_nsamples_old=nsamples_min,
|
|
1078
|
+
xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
1079
|
+
solve_main(objfun, xmin, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1080
|
+
diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, r0_avg_old=rmin, r0_nsamples_old=nsamples_min,
|
|
1016
1081
|
do_logging=do_logging, print_progress=print_progress)
|
|
1017
1082
|
else:
|
|
1018
|
-
xmin2, rmin2,
|
|
1019
|
-
solve_main(objfun, xmin,
|
|
1020
|
-
diagnostic_info, scaling_changes, do_logging=do_logging, print_progress=print_progress)
|
|
1083
|
+
xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
1084
|
+
solve_main(objfun, xmin, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1085
|
+
diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, do_logging=do_logging, print_progress=print_progress)
|
|
1021
1086
|
|
|
1022
|
-
if
|
|
1087
|
+
if objmin2 < objmin or np.isnan(objmin):
|
|
1023
1088
|
if do_logging:
|
|
1024
|
-
|
|
1089
|
+
module_logger.info("Successful run with new f = %s compared to old f = %s" % (objmin2, objmin))
|
|
1025
1090
|
last_successful_run = nruns
|
|
1026
|
-
(xmin, rmin,
|
|
1091
|
+
(xmin, rmin, objmin, nsamples_min) = (xmin2, rmin2, objmin2, nsamples2)
|
|
1027
1092
|
if jacmin2 is not None: # may be None if finished during setup phase, in which case just use old Jacobian
|
|
1028
1093
|
jacmin = jacmin2
|
|
1029
1094
|
else:
|
|
1030
1095
|
if do_logging:
|
|
1031
|
-
|
|
1096
|
+
module_logger.info("Unsuccessful run with new f = %s compared to old f = %s" % (objmin2, objmin))
|
|
1032
1097
|
|
|
1033
1098
|
if nruns - last_successful_run >= params("restarts.max_unsuccessful_restarts"):
|
|
1034
1099
|
exit_info = ExitInformation(EXIT_SUCCESS, "Reached maximum number of unsuccessful restarts")
|
|
@@ -1040,13 +1105,13 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
1040
1105
|
if scaling_changes is not None and jacmin is not None:
|
|
1041
1106
|
for i in range(n):
|
|
1042
1107
|
jacmin[:, i] = jacmin[:, i] / scaling_changes[1][i]
|
|
1043
|
-
results = OptimResults(remove_scaling(xmin, scaling_changes), rmin,
|
|
1108
|
+
results = OptimResults(remove_scaling(xmin, scaling_changes), rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg)
|
|
1044
1109
|
if params("logging.save_diagnostic_info"):
|
|
1045
1110
|
df = diagnostic_info.to_dataframe(with_xk=params("logging.save_xk"), with_rk=params("logging.save_rk"))
|
|
1046
1111
|
results.diagnostic_info = df
|
|
1047
1112
|
|
|
1048
1113
|
if do_logging:
|
|
1049
|
-
|
|
1114
|
+
module_logger.info("Did a total of %g run(s)" % nruns)
|
|
1050
1115
|
|
|
1051
1116
|
return results
|
|
1052
1117
|
|