DFO-LS 1.5.0__tar.gz → 1.5.2__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: DFO-LS
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: A flexible derivative-free solver for (bound constrained) nonlinear least-squares minimization
5
5
  Author-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
6
6
  Maintainer-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: DFO-LS
3
- Version: 1.5.0
3
+ Version: 1.5.2
4
4
  Summary: A flexible derivative-free solver for (bound constrained) nonlinear least-squares minimization
5
5
  Author-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
6
6
  Maintainer-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
@@ -39,7 +39,7 @@ alternative licensing.
39
39
  from __future__ import absolute_import, division, print_function, unicode_literals
40
40
 
41
41
  # DFO-LS version
42
- __version__ = '1.5.0'
42
+ __version__ = '1.5.2'
43
43
 
44
44
  # Main solver & exit flags
45
45
  from .solver import *
@@ -240,12 +240,12 @@ class Controller(object):
240
240
  # Handle exit conditions (f < min obj value or maxfun reached)
241
241
  if exit_info is not None:
242
242
  if num_samples_run > 0:
243
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
243
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
244
244
  x_in_abs_coords=True)
245
245
  return exit_info # return & quit
246
246
 
247
247
  # Otherwise, add new results (increments model.npt_so_far)
248
- self.model.change_point(k+1, x - self.model.xbase, rvec_list[0, :]) # expect step, not absolute x
248
+ self.model.change_point(k+1, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
249
249
  for i in range(1, num_samples_run):
250
250
  self.model.add_new_sample(k+1, rvec_extra=rvec_list[i, :])
251
251
 
@@ -253,69 +253,101 @@ class Controller(object):
253
253
 
254
254
  at_lower_boundary = (self.model.sl > -0.01 * self.delta) # sl = xl - x0, should be -ve, actually < -rhobeg
255
255
  at_upper_boundary = (self.model.su < 0.01 * self.delta) # su = xu - x0, should be +ve, actually > rhobeg
256
-
257
- xpts_added = np.zeros((num_directions + 1, self.n()))
258
- for k in range(1, num_directions + 1):
259
- # k = 0 --> base point (xpt = 0) [ not here]
260
- # k = 1, ..., 2n --> coordinate directions [1,...,n and n+1,...,2n]
261
- # k = 2n+1, ..., (n+1)(n+2)/2 --> off-diagonal directions
262
- if 1 <= k < self.n() + 1: # first step along coord directions
256
+
257
+ if params("init.run_in_parallel") and num_directions <= self.n():
258
+ # Can do all the evaluation in parallel if <= n+1 interpolation points, but if larger
259
+ # then the step depends on the function value at previous steps and does point swapping
260
+ xpts_added = np.zeros((num_directions + 1, self.n()))
261
+ eval_obj_results = []
262
+ for k in range(1, num_directions + 1): # k = 1, ..., num_directions
263
+ # always have k = 1, ..., n since num_directions <= n
263
264
  dirn = k - 1 # direction to move in (0,...,n-1)
264
265
  stepa = self.delta if not at_upper_boundary[dirn] else -self.delta # take a +delta step if at lower, -delta if at upper
265
266
  stepb = None
266
267
  xpts_added[k, dirn] = stepa # set new (relative) point to the step since we haven't done any moving, so relative point is all zeros.
268
+
269
+ # Evaluate objective at this new point
270
+ x = self.model.as_absolute_coordinates(xpts_added[k, :])
271
+ eval_obj_results.append(self.evaluate_objective(x, number_of_samples, params))
272
+
273
+ # Evaluations done, now add to the model
274
+ for k in range(1, num_directions + 1):
275
+ x = self.model.as_absolute_coordinates(xpts_added[k, :])
276
+ rvec_list, obj_list, num_samples_run, exit_info = eval_obj_results[k-1]
277
+ # Handle exit conditions (f < min obj value or maxfun reached)
278
+ if exit_info is not None:
279
+ if num_samples_run > 0:
280
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
281
+ x_in_abs_coords=True)
282
+ return exit_info # return & quit
267
283
 
