DFO-LS 1.2.1__py3-none-any.whl → 1.4.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.2.1.dist-info → DFO_LS-1.4.1.dist-info}/METADATA +58 -32
- DFO_LS-1.4.1.dist-info/RECORD +14 -0
- {DFO_LS-1.2.1.dist-info → DFO_LS-1.4.1.dist-info}/WHEEL +1 -1
- {DFO_LS-1.2.1.dist-info → DFO_LS-1.4.1.dist-info}/top_level.txt +0 -0
- dfols/__init__.py +4 -5
- dfols/controller.py +148 -24
- dfols/hessian.py +1 -1
- dfols/model.py +20 -6
- dfols/params.py +14 -0
- dfols/solver.py +84 -47
- dfols/trust_region.py +156 -5
- dfols/util.py +53 -3
- 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.4.1.dist-info}/LICENSE.txt +0 -0
dfols/params.py
CHANGED
|
@@ -44,6 +44,7 @@ class ParameterList(object):
|
|
|
44
44
|
self.params["init.random_directions_make_orthogonal"] = True # although random > orthogonal, avoid for init
|
|
45
45
|
# Interpolation
|
|
46
46
|
self.params["interpolation.precondition"] = True
|
|
47
|
+
self.params["interpolation.throw_error_on_nans"] = False # throw numpy.linalg.LinAlgError if interpolating to nan data?
|
|
47
48
|
# Logging
|
|
48
49
|
self.params["logging.n_to_print_whole_x_vector"] = 6
|
|
49
50
|
self.params["logging.save_diagnostic_info"] = False
|
|
@@ -108,6 +109,11 @@ class ParameterList(object):
|
|
|
108
109
|
self.params["growing.full_rank.min_sing_val"] = 1e-6 # absolute floor on singular values
|
|
109
110
|
self.params["growing.full_rank.svd_max_jac_cond"] = 1e8 # maximum condition number of Jacobian
|
|
110
111
|
self.params["growing.perturb_trust_region_step"] = False # add random direction onto TRS solution?
|
|
112
|
+
# Dykstra's algorithm
|
|
113
|
+
self.params["dykstra.d_tol"] = 1e-10
|
|
114
|
+
self.params["dykstra.max_iters"] = 100
|
|
115
|
+
# Matrix rank algorithm
|
|
116
|
+
self.params["matrix_rank.r_tol"] = 1e-18
|
|
111
117
|
|
|
112
118
|
self.params_changed = {}
|
|
113
119
|
for p in self.params:
|
|
@@ -142,6 +148,8 @@ class ParameterList(object):
|
|
|
142
148
|
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
|
|
143
149
|
elif key == "interpolation.precondition":
|
|
144
150
|
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
|
|
151
|
+
elif key == "interpolation.throw_error_on_nans":
|
|
152
|
+
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
|
|
145
153
|
elif key == "logging.n_to_print_whole_x_vector":
|
|
146
154
|
type_str, nonetype_ok, lower, upper = 'int', False, 0, None
|
|
147
155
|
elif key == "logging.save_diagnostic_info":
|
|
@@ -254,6 +262,12 @@ class ParameterList(object):
|
|
|
254
262
|
type_str, nonetype_ok, lower, upper = 'float', True, 1.0, None
|
|
255
263
|
elif key == "growing.perturb_trust_region_step":
|
|
256
264
|
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
|
|
265
|
+
elif key == "dykstra.d_tol":
|
|
266
|
+
type_str, nonetype_ok, lower, upper = 'float', False, 0.0, None
|
|
267
|
+
elif key == "dykstra.max_iters":
|
|
268
|
+
type_str, nonetype_ok, lower, upper = 'int', False, 0, None
|
|
269
|
+
elif key == "matrix_rank.r_tol":
|
|
270
|
+
type_str, nonetype_ok, lower, upper = 'float', False, 0.0, None
|
|
257
271
|
else:
|
|
258
272
|
assert False, "ParameterList.param_type() has unknown key: %s" % key
|
|
259
273
|
return type_str, nonetype_ok, lower, upper
|
dfols/solver.py
CHANGED
|
@@ -43,6 +43,8 @@ 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):
|
|
@@ -93,7 +95,7 @@ class OptimResults(object):
|
|
|
93
95
|
return output
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_far, nf_so_far, nx_so_far, nsamples, params,
|
|
98
|
+
def solve_main(objfun, x0, args, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns_so_far, nf_so_far, nx_so_far, nsamples, params,
|
|
97
99
|
diagnostic_info, scaling_changes, 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)
|
|
@@ -153,14 +155,14 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
153
155
|
# However, this can fail for m<n, so need to use an alternative method (perturb_trust_region_step)
|
|
154
156
|
if m < len(x0):
|
|
155
157
|
if do_logging:
|
|
156
|
-
|
|
158
|
+
module_logger.debug("Inverse problem (m<n), switching default growing method")
|
|
157
159
|
params('growing.full_rank.use_full_rank_interp', new_value=False)
|
|
158
160
|
params('growing.perturb_trust_region_step', new_value=True)
|
|
159
161
|
if not params.params_changed['growing.delta_scale_new_dirns']:
|
|
160
162
|
params('growing.delta_scale_new_dirns', new_value=0.1)
|
|
161
163
|
|
|
162
164
|
# Initialise controller
|
|
163
|
-
control = Controller(objfun, args, x0, r0_avg, num_samples_run, xl, xu, npt, rhobeg, rhoend, nf, nx, maxfun,
|
|
165
|
+
control = Controller(objfun, args, x0, r0_avg, num_samples_run, xl, xu, projections, npt, rhobeg, rhoend, nf, nx, maxfun,
|
|
164
166
|
params, scaling_changes, do_logging)
|
|
165
167
|
|
|
166
168
|
# Initialise interpolation set
|
|
@@ -169,11 +171,11 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
169
171
|
npt - 1) # cap at npt
|
|
170
172
|
if params("init.random_initial_directions"):
|
|
171
173
|
if do_logging:
|
|
172
|
-
|
|
174
|
+
module_logger.info("Initialising (random directions)")
|
|
173
175
|
exit_info = control.initialise_random_directions(number_of_samples, num_directions, params)
|
|
174
176
|
else:
|
|
175
177
|
if do_logging:
|
|
176
|
-
|
|
178
|
+
module_logger.info("Initialising (coordinate directions)")
|
|
177
179
|
exit_info = control.initialise_coordinate_directions(number_of_samples, num_directions, params)
|
|
178
180
|
if exit_info is not None:
|
|
179
181
|
x, rvec, f, jacmin, nsamples = control.model.get_final_results()
|
|
@@ -195,18 +197,18 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
195
197
|
# ------------------------------------------
|
|
196
198
|
current_iter = -1
|
|
197
199
|
if do_logging:
|
|
198
|
-
|
|
200
|
+
module_logger.info("Beginning main loop")
|
|
199
201
|
if print_progress:
|
|
200
202
|
print("{:^5}{:^7}{:^10}{:^10}{:^10}{:^10}{:^7}".format("Run", "Iter", "Obj", "Grad", "Delta", "rho", "Evals"))
|
|
201
203
|
while True:
|
|
202
204
|
current_iter += 1
|
|
203
205
|
|
|
204
206
|
if do_logging:
|
|
205
|
-
|
|
207
|
+
module_logger.debug("*** Iter %g (delta = %g, rho = %g) ***" % (current_iter, control.delta, control.rho))
|
|
206
208
|
|
|
207
209
|
if (not finished_growing) and control.model.npt() >= control.model.num_pts:
|
|
208
210
|
if do_logging:
|
|
209
|
-
|
|
211
|
+
module_logger.info("Finished growing init set")
|
|
210
212
|
finished_growing = True
|
|
211
213
|
|
|
212
214
|
if params("growing.reset_delta"):
|
|
@@ -217,7 +219,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
217
219
|
|
|
218
220
|
if not finished_growing:
|
|
219
221
|
if do_logging:
|
|
220
|
-
|
|
222
|
+
module_logger.debug("Main loop: still growing (have %g of %g pts)" % (control.model.npt(), control.model.num_pts))
|
|
221
223
|
|
|
222
224
|
# Noise level exit check
|
|
223
225
|
if finished_growing and params("noise.quit_on_noise_level") and control.all_values_within_noise_level(params):
|
|
@@ -247,7 +249,8 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
247
249
|
min_sing_val=params("growing.full_rank.min_sing_val"),
|
|
248
250
|
sing_val_frac=params("growing.full_rank.svd_scale_factor"),
|
|
249
251
|
max_jac_cond=params("growing.full_rank.svd_max_jac_cond"),
|
|
250
|
-
get_chg_J=params("restarts.use_restarts") and params("restarts.auto_detect")
|
|
252
|
+
get_chg_J=params("restarts.use_restarts") and params("restarts.auto_detect"),
|
|
253
|
+
throw_error_on_nans=params("interpolation.throw_error_on_nans"))
|
|
251
254
|
if not interp_ok:
|
|
252
255
|
if params("restarts.use_restarts") and params("restarts.use_soft_restarts"):
|
|
253
256
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
@@ -270,9 +273,9 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
270
273
|
|
|
271
274
|
|
|
272
275
|
# Trust region step
|
|
273
|
-
d, gopt, H, gnew, crvmin = control.trust_region_step()
|
|
276
|
+
d, gopt, H, gnew, crvmin = control.trust_region_step(params)
|
|
274
277
|
if do_logging:
|
|
275
|
-
|
|
278
|
+
module_logger.debug("Trust region step is d = " + str(d))
|
|
276
279
|
xnew = control.model.xopt() + d
|
|
277
280
|
dnorm = min(LA.norm(d), control.delta)
|
|
278
281
|
|
|
@@ -288,7 +291,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
288
291
|
|
|
289
292
|
if dnorm < params("general.safety_step_thresh") * control.rho and not finished_growing and params("growing.safety.do_safety_step"):
|
|
290
293
|
if do_logging:
|
|
291
|
-
|
|
294
|
+
module_logger.debug("Safety step during growing phase")
|
|
292
295
|
|
|
293
296
|
if params("logging.save_diagnostic_info"):
|
|
294
297
|
diagnostic_info.update_ratio(np.nan)
|
|
@@ -366,7 +369,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
366
369
|
elif dnorm < params("general.safety_step_thresh") * control.rho and finished_growing:
|
|
367
370
|
# (start safety step)
|
|
368
371
|
if do_logging:
|
|
369
|
-
|
|
372
|
+
module_logger.debug("Safety step (main phase)")
|
|
370
373
|
|
|
371
374
|
if params("logging.save_diagnostic_info"):
|
|
372
375
|
diagnostic_info.update_ratio(np.nan)
|
|
@@ -410,12 +413,12 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
410
413
|
# Reduce rho
|
|
411
414
|
control.reduce_rho(current_iter, params)
|
|
412
415
|
if do_logging:
|
|
413
|
-
|
|
416
|
+
module_logger.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
|
|
414
417
|
if control.n() < params("logging.n_to_print_whole_x_vector"):
|
|
415
|
-
|
|
418
|
+
module_logger.debug("Best so far: f = %.15g at x = " % (control.model.fopt())
|
|
416
419
|
+ str(control.model.xopt(abs_coordinates=True)))
|
|
417
420
|
else:
|
|
418
|
-
|
|
421
|
+
module_logger.debug("Best so far: f = %.15g at x = [...]" % (control.model.fopt()))
|
|
419
422
|
continue # next iteration
|
|
420
423
|
else:
|
|
421
424
|
# Quit on rho=rhoend
|
|
@@ -455,18 +458,18 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
455
458
|
else:
|
|
456
459
|
# (start trust region step)
|
|
457
460
|
if do_logging:
|
|
458
|
-
|
|
461
|
+
module_logger.debug("Standard trust region step")
|
|
459
462
|
|
|
460
463
|
# If growing, optionally perturb the trust region step in a new direction
|
|
461
464
|
if not finished_growing and params("growing.perturb_trust_region_step"):
|
|
462
465
|
step_length = params('growing.delta_scale_new_dirns') * control.delta
|
|
463
466
|
dirn = control.get_new_direction_for_growing(step_length)
|
|
464
467
|
if do_logging:
|
|
465
|
-
|
|
468
|
+
module_logger.debug("Perturbing trust region with step = %s" % str(dirn))
|
|
466
469
|
d += dirn
|
|
467
470
|
xnew += dirn
|
|
468
471
|
if do_logging:
|
|
469
|
-
|
|
472
|
+
module_logger.debug("New trust region step = %s" % str(d))
|
|
470
473
|
|
|
471
474
|
# If finished growing, add chgJ and delta to restart auto-detect set
|
|
472
475
|
if finished_growing and params("restarts.use_restarts") and params("restarts.auto_detect"):
|
|
@@ -513,6 +516,17 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
513
516
|
x = control.model.as_absolute_coordinates(xnew)
|
|
514
517
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
515
518
|
rvec_list, f_list, num_samples_run, exit_info = control.evaluate_objective(x, number_of_samples, params)
|
|
519
|
+
if np.any(np.isnan(rvec_list)):
|
|
520
|
+
# Just exit without saving the current point
|
|
521
|
+
# We should be able to do a hard restart though, because it's unlikely
|
|
522
|
+
# that we will get the same trust-region step after expanding the radius/re-initialising
|
|
523
|
+
module_logger.warning("NaN encountered in evaluation of trust-region step")
|
|
524
|
+
if params("interpolation.throw_error_on_nans"):
|
|
525
|
+
raise np.linalg.LinAlgError("NaN encountered in objective evaluations")
|
|
526
|
+
|
|
527
|
+
exit_info = ExitInformation(EXIT_EVAL_ERROR, "NaN received from objective function evaluation")
|
|
528
|
+
nruns_so_far += 1
|
|
529
|
+
break # quit
|
|
516
530
|
if exit_info is not None:
|
|
517
531
|
if num_samples_run > 0:
|
|
518
532
|
control.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
|
|
@@ -545,7 +559,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
545
559
|
|
|
546
560
|
# Update delta
|
|
547
561
|
if do_logging:
|
|
548
|
-
|
|
562
|
+
module_logger.debug("Ratio = %g" % ratio)
|
|
549
563
|
if params("logging.save_diagnostic_info"):
|
|
550
564
|
diagnostic_info.update_ratio(ratio)
|
|
551
565
|
diagnostic_info.update_slow_iter(-1) # n/a, unless otherwise update
|
|
@@ -603,7 +617,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
603
617
|
knew = control.model.npt()
|
|
604
618
|
|
|
605
619
|
if do_logging:
|
|
606
|
-
|
|
620
|
+
module_logger.debug("Updating with knew = %i" % knew)
|
|
607
621
|
control.model.change_point(knew, xnew, rvec_list[0, :]) # expect step, not absolute x
|
|
608
622
|
for i in range(1, num_samples_run):
|
|
609
623
|
control.model.add_new_sample(knew, rvec_extra=rvec_list[i, :])
|
|
@@ -615,7 +629,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
615
629
|
diagnostic_info.update_slow_iter(1 if this_iter_slow else 0)
|
|
616
630
|
if finished_growing and should_terminate:
|
|
617
631
|
if do_logging:
|
|
618
|
-
|
|
632
|
+
module_logger.info("Slow iteration - terminating/restarting")
|
|
619
633
|
if params("restarts.use_restarts") and params("restarts.use_soft_restarts"):
|
|
620
634
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
621
635
|
exit_info = control.soft_restart(number_of_samples, nruns_so_far, params,
|
|
@@ -649,7 +663,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
649
663
|
# While growing, (optionally) add new directions
|
|
650
664
|
if not finished_growing and params("growing.num_new_dirns_each_iter") > 0:
|
|
651
665
|
if do_logging:
|
|
652
|
-
|
|
666
|
+
module_logger.debug("Still growing: adding %g new directions" % params("growing.num_new_dirns_each_iter"))
|
|
653
667
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
654
668
|
exit_info = control.add_new_direction_while_growing(number_of_samples, params)
|
|
655
669
|
if exit_info is not None:
|
|
@@ -682,7 +696,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
682
696
|
control.model.npt() - 1) # cap at number of points
|
|
683
697
|
if finished_growing and ratio > 0.0 and num_regression_steps > 0:
|
|
684
698
|
if do_logging:
|
|
685
|
-
|
|
699
|
+
module_logger.info("Regression: moving %g points" % num_regression_steps)
|
|
686
700
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
687
701
|
if params("regression.momentum_extra_steps"): # move points as random extra steps
|
|
688
702
|
exit_info = control.move_furthest_points_momentum(d, number_of_samples, num_regression_steps, params)
|
|
@@ -729,7 +743,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
729
743
|
np.log(np.maximum(restart_auto_detect_chgJ, 1e-15)))
|
|
730
744
|
|
|
731
745
|
if do_logging:
|
|
732
|
-
|
|
746
|
+
module_logger.debug("Iter %g: (slope, intercept, r_value) = (%g, %g, %g)" % (current_iter, slope, intercept, r_value))
|
|
733
747
|
if slope > params("restarts.auto_detect.min_chgJ_slope") \
|
|
734
748
|
and r_value > params("restarts.auto_detect.min_correl"):
|
|
735
749
|
# increasing trend, with at least some positive correlation
|
|
@@ -740,9 +754,9 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
740
754
|
# Data available (full NumPy vectors of fixed length): restart_auto_detect_delta, restart_auto_detect_chgJ
|
|
741
755
|
if do_restart and params("restarts.use_soft_restarts"):
|
|
742
756
|
if do_logging:
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
757
|
+
module_logger.info("Auto detection: need to do a restart")
|
|
758
|
+
module_logger.debug("delta history = %s" % str(restart_auto_detect_delta))
|
|
759
|
+
module_logger.debug("chgJ history = %s" % str(restart_auto_detect_chgJ))
|
|
746
760
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
747
761
|
exit_info = control.soft_restart(number_of_samples, nruns_so_far, params,
|
|
748
762
|
x_in_abs_coords_to_save=None, rvec_to_save=None,
|
|
@@ -759,7 +773,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
759
773
|
continue # next iteration
|
|
760
774
|
elif do_restart:
|
|
761
775
|
if do_logging:
|
|
762
|
-
|
|
776
|
+
module_logger.info("Auto detection: need to do a restart")
|
|
763
777
|
exit_info = ExitInformation(EXIT_AUTO_DETECT_RESTART_WARNING, "Auto-detected restart")
|
|
764
778
|
nruns_so_far += 1
|
|
765
779
|
break # quit
|
|
@@ -767,7 +781,7 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
767
781
|
|
|
768
782
|
# Otherwise (ratio < eta1 = 0.1), check & fix geometry
|
|
769
783
|
if do_logging:
|
|
770
|
-
|
|
784
|
+
module_logger.debug("Checking and possibly improving geometry (unsuccessful step)")
|
|
771
785
|
distsq = max((2.0 * control.delta) ** 2, (10.0 * control.rho) ** 2)
|
|
772
786
|
update_delta = False
|
|
773
787
|
number_of_samples = max(nsamples(control.delta, control.rho, current_iter, nruns_so_far), 1)
|
|
@@ -812,12 +826,12 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
812
826
|
# Reduce rho
|
|
813
827
|
control.reduce_rho(current_iter, params)
|
|
814
828
|
if do_logging:
|
|
815
|
-
|
|
829
|
+
module_logger.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
|
|
816
830
|
if control.n() < params("logging.n_to_print_whole_x_vector"):
|
|
817
|
-
|
|
831
|
+
module_logger.debug("Best so far: f = %.15g at x = " % (control.model.fopt())
|
|
818
832
|
+ str(control.model.xopt(abs_coordinates=True)))
|
|
819
833
|
else:
|
|
820
|
-
|
|
834
|
+
module_logger.debug("Best so far: f = %.15g at x = [...]" % (control.model.fopt()))
|
|
821
835
|
continue # next iteration
|
|
822
836
|
else:
|
|
823
837
|
# Quit on rho=rhoend
|
|
@@ -845,14 +859,14 @@ def solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_f
|
|
|
845
859
|
# Quit & return the important information
|
|
846
860
|
x, rvec, f, jacmin, nsamples = control.model.get_final_results()
|
|
847
861
|
if do_logging:
|
|
848
|
-
|
|
849
|
-
|
|
862
|
+
module_logger.debug("At return from DFO-LS, number of function evals = %i" % nf)
|
|
863
|
+
module_logger.debug("Smallest objective value = %.15g at x = " % f + str(x))
|
|
850
864
|
return x, rvec, f, jacmin, nsamples, control.nf, control.nx, nruns_so_far, exit_info, diagnostic_info
|
|
851
865
|
|
|
852
866
|
|
|
853
|
-
def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8, maxfun=None, nsamples=None, user_params=None,
|
|
867
|
+
def solve(objfun, x0, args=(), bounds=None, projections=[], npt=None, rhobeg=None, rhoend=1e-8, maxfun=None, nsamples=None, user_params=None,
|
|
854
868
|
objfun_has_noise=False, scaling_within_bounds=False, do_logging=True, print_progress=False):
|
|
855
|
-
x0 = x0.astype(
|
|
869
|
+
x0 = x0.astype(float)
|
|
856
870
|
n = len(x0)
|
|
857
871
|
|
|
858
872
|
# Set missing inputs (if not specified) to some sensible defaults
|
|
@@ -861,13 +875,17 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
861
875
|
xu = None
|
|
862
876
|
else:
|
|
863
877
|
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(
|
|
878
|
+
xl = bounds[0].astype(float) if bounds[0] is not None else None
|
|
879
|
+
xu = bounds[1].astype(float) if bounds[1] is not None else None
|
|
866
880
|
|
|
867
881
|
if (xl is None or xu is None) and scaling_within_bounds:
|
|
868
882
|
scaling_within_bounds = False
|
|
869
883
|
warnings.warn("Ignoring scaling_within_bounds=True for unconstrained problem/1-sided bounds", RuntimeWarning)
|
|
870
884
|
|
|
885
|
+
if projections and scaling_within_bounds:
|
|
886
|
+
scaling_within_bounds = False
|
|
887
|
+
warnings.warn("Ignoring scaling_within_bounds=True for problem with arbitrary constraints", RuntimeWarning)
|
|
888
|
+
|
|
871
889
|
if xl is None:
|
|
872
890
|
xl = -1e20 * np.ones((n,)) # unconstrained
|
|
873
891
|
if xu is None:
|
|
@@ -881,6 +899,18 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
881
899
|
if nsamples is None:
|
|
882
900
|
nsamples = lambda delta, rho, iter, nruns: 1 # no averaging
|
|
883
901
|
|
|
902
|
+
# If using arbitrary constraints, create projection from bounds
|
|
903
|
+
if projections:
|
|
904
|
+
xlb = xl.copy()
|
|
905
|
+
xub = xu.copy()
|
|
906
|
+
bproj = lambda w: pbox(w,xlb,xub)
|
|
907
|
+
projections = list(projections)
|
|
908
|
+
projections.append(bproj)
|
|
909
|
+
|
|
910
|
+
# since using arbitrary constraints, don't constrain otherwise
|
|
911
|
+
xl = -1e20 * np.ones((n,))
|
|
912
|
+
xu = 1e20 * np.ones((n,))
|
|
913
|
+
|
|
884
914
|
# Set parameters
|
|
885
915
|
params = ParameterList(int(n), int(npt), int(maxfun), objfun_has_noise=objfun_has_noise) # make sure int, not np.int
|
|
886
916
|
if user_params is not None:
|
|
@@ -975,6 +1005,13 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
975
1005
|
results = OptimResults(None, None, None, None, 0, 0, 0, exit_flag, exit_msg)
|
|
976
1006
|
return results
|
|
977
1007
|
|
|
1008
|
+
# Enforce arbitrary constraint bounds on x0
|
|
1009
|
+
if projections:
|
|
1010
|
+
xp = dykstra(projections,x0,max_iter=params("dykstra.max_iters"),tol=params("dykstra.d_tol"))
|
|
1011
|
+
if not np.allclose(xp,x0):
|
|
1012
|
+
warnings.warn("x0 not feasible w.r.t given constraints, adjusting", RuntimeWarning)
|
|
1013
|
+
x0 = xp.copy()
|
|
1014
|
+
|
|
978
1015
|
# Enforce lower & upper bounds on x0
|
|
979
1016
|
idx = (x0 <= xl)
|
|
980
1017
|
if np.any(idx):
|
|
@@ -992,7 +1029,7 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
992
1029
|
nf = 0
|
|
993
1030
|
nx = 0
|
|
994
1031
|
xmin, rmin, fmin, jacmin, nsamples_min, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
995
|
-
solve_main(objfun, x0, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1032
|
+
solve_main(objfun, x0, args, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
996
1033
|
diagnostic_info, scaling_changes, default_growing_method_set_by_user=default_growing_method_set_by_user,
|
|
997
1034
|
do_logging=do_logging, print_progress=print_progress)
|
|
998
1035
|
|
|
@@ -1007,28 +1044,28 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
1007
1044
|
npt = min(npt, params("restarts.max_npt"))
|
|
1008
1045
|
|
|
1009
1046
|
if do_logging:
|
|
1010
|
-
|
|
1047
|
+
module_logger.info("Restarting from finish point (f = %g) after %g function evals; using rhobeg = %g and rhoend = %g"
|
|
1011
1048
|
% (fmin, nf, rhobeg, rhoend))
|
|
1012
1049
|
if params("restarts.hard.use_old_rk"):
|
|
1013
1050
|
xmin2, rmin2, fmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
1014
|
-
solve_main(objfun, xmin, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1051
|
+
solve_main(objfun, xmin, args, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1015
1052
|
diagnostic_info, scaling_changes, r0_avg_old=rmin, r0_nsamples_old=nsamples_min,
|
|
1016
1053
|
do_logging=do_logging, print_progress=print_progress)
|
|
1017
1054
|
else:
|
|
1018
1055
|
xmin2, rmin2, fmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
|
|
1019
|
-
solve_main(objfun, xmin, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1056
|
+
solve_main(objfun, xmin, args, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
|
|
1020
1057
|
diagnostic_info, scaling_changes, do_logging=do_logging, print_progress=print_progress)
|
|
1021
1058
|
|
|
1022
1059
|
if fmin2 < fmin or np.isnan(fmin):
|
|
1023
1060
|
if do_logging:
|
|
1024
|
-
|
|
1061
|
+
module_logger.info("Successful run with new f = %s compared to old f = %s" % (fmin2, fmin))
|
|
1025
1062
|
last_successful_run = nruns
|
|
1026
1063
|
(xmin, rmin, fmin, nsamples_min) = (xmin2, rmin2, fmin2, nsamples2)
|
|
1027
1064
|
if jacmin2 is not None: # may be None if finished during setup phase, in which case just use old Jacobian
|
|
1028
1065
|
jacmin = jacmin2
|
|
1029
1066
|
else:
|
|
1030
1067
|
if do_logging:
|
|
1031
|
-
|
|
1068
|
+
module_logger.info("Unsuccessful run with new f = %s compared to old f = %s" % (fmin2, fmin))
|
|
1032
1069
|
|
|
1033
1070
|
if nruns - last_successful_run >= params("restarts.max_unsuccessful_restarts"):
|
|
1034
1071
|
exit_info = ExitInformation(EXIT_SUCCESS, "Reached maximum number of unsuccessful restarts")
|
|
@@ -1046,7 +1083,7 @@ def solve(objfun, x0, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8,
|
|
|
1046
1083
|
results.diagnostic_info = df
|
|
1047
1084
|
|
|
1048
1085
|
if do_logging:
|
|
1049
|
-
|
|
1086
|
+
module_logger.info("Did a total of %g run(s)" % nruns)
|
|
1050
1087
|
|
|
1051
1088
|
return results
|
|
1052
1089
|
|
dfols/trust_region.py
CHANGED
|
@@ -11,6 +11,15 @@ produces a new vector d which (approximately) solves the trust region subproblem
|
|
|
11
11
|
The other outputs: gnew is the gradient of the model at d, and crvmin has
|
|
12
12
|
information about the curvature of the model at the solution.
|
|
13
13
|
|
|
14
|
+
For handling arbitrary constraints, the call is
|
|
15
|
+
d, gnew, crvmin = ctrsbox(xopt, g, H, projections, delta)
|
|
16
|
+
which produces a new vector d approximately solving the constrained trust region subproblem:
|
|
17
|
+
min_{d} g'*d + 0.5*d'*H*d
|
|
18
|
+
s.t. ||d|| <= delta
|
|
19
|
+
xopt + d is feasible w.r.t. the constraint set C
|
|
20
|
+
The other outputs: gnew is the gradient of the model at d, and crvmin has
|
|
21
|
+
information about the curvature of the model at the solution.
|
|
22
|
+
|
|
14
23
|
We also provide a function for maximising the absolute value of a linear function
|
|
15
24
|
inside a similar trust region - this is useful for geometry steps.
|
|
16
25
|
The call
|
|
@@ -23,6 +32,13 @@ With this value, the variable d=x-xbase solves the problem
|
|
|
23
32
|
min_s abs(c + g' * d)
|
|
24
33
|
s.t. lower <= xbase + d <= upper
|
|
25
34
|
||d|| <= delta
|
|
35
|
+
Again, we have a version of this for handling arbitrary constraints
|
|
36
|
+
The call
|
|
37
|
+
x = ctrsbox_geometry(xbase, c, g, projections, Delta)
|
|
38
|
+
Solves
|
|
39
|
+
min_s abs(c + g' * d)
|
|
40
|
+
s.t. xbase + d is feasible w.r.t. the constraint set C
|
|
41
|
+
||d|| <= delta
|
|
26
42
|
|
|
27
43
|
Notes
|
|
28
44
|
----
|
|
@@ -63,13 +79,77 @@ except ImportError:
|
|
|
63
79
|
# Fall back to Python implementation
|
|
64
80
|
USE_FORTRAN = False
|
|
65
81
|
|
|
82
|
+
from .util import dykstra, pball, pbox, sumsq, model_value
|
|
66
83
|
|
|
67
|
-
|
|
84
|
+
__all__ = ['ctrsbox', 'ctrsbox_geometry', 'trsbox', 'trsbox_geometry']
|
|
68
85
|
|
|
86
|
+
ZERO_THRESH = 1e-14
|
|
69
87
|
|
|
70
|
-
|
|
88
|
+
def ctrsbox(xopt, g, H, projections, delta, d_max_iters=100, d_tol=1e-10, use_fortran=USE_FORTRAN):
|
|
89
|
+
n = xopt.size
|
|
90
|
+
assert xopt.shape == (n,), "xopt has wrong shape (should be vector)"
|
|
91
|
+
assert g.shape == (n,), "g and xopt have incompatible sizes"
|
|
92
|
+
assert len(H.shape) == 2, "H must be a matrix"
|
|
93
|
+
assert H.shape == (n,n), "H and xopt have incompatible sizes"
|
|
94
|
+
assert np.allclose(H, H.T), "H must be symmetric"
|
|
95
|
+
assert delta > 0.0, "delta must be strictly positive"
|
|
71
96
|
|
|
72
|
-
|
|
97
|
+
d = np.zeros((n,))
|
|
98
|
+
gnew = g.copy()
|
|
99
|
+
gy = g.copy()
|
|
100
|
+
crvmin = -1.0
|
|
101
|
+
y = d.copy()
|
|
102
|
+
eta = 1.2 # L backtrack scaling factor
|
|
103
|
+
t = 1
|
|
104
|
+
|
|
105
|
+
# Initial guess of L is norm(Hessian)
|
|
106
|
+
L = np.linalg.norm(H, 2)
|
|
107
|
+
|
|
108
|
+
# trust region is a ball of radius delta around xopt
|
|
109
|
+
trproj = lambda w: pball(w, xopt, delta)
|
|
110
|
+
|
|
111
|
+
# combine trust region constraints with user-entered constraints
|
|
112
|
+
P = list(projections) # make a copy of the projections list
|
|
113
|
+
P.append(trproj)
|
|
114
|
+
def proj(d0):
|
|
115
|
+
p = dykstra(P, xopt+d0, max_iter=d_max_iters, tol=d_tol)
|
|
116
|
+
# we want the step only, so we subtract xopt
|
|
117
|
+
# from the new point: proj(xk+d) - xk
|
|
118
|
+
return p - xopt
|
|
119
|
+
|
|
120
|
+
MAX_LOOP_ITERS = 100 * n ** 2
|
|
121
|
+
|
|
122
|
+
# projected GD loop
|
|
123
|
+
for ii in range(MAX_LOOP_ITERS):
|
|
124
|
+
w = y - (1/L)*gy
|
|
125
|
+
prev_d = d.copy()
|
|
126
|
+
d = proj(w)
|
|
127
|
+
|
|
128
|
+
# size of step taken
|
|
129
|
+
s = d - prev_d
|
|
130
|
+
stplen = np.linalg.norm(s)
|
|
131
|
+
|
|
132
|
+
# update true gradient
|
|
133
|
+
gnew += H.dot(s)
|
|
134
|
+
|
|
135
|
+
# update CRVMIN
|
|
136
|
+
crv = s.dot(H).dot(s)/sumsq(s) if sumsq(s) >= ZERO_THRESH else crvmin
|
|
137
|
+
crvmin = min(crvmin, crv) if crvmin != -1.0 else crv
|
|
138
|
+
|
|
139
|
+
# exit condition
|
|
140
|
+
if stplen <= ZERO_THRESH:
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
# momentum update
|
|
144
|
+
prev_t = t
|
|
145
|
+
t = (1 + np.sqrt(1 + 4 * t ** 2))/2
|
|
146
|
+
prev_y = y.copy()
|
|
147
|
+
y = d + s*(prev_t - 1)/t
|
|
148
|
+
|
|
149
|
+
# update gradient w.r.t y
|
|
150
|
+
gy += H.dot(y - prev_y)
|
|
151
|
+
|
|
152
|
+
return d, gnew, crvmin
|
|
73
153
|
|
|
74
154
|
|
|
75
155
|
def trsbox(xopt, g, H, sl, su, delta, use_fortran=USE_FORTRAN):
|
|
@@ -103,7 +183,7 @@ def trsbox(xopt, g, H, sl, su, delta, use_fortran=USE_FORTRAN):
|
|
|
103
183
|
iterc = 0
|
|
104
184
|
nact = 0 # number of fixed variables
|
|
105
185
|
|
|
106
|
-
xbdi = np.zeros((n,), dtype=
|
|
186
|
+
xbdi = np.zeros((n,), dtype=int) # fix x_i at bounds? [values -1, 0, 1]
|
|
107
187
|
xbdi[(xopt <= sl) & (g >= 0.0)] = -1
|
|
108
188
|
xbdi[(xopt >= su) & (g <= 0.0)] = 1
|
|
109
189
|
|
|
@@ -405,8 +485,63 @@ def ball_step(x0, g, Delta):
|
|
|
405
485
|
if sqrt(gsqnorm) < ZERO_THRESH: # Error catching: if g=0, make no step
|
|
406
486
|
return 0.0
|
|
407
487
|
else:
|
|
408
|
-
|
|
488
|
+
# Sqrt had negative input on prob 46 in OG DFOLS with noise
|
|
489
|
+
# print("Inside of the sqrt:", gdotx0**2 + gsqnorm*(Delta**2 - x0sqnorm))
|
|
490
|
+
# Got Inside of the sqrt: -3.608971127647144e-42
|
|
491
|
+
# Added max(0,...) here
|
|
492
|
+
return (sqrt(np.maximum(0,gdotx0**2 + gsqnorm*(Delta**2 - x0sqnorm))) - gdotx0) / gsqnorm
|
|
493
|
+
|
|
494
|
+
def ctrsbox_linear(xbase, g, projections, Delta, d_max_iters=100, d_tol=1e-10, use_fortran=USE_FORTRAN):
|
|
495
|
+
# Solve the convex program:
|
|
496
|
+
# min_d g' * d
|
|
497
|
+
# s.t. xbase + d is feasible w.r.t. constraint set C
|
|
498
|
+
# ||d||^2 <= Delta^2
|
|
499
|
+
|
|
500
|
+
n = g.size
|
|
501
|
+
d = np.zeros((n,))
|
|
502
|
+
y = d.copy()
|
|
503
|
+
t = 1
|
|
504
|
+
dirn = -g
|
|
505
|
+
cons_dirns = []
|
|
506
|
+
|
|
507
|
+
# If g[i] = 0, never step along this direction
|
|
508
|
+
constant_directions = np.where(np.abs(dirn) < ZERO_THRESH)[0]
|
|
509
|
+
dirn[constant_directions] = 0.0
|
|
510
|
+
|
|
511
|
+
# trust region is a ball of radius delta centered around xbase
|
|
512
|
+
trproj = lambda w: pball(w, xbase, Delta)
|
|
409
513
|
|
|
514
|
+
# combine trust region constraints with user-entered constraints
|
|
515
|
+
P = list(projections) # make a copy of the projections list
|
|
516
|
+
P.append(trproj)
|
|
517
|
+
def proj(d0):
|
|
518
|
+
p = dykstra(P, xbase + d0, max_iter=d_max_iters, tol=d_tol)
|
|
519
|
+
# we want the step only, so we subtract
|
|
520
|
+
# xbase from the new point: proj(xk + d) - xk
|
|
521
|
+
return p - xbase
|
|
522
|
+
|
|
523
|
+
MAX_LOOP_ITERS = 100 * n ** 2
|
|
524
|
+
|
|
525
|
+
# projected GD loop
|
|
526
|
+
for ii in range(MAX_LOOP_ITERS):
|
|
527
|
+
w = y + dirn
|
|
528
|
+
prev_d = d.copy()
|
|
529
|
+
d = proj(w)
|
|
530
|
+
|
|
531
|
+
s = d - prev_d
|
|
532
|
+
stplen = np.linalg.norm(s)
|
|
533
|
+
|
|
534
|
+
# exit condition
|
|
535
|
+
if stplen <= ZERO_THRESH:
|
|
536
|
+
break
|
|
537
|
+
|
|
538
|
+
# 'momentum' update
|
|
539
|
+
prev_t = t
|
|
540
|
+
t = (1 + np.sqrt(1 + 4 * t ** 2))/2
|
|
541
|
+
prev_y = y.copy()
|
|
542
|
+
y = d + s*(prev_t - 1)/t
|
|
543
|
+
|
|
544
|
+
return d
|
|
410
545
|
|
|
411
546
|
def trsbox_linear(g, a_in, b_in, Delta, use_fortran=USE_FORTRAN):
|
|
412
547
|
# Solve the convex program:
|
|
@@ -466,6 +601,22 @@ def trsbox_linear(g, a_in, b_in, Delta, use_fortran=USE_FORTRAN):
|
|
|
466
601
|
dirn[idx_hit] = 0.0 # no more searching this direction
|
|
467
602
|
return x
|
|
468
603
|
|
|
604
|
+
def ctrsbox_geometry(xbase, c, g, projections, Delta, d_max_iters=100, d_tol=1e-10, use_fortran=USE_FORTRAN):
|
|
605
|
+
# Given a Lagrange polynomial defined by: L(x) = c + g' * (x - xbase)
|
|
606
|
+
# Maximise |L(x)| in a box + trust region - that is, solve:
|
|
607
|
+
# max_x abs(c + g' * (x - xbase))
|
|
608
|
+
# s.t. x is feasible w.r.t constraint set C
|
|
609
|
+
# ||x-xbase|| <= Delta
|
|
610
|
+
# Setting s = x-xbase (or x = xbase + s), this is equivalent to:
|
|
611
|
+
# max_s abs(c + g' * s)
|
|
612
|
+
# s.t. xbase + s is is feasible w.r.t constraint set C
|
|
613
|
+
# ||s|| <= Delta
|
|
614
|
+
smin = ctrsbox_linear(xbase, g, projections, Delta, d_max_iters=100, d_tol=1e-10, use_fortran=use_fortran) # minimise g' * s
|
|
615
|
+
smax = ctrsbox_linear(xbase, -g, projections, Delta, d_max_iters=100, d_tol=1e-10, use_fortran=use_fortran) # maximise g' * s
|
|
616
|
+
if abs(c + np.dot(g, smin)) >= abs(c + np.dot(g, smax)): # choose the one with largest absolute value
|
|
617
|
+
return smin
|
|
618
|
+
else:
|
|
619
|
+
return smax
|
|
469
620
|
|
|
470
621
|
def trsbox_geometry(xbase, c, g, lower, upper, Delta, use_fortran=USE_FORTRAN):
|
|
471
622
|
# Given a Lagrange polynomial defined by: L(x) = c + g' * (x - xbase)
|