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.

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, fmin, jacmin, nf, nx, nruns, exit_flag, exit_msg):
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.f = fmin
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.f
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, args, xl, xu, npt, rhobeg, rhoend, maxfun, nruns_so_far, nf_so_far, nx_so_far, nsamples, params,
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, f0 = eval_least_squares_objective(objfun, remove_scaling(x0, scaling_changes),
107
- args=args, eval_num=nf, pt_num=nx,
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
- f_list = np.zeros((number_of_samples,))
116
+ obj_list = np.zeros((number_of_samples,))
116
117
  rvec_list[0, :] = r0
117
- f_list[0] = f0
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, :], f_list[i] = eval_least_squares_objective(objfun, remove_scaling(x0, scaling_changes), args=args, eval_num=nf, pt_num=nx,
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
- if sumsq(r0_avg) <= params("model.abs_tol"):
137
- exit_info = ExitInformation(EXIT_SUCCESS, "Objective is sufficiently small")
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
- logging.debug("Inverse problem (m<n), switching default growing method")
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, args, x0, r0_avg, num_samples_run, xl, xu, npt, rhobeg, rhoend, nf, nx, maxfun,
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
- logging.info("Initialising (random directions)")
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
- logging.info("Initialising (coordinate directions)")
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, f, jacmin, nsamples = control.model.get_final_results()
180
- return x, rvec, f, None, nsamples, control.nf, control.nx, nruns_so_far + 1, exit_info, diagnostic_info
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
- logging.info("Beginning main loop")
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
- logging.debug("*** Iter %g (delta = %g, rho = %g) ***" % (current_iter, control.delta, control.rho))
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
- logging.info("Finished growing init set")
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
- logging.debug("Main loop: still growing (have %g of %g pts)" % (control.model.npt(), control.model.num_pts))
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
- # Trust region step
273
- d, gopt, H, gnew, crvmin = control.trust_region_step()
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
- logging.debug("Trust region step is d = " + str(d))
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.fopt(), np.linalg.norm(gopt), control.delta, control.rho, control.nf))
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
- logging.debug("Safety step during growing phase")
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
- logging.debug("Safety step (main phase)")
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
- logging.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
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
- logging.debug("Best so far: f = %.15g at x = " % (control.model.fopt())
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
- logging.debug("Best so far: f = %.15g at x = [...]" % (control.model.fopt()))
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, f_list, num_samples_run, exit_info = control.evaluate_objective(x, number_of_samples,
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
- logging.debug("Standard trust region step")
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
- logging.debug("Perturbing trust region with step = %s" % str(dirn))
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
- logging.debug("New trust region step = %s" % str(d))
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, f_list, num_samples_run, exit_info = control.evaluate_objective(x, number_of_samples, params)
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
- logging.debug("Ratio = %g" % ratio)
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
- logging.debug("Updating with knew = %i" % knew)
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
- logging.info("Slow iteration - terminating/restarting")
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.fsave is not None and control.model.fopt() > control.model.fsave
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
- logging.debug("Still growing: adding %g new directions" % params("growing.num_new_dirns_each_iter"))
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
- logging.info("Regression: moving %g points" % num_regression_steps)
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
- logging.debug("Iter %g: (slope, intercept, r_value) = (%g, %g, %g)" % (current_iter, slope, intercept, r_value))
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
- logging.info("Auto detection: need to do a restart")
744
- logging.debug("delta history = %s" % str(restart_auto_detect_delta))
745
- logging.debug("chgJ history = %s" % str(restart_auto_detect_chgJ))
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
- logging.info("Auto detection: need to do a restart")
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
- logging.debug("Checking and possibly improving geometry (unsuccessful step)")
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
- logging.info("New rho = %g after %i function evaluations" % (control.rho, control.nf))
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
- logging.debug("Best so far: f = %.15g at x = " % (control.model.fopt())
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
- logging.debug("Best so far: f = %.15g at x = [...]" % (control.model.fopt()))
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, f, jacmin, nsamples = control.model.get_final_results()
880
+ x, rvec, obj, jacmin, nsamples = control.model.get_final_results()
847
881
  if do_logging:
848
- logging.debug("At return from DFO-LS, number of function evals = %i" % nf)
849
- logging.debug("Smallest objective value = %.15g at x = " % f + str(x))
850
- return x, rvec, f, jacmin, nsamples, control.nf, control.nx, nruns_so_far, exit_info, diagnostic_info
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, args=(), bounds=None, npt=None, rhobeg=None, rhoend=1e-8, maxfun=None, nsamples=None, user_params=None,
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(np.float)
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(np.float) if bounds[0] is not None else None
865
- xu = bounds[1].astype(np.float) if bounds[1] is not None else None
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 < 0.0:
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 < 0.0:
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 <= xl)
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 >= xu)
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, 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,
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
- logging.info("Restarting from finish point (f = %g) after %g function evals; using rhobeg = %g and rhoend = %g"
1011
- % (fmin, nf, rhobeg, rhoend))
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, 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,
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, 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,
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 fmin2 < fmin or np.isnan(fmin):
1087
+ if objmin2 < objmin or np.isnan(objmin):
1023
1088
  if do_logging:
1024
- logging.info("Successful run with new f = %s compared to old f = %s" % (fmin2, fmin))
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, fmin, nsamples_min) = (xmin2, rmin2, fmin2, nsamples2)
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
- logging.info("Unsuccessful run with new f = %s compared to old f = %s" % (fmin2, fmin))
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, fmin, jacmin, nf, nx, nruns, exit_flag, exit_msg)
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
- logging.info("Did a total of %g run(s)" % nruns)
1114
+ module_logger.info("Did a total of %g run(s)" % nruns)
1050
1115
 
1051
1116
  return results
1052
1117