268
- elif self.n() + 1 <= k < 2 * self.n() + 1: # second step along coord directions
269
- dirn = k - self.n() - 1 # direction to move in (0,...,n-1)
270
- stepa = xpts_added[k - self.n(), dirn] # previous step
271
- stepb = -self.delta # new step
272
- if at_lower_boundary[dirn]:
273
- # if at lower boundary, set the second step to be +ve
274
- stepb = min(2.0 * self.delta, self.model.su[dirn]) # su = xu - x0, should be +ve
275
- if at_upper_boundary[dirn]:
276
- # if at upper boundary, set the second step to be -ve
277
- stepb = max(-2.0 * self.delta, self.model.sl[dirn]) # sl = xl - x0, should be -ve
278
- xpts_added[k, dirn] = stepb
279
-
280
- else: # k = 2n+1, ..., (n+1)(n+2)/2
281
- # p = (k - 1) % n + 1 # cycles through (1,...,n), starting at 2n+1 --> 1
282
- # l = (k - 2 * n - 1) / n + 1 # (1,...,1, 2, ..., 2, etc.) where each number appears n times
283
- # q = (p + l if p + l <= n else p + l - n)
284
- stepa = None
285
- stepb = None
286
- itemp = (k - self.n() - 1) // self.n()
287
- q = k - itemp * self.n() - self.n()
288
- p = q + itemp
289
- if p > self.n():
290
- p, q = q, p - self.n() # does swap correctly in Python
291
-
292
- xpts_added[k, p - 1] = xpts_added[p, p - 1]
293
- xpts_added[k, q - 1] = xpts_added[q, q - 1]
284
+ # Otherwise, add new results (increments model.npt_so_far)
285
+ self.model.change_point(k, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
286
+ for i in range(1, num_samples_run):
287
+ self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
288
+ else:
289
+ xpts_added = np.zeros((num_directions + 1, self.n()))
290
+ for k in range(1, num_directions + 1):
291
+ # k = 0 --> base point (xpt = 0) [ not here]
292
+ # k = 1, ..., 2n --> coordinate directions [1,...,n and n+1,...,2n]
293
+ # k = 2n+1, ..., (n+1)(n+2)/2 --> off-diagonal directions
294
+ if 1 <= k < self.n() + 1: # first step along coord directions
295
+ dirn = k - 1 # direction to move in (0,...,n-1)
296
+ stepa = self.delta if not at_upper_boundary[dirn] else -self.delta # take a +delta step if at lower, -delta if at upper
297
+ stepb = None
298
+ xpts_added[k, dirn] = stepa # set new (relative) point to the step since we haven't done any moving, so relative point is all zeros.
299
+
300
+ elif self.n() + 1 <= k < 2 * self.n() + 1: # second step along coord directions
301
+ dirn = k - self.n() - 1 # direction to move in (0,...,n-1)
302
+ stepa = xpts_added[k - self.n(), dirn] # previous step
303
+ stepb = -self.delta # new step
304
+ if at_lower_boundary[dirn]:
305
+ # if at lower boundary, set the second step to be +ve
306
+ stepb = min(2.0 * self.delta, self.model.su[dirn]) # su = xu - x0, should be +ve
307
+ if at_upper_boundary[dirn]:
308
+ # if at upper boundary, set the second step to be -ve
309
+ stepb = max(-2.0 * self.delta, self.model.sl[dirn]) # sl = xl - x0, should be -ve
310
+ xpts_added[k, dirn] = stepb
311
+
312
+ else: # k = 2n+1, ..., (n+1)(n+2)/2
313
+ # p = (k - 1) % n + 1 # cycles through (1,...,n), starting at 2n+1 --> 1
314
+ # l = (k - 2 * n - 1) / n + 1 # (1,...,1, 2, ..., 2, etc.) where each number appears n times
315
+ # q = (p + l if p + l <= n else p + l - n)
316
+ stepa = None
317
+ stepb = None
318
+ itemp = (k - self.n() - 1) // self.n()
319
+ q = k - itemp * self.n() - self.n()
320
+ p = q + itemp
321
+ if p > self.n():
322
+ p, q = q, p - self.n() # does swap correctly in Python
323
+
324
+ xpts_added[k, p - 1] = xpts_added[p, p - 1]
325
+ xpts_added[k, q - 1] = xpts_added[q, q - 1]
294
326
 
295
- # Evaluate objective at this new point
296
- x = self.model.as_absolute_coordinates(xpts_added[k, :])
297
- rvec_list, obj_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
327
+ # Evaluate objective at this new point
328
+ x = self.model.as_absolute_coordinates(xpts_added[k, :])
329
+ rvec_list, obj_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
298
330
 
299
- # Handle exit conditions (f < min obj value or maxfun reached)
300
- if exit_info is not None:
301
- if num_samples_run > 0:
302
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
303
- x_in_abs_coords=True)
304
- return exit_info # return & quit
331
+ # Handle exit conditions (f < min obj value or maxfun reached)
332
+ if exit_info is not None:
333
+ if num_samples_run > 0:
334
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
335
+ x_in_abs_coords=True)
336
+ return exit_info # return & quit
305
337
 
306
- # Otherwise, add new results (increments model.npt_so_far)
307
- self.model.change_point(k, x - self.model.xbase, rvec_list[0, :]) # expect step, not absolute x
308
- for i in range(1, num_samples_run):
309
- self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
338
+ # Otherwise, add new results (increments model.npt_so_far)
339
+ self.model.change_point(k, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
340
+ for i in range(1, num_samples_run):
341
+ self.model.add_new_sample(k, rvec_extra=rvec_list[i, :])
310
342
 
311
- # If k exceeds N+1, then the positions of the k-th and (k-N)-th interpolation
312
- # points may be switched, in order that the function value at the first of them
313
- # contributes to the off-diagonal second derivative terms of the initial quadratic model.
314
- # Note: this works because the steps for (k) and (k-n) points were in the same coordinate direction
315
- if self.n() + 1 <= k < 2 * self.n() + 1:
316
- # Only swap if steps were in different directions AND new pt has lower objective
317
- if stepa * stepb < 0.0 and self.model.objval[k] < self.model.objval[k - self.n()]:
318
- xpts_added[[k, k-self.n()]] = xpts_added[[k-self.n(), k]]
343
+ # If k exceeds N+1, then the positions of the k-th and (k-N)-th interpolation
344
+ # points may be switched, in order that the function value at the first of them
345
+ # contributes to the off-diagonal second derivative terms of the initial quadratic model.
346
+ # Note: this works because the steps for (k) and (k-n) points were in the same coordinate direction
347
+ if self.n() + 1 <= k < 2 * self.n() + 1:
348
+ # Only swap if steps were in different directions AND new pt has lower objective
349
+ if stepa * stepb < 0.0 and self.model.objval[k] < self.model.objval[k - self.n()]:
350
+ xpts_added[[k, k-self.n()]] = xpts_added[[k-self.n(), k]]
319
351
 
320
352
  return None # return & continue
321
353
 
@@ -351,13 +383,13 @@ class Controller(object):
351
383
  # Handle exit conditions (f < min obj value or maxfun reached)
352
384
  if exit_info is not None:
353
385
  if num_samples_run > 0:
354
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
386
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
355
387
  x_in_abs_coords=True)
356
388
  return exit_info # return & quit
357
389
 
358
390
  # Otherwise, add new results (increments model.npt_so_far)
359
391
  self.model.change_point(1 + ndirns, x - self.model.xbase,
360
- rvec_list[0, :]) # expect step, not absolute x
392
+ rvec_list[0, :], self.nx) # expect step, not absolute x
361
393
  for i in range(1, num_samples_run):
362
394
  self.model.add_new_sample(1 + ndirns, rvec_extra=rvec_list[i, :])
363
395
  else:
@@ -371,12 +403,12 @@ class Controller(object):
371
403
  # Handle exit conditions (f < min obj value or maxfun reached)
372
404
  if exit_info is not None:
373
405
  if num_samples_run > 0:
374
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
406
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
375
407
  x_in_abs_coords=True)
376
408
  return exit_info # return & quit
377
409
 
378
410
  # Otherwise, add new results (increments model.npt_so_far)
379
- self.model.change_point(1 + ndirns, x - self.model.xbase, rvec_list[0, :]) # expect step, not absolute x
411
+ self.model.change_point(1 + ndirns, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
380
412
  for i in range(1, num_samples_run):
381
413
  self.model.add_new_sample(1 + ndirns, rvec_extra=rvec_list[i, :])
382
414
 
@@ -408,7 +440,7 @@ class Controller(object):
408
440
  # Handle exit conditions (f < min obj value or maxfun reached)
409
441
  if exit_info is not None:
410
442
  if num_samples_run > 0:
411
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
443
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
412
444
  x_in_abs_coords=True)
413
445
  return exit_info # return & quit
414
446
 
@@ -422,7 +454,7 @@ class Controller(object):
422
454
  return exit_info # return & quit
423
455
 
424
456
  # Otherwise, add new results
425
- self.model.change_point(kmin, xnew, rvec_list[0, :]) # expect step, not absolute x
457
+ self.model.change_point(kmin, xnew, rvec_list[0, :], self.nx) # expect step, not absolute x
426
458
  for i in range(1, num_samples_run):
427
459
  self.model.add_new_sample(kmin, rvec_extra=rvec_list[i, :])
428
460
 
@@ -548,12 +580,12 @@ class Controller(object):
548
580
  # Handle exit conditions (f < min obj value or maxfun reached)
549
581
  if exit_info is not None:
550
582
  if num_samples_run > 0:
551
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
583
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
552
584
  x_in_abs_coords=True)
553
585
  return exit_info # didn't fix geometry - return & quit
554
586
 
555
587
  # Otherwise, add new results
556
- self.model.change_point(knew, xnew, rvec_list[0, :]) # expect step, not absolute x
588
+ self.model.change_point(knew, xnew, rvec_list[0, :], self.nx) # expect step, not absolute x
557
589
  for i in range(1, num_samples_run):
558
590
  self.model.add_new_sample(knew, rvec_extra=rvec_list[i, :])
559
591
 
@@ -768,8 +800,8 @@ class Controller(object):
768
800
  if x_in_abs_coords_to_save is not None:
769
801
  assert rvec_to_save is not None, "Soft restart: specified x_to_save but not rvec_to_save"
770
802
  assert nsamples_to_save is not None, "Soft restart: specified x_to_save but not nsamples_to_save"
771
- self.model.save_point(x_in_abs_coords_to_save, rvec_to_save, nsamples_to_save, x_in_abs_coords=True)
772
- self.model.save_point(self.model.xopt(abs_coordinates=True), self.model.ropt(),
803
+ self.model.save_point(x_in_abs_coords_to_save, rvec_to_save, nsamples_to_save, self.nx, x_in_abs_coords=True)
804
+ self.model.save_point(self.model.xopt(abs_coordinates=True), self.model.ropt(), self.nx,
773
805
  self.model.nsamples[self.model.kopt], x_in_abs_coords=True)
774
806
 
775
807
  if self.do_logging:
@@ -820,12 +852,12 @@ class Controller(object):
820
852
  # Handle exit conditions (f < min obj value or maxfun reached)
821
853
  if exit_info is not None:
822
854
  if num_samples_run > 0:
823
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
855
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
824
856
  x_in_abs_coords=True)
825
857
  return exit_info # return & quit
826
858
 
827
859
  # Otherwise, add new results
828
- self.model.add_new_point(xnew, rvec_list[0, :]) # expect step, not absolute x
860
+ self.model.add_new_point(xnew, rvec_list[0, :], self.nx) # expect step, not absolute x
829
861
  for i in range(1, num_samples_run):
830
862
  self.model.add_new_sample(self.model.npt() - 1, rvec_extra=rvec_list[i, :])
831
863
 
@@ -900,12 +932,12 @@ class Controller(object):
900
932
  # Handle exit conditions (f < min obj value or maxfun reached)
901
933
  if exit_info is not None:
902
934
  if num_samples_run > 0:
903
- self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
935
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, self.nx,
904
936
  x_in_abs_coords=True)
905
937
  return exit_info # return & quit
906
938
 
907
939
  # Otherwise, add new results
908
- self.model.change_point(knew, xnew, rvec_list[0, :]) # expect step, not absolute x
940
+ self.model.change_point(knew, xnew, rvec_list[0, :], self.nx) # expect step, not absolute x
909
941
  for i in range(1, num_samples_run):
910
942
  self.model.add_new_sample(knew, rvec_extra=rvec_list[i, :])
911
943
  return None
@@ -102,7 +102,7 @@ class DiagnosticInfo(object):
102
102
  self.data["rho"].append(control.rho)
103
103
  # And from a model?
104
104
  self.data["npt"].append(control.model.npt())
105
- x, rvec, f, jac, nsamples = control.model.get_final_results()
105
+ x, rvec, f, jac, nsamples, eval_num, jac_eval_nums = control.model.get_final_results()
106
106
  self.data["xk"].append(remove_scaling(x, control.scaling_changes))
107
107
  self.data["rk"].append(rvec)
108
108
  self.data["fk"].append(f)
@@ -85,6 +85,8 @@ class Model(object):
85
85
  self.nsamples = np.zeros((npt,), dtype=int) # number of samples used to evaluate objective at each point
86
86
  self.nsamples[0] = r0_nsamples
87
87
  self.objbeg = self.objval[0] # f(x0), saved to check for sufficient reduction
88
+ self.eval_num = np.zeros((npt,), dtype=int) # which evaluation number (1-indexed, nx not nf) is currently stored self.points[k,:]
89
+ self.eval_num[0] = 1
88
90
 
89
91
  # Termination criteria
90
92
  self.abs_tol = abs_tol
@@ -93,6 +95,7 @@ class Model(object):
93
95
  # Model information
94
96
  self.model_const = np.zeros((m, )) # constant term for model m(s) = c + J*s
95
97
  self.model_jac = np.zeros((m, n)) # Jacobian term for model m(s) = c + J*s
98
+ self.model_jac_eval_nums = None # which evaluation numbers (1-indexed, nx not nf) were used to build model_jac
96
99
 
97
100
  # Saved point (in absolute coordinates) - always check this value before quitting solver
98
101
  self.xsave = None
@@ -100,6 +103,8 @@ class Model(object):
100
103
  self.objsave = None
101
104
  self.jacsave = None
102
105
  self.nsamples_save = None
106
+ self.eval_num_save = None
107
+ self.jacsave_eval_nums = None
103
108
 
104
109
  # Factorisation of interpolation matrix
105
110
  self.factorisation_current = False
@@ -174,7 +179,7 @@ class Model(object):
174
179
  sq_distances[k] = sumsq(self.points[k, :] - xopt)
175
180
  return sq_distances
176
181
 
177
- def change_point(self, k, x, rvec, allow_kopt_update=True):
182
+ def change_point(self, k, x, rvec, eval_num, allow_kopt_update=True):
178
183
  # Update point k to x (w.r.t. xbase), with residual values fvec
179
184
  if k >= self.npt_so_far and self.npt_so_far < self.num_pts:
180
185
  assert k == self.npt_so_far, "Growing: updating wrong point"
@@ -188,6 +193,7 @@ class Model(object):
188
193
  if self.h is not None:
189
194
  self.objval[k] += self.h(remove_scaling(self.xbase + x, self.scaling_changes), *self.argsh)
190
195
  self.nsamples[k] = 1
196
+ self.eval_num[k] = eval_num
191
197
  self.factorisation_current = False
192
198
 
193
199
  if allow_kopt_update and self.objval[k] < self.objopt():
@@ -198,6 +204,7 @@ class Model(object):
198
204
  self.points[[k1, k2], :] = self.points[[k2, k1], :]
199
205
  self.fval_v[[k1, k2], :] = self.fval_v[[k2, k1], :]
200
206
  self.objval[[k1, k2]] = self.objval[[k2, k1]]
207
+ self.eval_num[[k1, k2]] = self.eval_num[[k2, k1]]
201
208
  if self.kopt == k1:
202
209
  self.kopt = k2
203
210
  elif self.kopt == k2:
@@ -219,7 +226,7 @@ class Model(object):
219
226
  self.kopt = np.argmin(self.objval[:self.npt()]) # make sure kopt is always the best value we have
220
227
  return
221
228
 
222
- def add_new_point(self, x, rvec):
229
+ def add_new_point(self, x, rvec, eval_num):
223
230
  self.points = np.append(self.points, x.reshape((1, self.n())), axis=0) # append row to xpt
224
231
  self.fval_v = np.append(self.fval_v, rvec.reshape((1, self.m())), axis=0) # append row to fval_v
225
232
  obj = sumsq(rvec)
@@ -227,6 +234,7 @@ class Model(object):
227
234
  obj += self.h(remove_scaling(self.xbase + x, self.scaling_changes), *self.argsh)
228
235
  self.objval = np.append(self.objval, obj) # append entry to fval
229
236
  self.nsamples = np.append(self.nsamples, 1) # add new sample number
237
+ self.eval_num = np.append(self.eval_num, eval_num) # add new evaluation number
230
238
  self.num_pts += 1 # make sure npt is updated
231
239
  self.npt_so_far += 1
232
240
 
@@ -249,7 +257,7 @@ class Model(object):
249
257
  self.model_const += np.dot(self.model_jac, xbase_shift)
250
258
  return
251
259
 
252
- def save_point(self, x, rvec, nsamples, x_in_abs_coords=True):
260
+ def save_point(self, x, rvec, nsamples, eval_num, x_in_abs_coords=True):
253
261
  xabs = x.copy() if x_in_abs_coords else self.as_absolute_coordinates(x)
254
262
  obj = sumsq(rvec)
255
263
  if self.h is not None:
@@ -258,8 +266,10 @@ class Model(object):
258
266
  self.xsave = xabs
259
267
  self.rsave = rvec.copy()
260
268
  self.objsave = obj
261
- self.jacsave = self.model_jac.copy()
269
+ self.jacsave = self.model_jac.copy() if self.model_jac is not None else None
262
270
  self.nsamples_save = nsamples
271
+ self.eval_num_save = eval_num
272
+ self.jacsave_eval_nums = self.model_jac_eval_nums.copy() if self.model_jac_eval_nums is not None else None
263
273
  return True
264
274
  else:
265
275
  return False # this value is worse than what we have already - didn't save
@@ -267,9 +277,9 @@ class Model(object):
267
277
  def get_final_results(self):
268
278
  # Return x and objval for optimal point (either from xsave+objsave or kopt)
269
279
  if self.objsave is None or self.objopt() <= self.objsave: # optimal has changed since xsave+objsave were last set
270
- return self.xopt(abs_coordinates=True).copy(), self.ropt().copy(), self.objopt(), self.model_jac.copy(), self.nsamples[self.kopt]
280
+ return self.xopt(abs_coordinates=True).copy(), self.ropt().copy(), self.objopt(), self.model_jac.copy(), self.nsamples[self.kopt], self.eval_num[self.kopt], self.model_jac_eval_nums
271
281
  else:
272
- return self.xsave.copy(), self.rsave.copy(), self.objsave, self.jacsave, self.nsamples_save
282
+ return self.xsave.copy(), self.rsave.copy(), self.objsave, self.jacsave, self.nsamples_save, self.eval_num_save, self.jacsave_eval_nums
273
283
 
274
284
  def min_objective_value(self):
275
285
  # Get termination criterion for f small: f <= abs_tol or f <= rel_tol * f0
@@ -367,6 +377,7 @@ class Model(object):
367
377
  J_old = self.model_jac.copy()
368
378
  self.model_jac = dg[1:,:].T
369
379
  self.model_const = dg[0,:] - np.dot(self.model_jac, xopt) # shift base to xbase
380
+ self.model_jac_eval_nums = self.eval_num.copy()
370
381
  if verbose or get_chg_J:
371
382
  norm_J_error = np.linalg.norm(self.model_jac - J_old, ord='fro')**2
372
383
  linalg_resid = np.linalg.norm(W.dot(dg) - rhs)**2
@@ -48,7 +48,7 @@ module_logger = logging.getLogger(__name__)
48
48
 
49
49
  # A container for the results of the optimization routine
50
50
  class OptimResults(object):
51
- def __init__(self, xmin, rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg):
51
+ def __init__(self, xmin, rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg, xmin_eval_num, jacmin_eval_nums):
52
52
  self.x = xmin
53
53
  self.resid = rmin
54
54
  self.obj = objmin
@@ -59,6 +59,8 @@ class OptimResults(object):
59
59
  self.flag = exit_flag
60
60
  self.msg = exit_msg
61
61
  self.diagnostic_info = None
62
+ self.xmin_eval_num = xmin_eval_num
63
+ self.jacmin_eval_nums = jacmin_eval_nums
62
64
  # Set standard names for exit flags
63
65
  self.EXIT_SLOW_WARNING = EXIT_SLOW_WARNING
64
66
  self.EXIT_MAXFUN_WARNING = EXIT_MAXFUN_WARNING
@@ -89,6 +91,9 @@ class OptimResults(object):
89
91
  output += "Not showing approximate Jacobian because it is too long; check self.jacobian\n"
90
92
  if self.diagnostic_info is not None:
91
93
  output += "Diagnostic information available; check self.diagnostic_info\n"
94
+ output += "Solution xmin was evaluation point %g\n" % self.xmin_eval_num
95
+ if len(self.jacmin_eval_nums) < 100:
96
+ output += "Approximate Jacobian formed using evaluation points %s\n" % str(self.jacmin_eval_nums)
92
97
  output += "Exit flag = %g\n" % self.flag
93
98
  output += "%s\n" % self.msg
94
99
  output += "****************************\n"
@@ -182,8 +187,8 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
182
187
  module_logger.info("Initialising (coordinate directions)")
183
188
  exit_info = control.initialise_coordinate_directions(number_of_samples, num_directions, params)
184
189
  if exit_info is not None:
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
190
+ x, rvec, obj, jacmin, nsamples, x_eval_num, jac_eval_nums = control.model.get_final_results()
191
+ return x, rvec, obj, None, nsamples, control.nf, control.nx, nruns_so_far + 1, exit_info, diagnostic_info, x_eval_num, jac_eval_nums
187
192
 
188
193
  finished_growing = (control.model.npt() >= control.model.num_pts) # have we finished growing the initial set yet?
189
194
 
@@ -464,7 +469,7 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
464
469
 
465
470
  if num_samples_run > 0:
466
471
  control.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0),
467
- num_samples_run, x_in_abs_coords=True)
472
+ num_samples_run, control.nx, x_in_abs_coords=True)
468
473
 
469
474
  if exit_info is not None:
470
475
  nruns_so_far += 1
@@ -549,7 +554,7 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
549
554
  break # quit
550
555
  if exit_info is not None:
551
556
  if num_samples_run > 0:
552
- control.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
557
+ control.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run, control.nx,
553
558
  x_in_abs_coords=True)
554
559
  nruns_so_far += 1
555
560
  break # quit
@@ -638,7 +643,7 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
638
643
 
639
644
  if do_logging:
640
645
  module_logger.debug("Updating with knew = %i" % knew)
641
- control.model.change_point(knew, xnew, rvec_list[0, :]) # expect step, not absolute x
646
+ control.model.change_point(knew, xnew, rvec_list[0, :], control.nx) # expect step, not absolute x
642
647
  for i in range(1, num_samples_run):
643
648
  control.model.add_new_sample(knew, rvec_extra=rvec_list[i, :])
644
649
 
@@ -877,11 +882,11 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
877
882
  # (end main loop)
878
883
 
879
884
  # Quit & return the important information
880
- x, rvec, obj, jacmin, nsamples = control.model.get_final_results()
885
+ x, rvec, obj, jacmin, nsamples, x_eval_num, jac_eval_nums = control.model.get_final_results()
881
886
  if do_logging:
882
887
  module_logger.debug("At return from DFO-LS, number of function evals = %i" % nf)
883
888
  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
889
+ return x, rvec, obj, jacmin, nsamples, control.nf, control.nx, nruns_so_far, exit_info, diagnostic_info, x_eval_num, jac_eval_nums
885
890
 
886
891
 
887
892
  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,
@@ -1056,7 +1061,7 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1056
1061
  nruns = 0
1057
1062
  nf = 0
1058
1063
  nx = 0
1059
- xmin, rmin, objmin, jacmin, nsamples_min, nf, nx, nruns, exit_info, diagnostic_info = \
1064
+ xmin, rmin, objmin, jacmin, nsamples_min, nf, nx, nruns, exit_info, diagnostic_info, xmin_eval_num, jacmin_eval_nums = \
1060
1065
  solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
1061
1066
  diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, default_growing_method_set_by_user=default_growing_method_set_by_user,
1062
1067
  do_logging=do_logging, print_progress=print_progress)
@@ -1075,12 +1080,12 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1075
1080
  module_logger.info("Restarting from finish point (f = %g) after %g function evals; using rhobeg = %g and rhoend = %g"
1076
1081
  % (objmin, nf, rhobeg, rhoend))
1077
1082
  if params("restarts.hard.use_old_rk"):
1078
- xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
1083
+ xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info, xmin_eval_num2, jacmin_eval_nums2 = \
1079
1084
  solve_main(objfun, xmin, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
1080
1085
  diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, r0_avg_old=rmin, r0_nsamples_old=nsamples_min,
1081
1086
  do_logging=do_logging, print_progress=print_progress)
1082
1087
  else:
1083
- xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info = \
1088
+ xmin2, rmin2, objmin2, jacmin2, nsamples2, nf, nx, nruns, exit_info, diagnostic_info, xmin_eval_num2, jacmin_eval_nums2 = \
1084
1089
  solve_main(objfun, xmin, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns, nf, nx, nsamples, params,
1085
1090
  diagnostic_info, scaling_changes, h, lh, argsh, prox_uh, argsprox, do_logging=do_logging, print_progress=print_progress)
1086
1091
 
@@ -1088,9 +1093,10 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1088
1093
  if do_logging:
1089
1094
  module_logger.info("Successful run with new f = %s compared to old f = %s" % (objmin2, objmin))
1090
1095
  last_successful_run = nruns
1091
- (xmin, rmin, objmin, nsamples_min) = (xmin2, rmin2, objmin2, nsamples2)
1096
+ (xmin, rmin, objmin, nsamples_min, xmin_eval_num) = (xmin2, rmin2, objmin2, nsamples2, xmin_eval_num2)
1092
1097
  if jacmin2 is not None: # may be None if finished during setup phase, in which case just use old Jacobian
1093
1098
  jacmin = jacmin2
1099
+ jacmin_eval_nums = jacmin_eval_nums2
1094
1100
  else:
1095
1101
  if do_logging:
1096
1102
  module_logger.info("Unsuccessful run with new f = %s compared to old f = %s" % (objmin2, objmin))
@@ -1105,7 +1111,7 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1105
1111
  if scaling_changes is not None and jacmin is not None:
1106
1112
  for i in range(n):
1107
1113
  jacmin[:, i] = jacmin[:, i] / scaling_changes[1][i]
1108
- results = OptimResults(remove_scaling(xmin, scaling_changes), rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg)
1114
+ results = OptimResults(remove_scaling(xmin, scaling_changes), rmin, objmin, jacmin, nf, nx, nruns, exit_flag, exit_msg, xmin_eval_num, jacmin_eval_nums)
1109
1115
  if params("logging.save_diagnostic_info"):
1110
1116
  df = diagnostic_info.to_dataframe(with_xk=params("logging.save_xk"), with_rk=params("logging.save_rk"))
1111
1117
  results.diagnostic_info = df
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes