DFO-LS 1.5.4__py3-none-any.whl → 1.6__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.

@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: DFO-LS
3
- Version: 1.5.4
3
+ Version: 1.6
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>
@@ -41,6 +41,7 @@ Requires-Dist: Sphinx; extra == "dev"
41
41
  Requires-Dist: sphinx-rtd-theme; extra == "dev"
42
42
  Provides-Extra: trustregion
43
43
  Requires-Dist: trustregion>=1.1; extra == "trustregion"
44
+ Dynamic: license-file
44
45
 
45
46
  ===================================================
46
47
  DFO-LS: Derivative-Free Optimizer for Least-Squares
@@ -0,0 +1,15 @@
1
+ dfo_ls-1.6.dist-info/licenses/LICENSE.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
2
+ dfols/__init__.py,sha256=Qmcjy68aqTr5qqgbslJ2l1OdSEl7kpoDA9F4kAp4QFQ,1689
3
+ dfols/controller.py,sha256=LHk8ES0JjsHeAixLxDxv_t08tLSRchgopagH8Trsn1c,55525
4
+ dfols/diagnostic_info.py,sha256=kEcFCjD2rk39XRa90ocEaQvJWc0wj_ZPpQkOulVIM-k,6106
5
+ dfols/evaluations_database.py,sha256=t9H8VA1sRClkh6y7EeJAyoKJxo6mW4Y2KUrat7NXSKQ,10245
6
+ dfols/hessian.py,sha256=sExx4J4KoGwHItbthX2odosB2ONbQFvLdlcod7PIh4k,4262
7
+ dfols/model.py,sha256=1Npj3fJvMv66bKu_RIzLLI-2tyzPWOsKuyv-YUjcv2c,20711
8
+ dfols/params.py,sha256=VGDvfDWxqhPEUWpNm4TtehzA5sw13m1hLs44WzK_5k0,18556
9
+ dfols/solver.py,sha256=gzH5SCrI1xHNzt40gcMnvIzWXm2aAOlIV0ZMHf1bItU,69314
10
+ dfols/trust_region.py,sha256=JbHLBDw7H88a3cIMuialh7kpMNGjL3Lp9JsjrBNpDWQ,28231
11
+ dfols/util.py,sha256=XYb42bc5X9nJtFT27sx6_tD_EcBbqOnCCjKy-1wLJxY,10725
12
+ dfo_ls-1.6.dist-info/METADATA,sha256=3ED5Qf0wCq95qtPXSrcidDtuavyVbw4aDzRjF5x27Yk,8083
13
+ dfo_ls-1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ dfo_ls-1.6.dist-info/top_level.txt,sha256=UfxRhaDN8HQx2_l17KbrDrERJ90OCN7VKkDMpYYbRLU,6
15
+ dfo_ls-1.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
dfols/__init__.py CHANGED
@@ -39,9 +39,11 @@ 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.4'
42
+ __version__ = '1.6'
43
43
 
44
44
  # Main solver & exit flags
45
45
  from .solver import *
46
46
  __all__ = ['solve', 'OptimResults']
47
47
 
48
+ from .evaluations_database import *
49
+ __all__ += ['EvaluationDatabase']
dfols/controller.py CHANGED
@@ -414,6 +414,48 @@ class Controller(object):
414
414
 
415
415
  return None
416
416
 
417
+ def initialise_from_database(self, eval_database, number_of_samples, params):
418
+ # Here, eval_database has at least one entry, and the base index has already been used
419
+ # to evaluate (x0,r0), which has already been added to self.model
420
+ # Now, find exactly n feasible perturbations (either from database or new evals) and add them to the model
421
+ base_idx, perturbation_idx, new_perturbations = eval_database.select_starting_evals(self.delta,
422
+ xl=self.model.xbase + self.model.sl,
423
+ xu=self.model.xbase + self.model.su,
424
+ projections=self.model.projections,
425
+ tol=params("database.new_direction_tol"),
426
+ dykstra_max_iters=params("dykstra.max_iters"),
427
+ dykstra_tol=params("dykstra.d_tol"))
428
+
429
+ # Add suitable pre-existing evaluations
430
+ for i, idx in enumerate(perturbation_idx):
431
+ module_logger.info("Adding pre-existing evaluation %g to initial model" % idx)
432
+ x, rx = eval_database.get_eval(idx)
433
+ self.model.change_point(i + 1, x - self.model.xbase, rx, -idx) # use eval_num = -idx
434
+
435
+ if new_perturbations is not None:
436
+ num_perturbations = new_perturbations.shape[0]
437
+ module_logger.debug("Adding %g new evaluations to initial model" % num_perturbations)
438
+ for i in range(num_perturbations):
439
+ new_point = (eval_database.get_x(base_idx) - self.model.xbase) + new_perturbations[i,:] # new_perturbations[i,:] has length <= self.delta
440
+
441
+ # Evaluate objective
442
+ x = self.model.as_absolute_coordinates(new_point)
443
+ rvec_list, obj_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
444
+
445
+ # Handle exit conditions (f < min obj value or maxfun reached)
446
+ if exit_info is not None:
447
+ if num_samples_run > 0:
448
+ self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
449
+ self.nx, x_in_abs_coords=True)
450
+ return exit_info # return & quit
451
+
452
+ # Otherwise, add new results (increments model.npt_so_far)
453
+ self.model.change_point(len(perturbation_idx) + 1 + i, x - self.model.xbase, rvec_list[0, :], self.nx) # expect step, not absolute x
454
+ for j in range(1, num_samples_run):
455
+ self.model.add_new_sample(len(perturbation_idx) + 1 + i, rvec_extra=rvec_list[j, :])
456
+
457
+ return None
458
+
417
459
  def add_new_direction_while_growing(self, number_of_samples, params, min_num_steps=0):
418
460
  num_steps = max(params('growing.num_new_dirns_each_iter'), min_num_steps)
419
461
  step_length = params('growing.delta_scale_new_dirns') * self.delta
@@ -0,0 +1,208 @@
1
+ """
2
+ Class to create/store database of existing evaluations, and routines to select
3
+ existing evaluations to build an initial linear model
4
+ """
5
+ import logging
6
+ import numpy as np
7
+
8
+ from .util import apply_scaling, dykstra
9
+ from .trust_region import ctrsbox_geometry, trsbox_geometry
10
+
11
+ __all__ = ['EvaluationDatabase']
12
+
13
+ module_logger = logging.getLogger(__name__)
14
+
15
+
16
+ # Class to store set of evaluations (x, rx)
17
+ class EvaluationDatabase(object):
18
+ def __init__(self, eval_list=None, starting_eval=None):
19
+ # eval_list is a list of tuples (x, rx)
20
+ self._evals = []
21
+ if eval_list is not None:
22
+ for e in eval_list:
23
+ self._evals.append(e)
24
+
25
+ # Which evaluation index should be the starting point of the optimization?
26
+ self.starting_eval = None
27
+ if starting_eval is not None and 0 <= starting_eval <= len(self._evals):
28
+ self.starting_eval = starting_eval
29
+
30
+ def __len__(self):
31
+ return len(self._evals)
32
+
33
+ def append(self, x, rx, make_starting_eval=False):
34
+ self._evals.append((x, rx))
35
+ if make_starting_eval:
36
+ self.starting_eval = len(self) - 1
37
+
38
+ def set_starting_eval(self, index):
39
+ if 0 <= index < len(self):
40
+ self.starting_eval = index
41
+ else:
42
+ raise IndexError("Invalid index %g given current set of %g evaluations" % (index, len(self)))
43
+
44
+ def get_starting_eval_idx(self):
45
+ if len(self) == 0:
46
+ raise RuntimeError("No evaluations available, no suitable starting evaluation ")
47
+ elif self.starting_eval is None:
48
+ module_logger.warning("Starting evaluation index not set, using most recently appended evaluation")
49
+ self.starting_eval = len(self) - 1
50
+
51
+ return self.starting_eval
52
+
53
+ def get_eval(self, index):
54
+ # Return (x, rx) for given index
55
+ if 0 <= index < len(self):
56
+ return self._evals[index][0], self._evals[index][1]
57
+ else:
58
+ raise IndexError("Invalid index %g given current set of %g evaluations" % (index, len(self)))
59
+
60
+ def get_x(self, index):
61
+ return self.get_eval(index)[0]
62
+
63
+ def get_rx(self, index):
64
+ return self.get_eval(index)[1]
65
+
66
+ def apply_scaling(self, scaling_changes):
67
+ # Adjust all input x values based on scaling
68
+ if scaling_changes is not None:
69
+ for i in range(len(self)):
70
+ x, rx = self._evals[i]
71
+ self._evals[i] = (apply_scaling(x, scaling_changes), rx)
72
+ return
73
+
74
+ def select_starting_evals(self, delta, xl=None, xu=None, projections=[], tol=1e-8,
75
+ dykstra_max_iters=100, dykstra_tol=1e-10):
76
+ # Given a database 'evals' with prescribed starting index, and initial trust-region radius delta > 0
77
+ # determine a subset of the database to use
78
+
79
+ # The bounds xl <= x <= xu and projection list are used to determine where to evaluate any new points
80
+ # (ensuring they are feasible)
81
+
82
+ if delta <= 0.0:
83
+ raise RuntimeError("delta must be strictly positive")
84
+ if len(self) == 0:
85
+ raise RuntimeError("Need at least one evaluation to select starting evaluations")
86
+
87
+ base_idx = self.get_starting_eval_idx()
88
+ xbase = self.get_x(self.get_starting_eval_idx())
89
+ n = len(xbase)
90
+ module_logger.debug("Selecting starting evaluations from existing database")
91
+ module_logger.debug("Have %g evaluations to choose from" % len(self))
92
+ module_logger.debug("Using base index %g" % base_idx)
93
+
94
+ # For linear interpolation, we will use the matrix
95
+ # M = [[1, 0], [0, L]] where L has rows (xi-xbase)/delta
96
+ # So, just build a large matrix Lfull with everything
97
+ n_perturbations = len(self) - 1
98
+ Lfull = np.zeros((n_perturbations, n))
99
+ row_idx = 0
100
+ for i in range(n_perturbations + 1):
101
+ if i == base_idx:
102
+ continue
103
+ Lfull[row_idx, :] = (self.get_x(i) - xbase) / delta # Lfull[i,:] = (xi-xbase) / delta
104
+ row_idx += 1
105
+
106
+ xdist = np.linalg.norm(Lfull, axis=1) # xdist[i] = ||Lfull[i,:]|| = ||xi-xbase|| / delta
107
+ # module_logger.debug("xdist =", xdist)
108
+
109
+ # We ideally want xdist ~ 1, so reweight these distances based on that (large xdist_reweighted --> xdist ~ 1 --> good)
110
+ xdist_reweighted = 1.0 / np.maximum(xdist, 1.0 / xdist)
111
+ # module_logger.debug("xdist_reweighted =", xdist_reweighted)
112
+
113
+ if n_perturbations == 0:
114
+ module_logger.debug("Only one evaluation available, just selecting that")
115
+ return base_idx, [], delta * np.eye(n)
116
+
117
+ # Now, find as many good perturbations as we can
118
+ # Good = not too far from xbase (relative to delta) and sufficiently linearly independent
119
+ # from other selected perturbations (i.e. Lfull[perturbation_idx,:] well-conditioned
120
+ # and len(perturbation_idx) <= n
121
+ perturbation_idx = [] # what point indices to use as perturbations
122
+
123
+ for iter in range(min(n_perturbations, n)):
124
+ # Add one more good perturbation, if available
125
+ # Note: can only add at most the number of available perturbations, or n perturbations, whichever is smaller
126
+ if iter == 0:
127
+ # First perturbation: every direction is equally good, so pick the point closest to the
128
+ # trust-region boundary
129
+ idx = int(np.argmax(xdist_reweighted))
130
+ module_logger.debug("Adding index %g with ||xi-xbase|| / delta = %g" % (idx if idx < base_idx else idx+1, xdist[idx]))
131
+ perturbation_idx.append(idx)
132
+ else:
133
+ Q, R = np.linalg.qr(Lfull[perturbation_idx, :].T, mode='reduced')
134
+ # module_logger.debug("Current perturbation_idx =", perturbation_idx)
135
+ L_rem = Lfull @ (np.eye(n) - Q @ Q.T) # part of (xi-xbase)/delta orthogonal to current perturbations
136
+ # rem_size = fraction of original length ||xi-xbase||/delta that is orthogonal to current perturbations
137
+ # all entries are in [0,1], and is zero for already selected perturbations
138
+ rem_size = np.linalg.norm(L_rem, axis=1) / xdist
139
+ rem_size[perturbation_idx] = 0 # ensure this holds exactly
140
+ # module_logger.debug("rem_size =", rem_size)
141
+ # module_logger.debug("rem_size * xdist_reweighted =", rem_size * xdist_reweighted)
142
+
143
+ # We want a point with large rem_size and xdist ~ 1 (i.e. xdist_reweighted large)
144
+ idx = int(np.argmax(rem_size * xdist_reweighted))
145
+ if rem_size[idx] * xdist_reweighted[idx] > tol:
146
+ # This ensures new perturbation is sufficiently linearly independent of existing perturbations
147
+ # (and also ensures idx hasn't already been chosen)
148
+ module_logger.debug("Adding index %g" % (idx if idx < base_idx else idx+1))
149
+ perturbation_idx.append(idx)
150
+ else:
151
+ module_logger.debug("No more linearly independent directions, quitting")
152
+ break
153
+
154
+ # Find new linearly independent directions
155
+ if len(perturbation_idx) < n:
156
+ module_logger.debug("Selecting %g new linearly independent directions" % (n - len(perturbation_idx)))
157
+ Q, _ = np.linalg.qr(Lfull[perturbation_idx, :].T, mode='complete')
158
+ new_perturbations = delta * Q[:, len(perturbation_idx):].T
159
+
160
+ # Make perturbations feasible w.r.t. xl <= x <= xu and projections
161
+ # Note: if len(projections) > 0, then the projection list *already* includes bounds
162
+ # Don't need to make pre-existing evaluations feasible, since we already have r(x) for these
163
+
164
+ # Start construction of interpolation matrix for later
165
+ L = np.zeros((n, n), dtype=float)
166
+ L[:len(perturbation_idx), :] = Lfull[perturbation_idx, :]
167
+ L[len(perturbation_idx):, :] = new_perturbations / delta
168
+
169
+ # Since we already have a full set of linearly independent directions,
170
+ # we do this by moving each infeasible perturbation to a geometry-improving location
171
+ for i in range(new_perturbations.shape[0]):
172
+ xnew = xbase + new_perturbations[i, :]
173
+ # Check feasibility
174
+ if len(projections) == 0:
175
+ # Bounds only
176
+ feasible = np.all(xnew >= xl) and np.all(xnew <= xu)
177
+ else:
178
+ # Projections
179
+ xnew_C = dykstra(projections, xnew, max_iter=dykstra_max_iters, tol=dykstra_tol)
180
+ feasible = np.linalg.norm(xnew - xnew_C) < dykstra_tol
181
+
182
+ if feasible:
183
+ # Skip feasible points, nothing to do
184
+ continue
185
+
186
+ # If infeasible, build Lagrange polynomial and move to geometry-improving location in B(xbase,delta)
187
+ # which will automatically be feasible
188
+ module_logger.debug("Moving default %g-th new perturbation to ensure feasibility" % i)
189
+ c = 0.0 # Lagrange polynomial centered at xbase
190
+ ei = np.zeros((n,), dtype=float)
191
+ ei[len(perturbation_idx) + i] = 1.0
192
+ g = np.linalg.solve(L, ei) / delta # divide by delta because L is scaled by 1/delta
193
+ if len(projections) == 0:
194
+ new_perturbations[i, :] = trsbox_geometry(xbase, c, g, xl, xu, delta)
195
+ else:
196
+ new_perturbations[i, :] = ctrsbox_geometry(xbase, c, g, projections, delta)
197
+
198
+ # Update L after replacement
199
+ L[len(perturbation_idx) + i, :] = new_perturbations[i,:] / delta
200
+ else:
201
+ module_logger.debug("Full set of directions found, no need for new evaluations")
202
+ new_perturbations = None
203
+
204
+ # perturbation_idx in [0, ..., n_perturbations-1], reset to be actual indices
205
+ for i in range(len(perturbation_idx)):
206
+ if perturbation_idx[i] >= base_idx:
207
+ perturbation_idx[i] += 1
208
+ return base_idx, perturbation_idx, new_perturbations
dfols/params.py CHANGED
@@ -122,6 +122,9 @@ class ParameterList(object):
122
122
  self.params["func_tol.tr_step"] = 1-1e-1
123
123
  self.params["func_tol.max_iters"] = 500
124
124
  self.params["sfista.max_iters_scaling"] = 2.0
125
+
126
+ # Evaluation database
127
+ self.params["database.new_direction_tol"] = 1e-8
125
128
 
126
129
  self.params_changed = {}
127
130
  for p in self.params:
@@ -284,6 +287,8 @@ class ParameterList(object):
284
287
  type_str, nonetype_ok, lower, upper = 'int', False, 0, None
285
288
  elif key == "sfista.max_iters_scaling":
286
289
  type_str, nonetype_ok, lower, upper = 'float', False, 1.0, None
290
+ elif key == "database.new_direction_tol":
291
+ type_str, nonetype_ok, lower, upper = 'float', False, 0.0, None
287
292
  else:
288
293
  assert False, "ParameterList.param_type() has unknown key: %s" % key
289
294
  return type_str, nonetype_ok, lower, upper
dfols/solver.py CHANGED
@@ -39,6 +39,7 @@ import warnings
39
39
 
40
40
  from .controller import *
41
41
  from .diagnostic_info import *
42
+ from .evaluations_database import *
42
43
  from .params import *
43
44
  from .util import *
44
45
 
@@ -70,13 +71,16 @@ class OptimResults(object):
70
71
  self.EXIT_TR_INCREASE_ERROR = EXIT_TR_INCREASE_ERROR
71
72
  self.EXIT_LINALG_ERROR = EXIT_LINALG_ERROR
72
73
  self.EXIT_FALSE_SUCCESS_WARNING = EXIT_FALSE_SUCCESS_WARNING
74
+ self.max_resid_length_print = 20 # don't print self.resid in __str__ if length >= this value
75
+ self.max_jac_length_print = 40 # don't print self.jacobian in __str__ if length >= this value
76
+
73
77
 
74
78
  def __str__(self):
75
79
  # Result of calling print(soln)
76
80
  output = "****** DFO-LS Results ******\n"
77
81
  if self.flag != self.EXIT_INPUT_ERROR:
78
82
  output += "Solution xmin = %s\n" % str(self.x)
79
- if len(self.resid) < 100:
83
+ if len(self.resid) < self.max_resid_length_print:
80
84
  output += "Residual vector = %s\n" % str(self.resid)
81
85
  else:
82
86
  output += "Not showing residual vector because it is too long; check self.resid\n"
@@ -84,7 +88,7 @@ class OptimResults(object):
84
88
  output += "Needed %g objective evaluations (at %g points)\n" % (self.nf, self.nx)
85
89
  if self.nruns > 1:
86
90
  output += "Did a total of %g runs\n" % self.nruns
87
- if self.jacobian is not None and np.size(self.jacobian) < 200:
91
+ if self.jacobian is not None and np.size(self.jacobian) < self.max_jac_length_print:
88
92
  output += "Approximate Jacobian = %s\n" % str(self.jacobian)
89
93
  elif self.jacobian is None:
90
94
  output += "No Jacobian returned\n"
@@ -93,7 +97,7 @@ class OptimResults(object):
93
97
  if self.diagnostic_info is not None:
94
98
  output += "Diagnostic information available; check self.diagnostic_info\n"
95
99
  output += "Solution xmin was evaluation point %g\n" % self.xmin_eval_num
96
- if self.jacmin_eval_nums is not None and len(self.jacmin_eval_nums) < 100:
100
+ if self.jacmin_eval_nums is not None and len(self.jacmin_eval_nums) < self.max_resid_length_print:
97
101
  output += "Approximate Jacobian formed using evaluation points %s\n" % str(self.jacmin_eval_nums)
98
102
  elif self.jacmin_eval_nums is None:
99
103
  output += "Approximate Jacobian not formed using problem information, disregard\n"
@@ -152,58 +156,77 @@ class OptimResults(object):
152
156
  def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxfun, nruns_so_far, nf_so_far, nx_so_far, nsamples, params,
153
157
  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,
154
158
  do_logging=True, print_progress=False):
159
+
160
+ if type(x0) == EvaluationDatabase:
161
+ x0_is_eval_database = True
162
+ x0_vec = x0.get_x(x0.get_starting_eval_idx())
163
+ else:
164
+ x0_vec = x0
165
+ x0_is_eval_database = False
166
+ n = len(x0_vec)
167
+
155
168
  # Evaluate at x0 (keep nf, nx correct and check for f < 1e-12)
156
169
  # The hard bit is determining what m = len(r0) should be, and allocating memory appropriately
157
170
  if r0_avg_old is None:
158
- number_of_samples = max(nsamples(rhobeg, rhobeg, 0, nruns_so_far), 1)
159
- # Evaluate the first time...
160
- nf = nf_so_far + 1
161
- nx = nx_so_far + 1
162
- r0, obj0 = eval_least_squares_with_regularisation(objfun, remove_scaling(x0, scaling_changes), h,
163
- argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
164
- full_x_thresh=params("logging.n_to_print_whole_x_vector"),
165
- check_for_overflow=params("general.check_objfun_for_overflow"))
166
- m = len(r0)
167
-
168
- # Now we have m, we can evaluate the rest of the times
169
- rvec_list = np.zeros((number_of_samples, m))
170
- obj_list = np.zeros((number_of_samples,))
171
- rvec_list[0, :] = r0
172
- obj_list[0] = obj0
173
- num_samples_run = 1
174
171
  exit_info = None
172
+ if x0_is_eval_database:
173
+ # We have already got r(x0), so just extract this information
174
+ nf = nf_so_far
175
+ nx = nx_so_far
176
+ num_samples_run = 1
177
+ r0_avg = x0.get_rx(x0.get_starting_eval_idx())
178
+ m = len(r0_avg)
179
+ module_logger.info("Using pre-existing evaluation %g as starting point" % (x0.get_starting_eval_idx()))
180
+ else:
181
+ number_of_samples = max(nsamples(rhobeg, rhobeg, 0, nruns_so_far), 1)
182
+ # Evaluate the first time...
183
+ nf = nf_so_far + 1
184
+ nx = nx_so_far + 1
185
+ r0, obj0 = eval_least_squares_with_regularisation(objfun, remove_scaling(x0_vec, scaling_changes), h,
186
+ argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
187
+ full_x_thresh=params("logging.n_to_print_whole_x_vector"),
188
+ check_for_overflow=params("general.check_objfun_for_overflow"))
189
+ m = len(r0)
190
+
191
+ # Now we have m, we can evaluate the rest of the times
192
+ rvec_list = np.zeros((number_of_samples, m))
193
+ obj_list = np.zeros((number_of_samples,))
194
+ rvec_list[0, :] = r0
195
+ obj_list[0] = obj0
196
+ num_samples_run = 1
197
+
198
+ for i in range(1, number_of_samples): # skip first eval - already did this
199
+ if nf >= maxfun:
200
+ exit_info = ExitInformation(EXIT_MAXFUN_WARNING, "Objective has been called MAXFUN times")
201
+ nruns_so_far += 1
202
+ break # stop evaluating at x0
175
203
 
176
- for i in range(1, number_of_samples): # skip first eval - already did this
177
- if nf >= maxfun:
178
- exit_info = ExitInformation(EXIT_MAXFUN_WARNING, "Objective has been called MAXFUN times")
179
- nruns_so_far += 1
180
- break # stop evaluating at x0
204
+ nf += 1
205
+ # Don't increment nx for x0 - we did this earlier
206
+ rvec_list[i, :], obj_list[i] = eval_least_squares_with_regularisation(objfun, remove_scaling(x0_vec, scaling_changes), h,
207
+ argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
208
+ full_x_thresh=params("logging.n_to_print_whole_x_vector"),
209
+ check_for_overflow=params("general.check_objfun_for_overflow"))
210
+ num_samples_run += 1
181
211
 
182
- nf += 1
183
- # Don't increment nx for x0 - we did this earlier
184
- rvec_list[i, :], obj_list[i] = eval_least_squares_with_regularisation(objfun, remove_scaling(x0, scaling_changes), h,
185
- argsf=argsf, argsh=argsh, verbose=do_logging, eval_num=nf, pt_num=nx,
186
- full_x_thresh=params("logging.n_to_print_whole_x_vector"),
187
- check_for_overflow=params("general.check_objfun_for_overflow"))
188
- num_samples_run += 1
212
+ r0_avg = np.mean(rvec_list[:num_samples_run, :], axis=0)
189
213
 
190
- r0_avg = np.mean(rvec_list[:num_samples_run, :], axis=0)
191
214
  # NOTE: modify objvalue here
192
215
  if h is None:
193
216
  if sumsq(r0_avg) <= params("model.abs_tol"):
194
217
  exit_info = ExitInformation(EXIT_SUCCESS, "Objective is sufficiently small")
195
218
  else:
196
- if sumsq(r0_avg) + h(remove_scaling(x0, scaling_changes), *argsh)<= params("model.abs_tol"):
219
+ if sumsq(r0_avg) + h(remove_scaling(x0_vec, scaling_changes), *argsh)<= params("model.abs_tol"):
197
220
  exit_info = ExitInformation(EXIT_SUCCESS, "Objective is sufficiently small")
198
221
 
199
222
  if exit_info is not None:
200
223
  xmin_eval_num = 0
201
224
  jacmin_eval_nums = np.array([0], dtype=int)
202
- return x0, r0_avg, sumsq(r0_avg), None, num_samples_run, nf, nx, nruns_so_far+1, exit_info, diagnostic_info, xmin_eval_num, jacmin_eval_nums
225
+ return x0_vec, r0_avg, sumsq(r0_avg), None, num_samples_run, nf, nx, nruns_so_far+1, exit_info, diagnostic_info, xmin_eval_num, jacmin_eval_nums
203
226
 
204
227
  else: # have old r0 information (e.g. from previous restart), use this instead
205
228
 
206
- # m = len(r0_avg_old)
229
+ m = len(r0_avg_old)
207
230
  r0_avg = r0_avg_old
208
231
  num_samples_run = r0_nsamples_old
209
232
  nf = nf_so_far
@@ -213,7 +236,7 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
213
236
  if default_growing_method_set_by_user is not None and (not default_growing_method_set_by_user):
214
237
  # If m>=n, the default growing method (use_full_rank_interp) is best
215
238
  # However, this can fail for m<n, so need to use an alternative method (perturb_trust_region_step)
216
- if m < len(x0):
239
+ if m < n:
217
240
  if do_logging:
218
241
  module_logger.debug("Inverse problem (m<n), switching default growing method")
219
242
  params('growing.full_rank.use_full_rank_interp', new_value=False)
@@ -222,25 +245,32 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
222
245
  params('growing.delta_scale_new_dirns', new_value=0.1)
223
246
 
224
247
  # Initialise controller
225
- control = Controller(objfun, argsf, x0, r0_avg, num_samples_run, xl, xu, projections, npt, rhobeg, rhoend, nf, nx, maxfun,
248
+ control = Controller(objfun, argsf, x0_vec, r0_avg, num_samples_run, xl, xu, projections, npt, rhobeg, rhoend, nf, nx, maxfun,
226
249
  params, scaling_changes, do_logging, h=h, lh=lh, argsh=argsh, prox_uh=prox_uh, argsprox=argsprox)
227
250
 
228
251
  # Initialise interpolation set
229
252
  number_of_samples = max(nsamples(control.delta, control.rho, 0, nruns_so_far), 1)
230
253
  num_directions = min(params("growing.ndirs_initial") + params("restarts.hard.increase_ndirs_initial_amt") * nruns_so_far,
231
254
  npt - 1) # cap at npt
232
- if params("init.random_initial_directions"):
233
- if do_logging:
234
- module_logger.info("Initialising (random directions)")
235
- exit_info = control.initialise_random_directions(number_of_samples, num_directions, params)
255
+ if x0_is_eval_database:
256
+ if num_directions != n:
257
+ module_logger.warning("When evaluation database provided, we will always initialize with n+1 evaluations")
258
+ exit_info = control.initialise_from_database(x0, number_of_samples, params)
236
259
  else:
237
- if do_logging:
238
- module_logger.info("Initialising (coordinate directions)")
239
- exit_info = control.initialise_coordinate_directions(number_of_samples, num_directions, params)
260
+ if params("init.random_initial_directions"):
261
+ if do_logging:
262
+ module_logger.info("Initialising (random directions)")
263
+ exit_info = control.initialise_random_directions(number_of_samples, num_directions, params)
264
+ else:
265
+ if do_logging:
266
+ module_logger.info("Initialising (coordinate directions)")
267
+ exit_info = control.initialise_coordinate_directions(number_of_samples, num_directions, params)
240
268
  if exit_info is not None:
241
269
  x, rvec, obj, jacmin, nsamples, x_eval_num, jac_eval_nums = control.model.get_final_results()
242
270
  return x, rvec, obj, None, nsamples, control.nf, control.nx, nruns_so_far + 1, exit_info, diagnostic_info, x_eval_num, jac_eval_nums
243
271
 
272
+ # model.npt() = actual number of evaluations available to the model so far
273
+ # model.num_pts = desired interp set size >= n+1
244
274
  finished_growing = (control.model.npt() >= control.model.num_pts) # have we finished growing the initial set yet?
245
275
 
246
276
  # Save list of last N successful steps: whether they failed to be an improvement over fsave
@@ -942,8 +972,16 @@ def solve_main(objfun, x0, argsf, xl, xu, projections, npt, rhobeg, rhoend, maxf
942
972
 
943
973
  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,
944
974
  objfun_has_noise=False, scaling_within_bounds=False, do_logging=True, print_progress=False):
945
- x0 = x0.astype(float)
946
- n = len(x0)
975
+
976
+ if type(x0) == EvaluationDatabase:
977
+ assert len(x0) > 0, "evaluation database x0 cannot be empty"
978
+ assert 0 <= x0.get_starting_eval_idx() < len(x0), "evaluation database must have valid starting index set"
979
+ x0_is_eval_database = True
980
+ n = len(x0.get_x(x0.get_starting_eval_idx()))
981
+ else:
982
+ x0 = np.array(x0).astype(float)
983
+ n = len(x0)
984
+ x0_is_eval_database = False
947
985
 
948
986
  # Set missing inputs (if not specified) to some sensible defaults
949
987
  if bounds is None:
@@ -969,7 +1007,8 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
969
1007
  if npt is None:
970
1008
  npt = n + 1
971
1009
  if rhobeg is None:
972
- rhobeg = 0.1 if scaling_within_bounds else 0.1 * max(np.max(np.abs(x0)), 1.0)
1010
+ x0_norm = np.max(np.abs(x0.get_x(x0.get_starting_eval_idx()))) if x0_is_eval_database else np.max(np.abs(x0))
1011
+ rhobeg = 0.1 if scaling_within_bounds else 0.1 * max(x0_norm, 1.0)
973
1012
  if maxfun is None:
974
1013
  maxfun = min(100 * (n + 1), 1000) # 100 gradients, capped at 1000
975
1014
  if nsamples is None:
@@ -1004,7 +1043,10 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1004
1043
  scale = xu - xl
1005
1044
  scaling_changes = (shift, scale)
1006
1045
 
1007
- x0 = apply_scaling(x0, scaling_changes)
1046
+ if x0_is_eval_database:
1047
+ x0.apply_scaling(scaling_changes)
1048
+ else:
1049
+ x0 = apply_scaling(x0, scaling_changes)
1008
1050
  xl = apply_scaling(xl, scaling_changes)
1009
1051
  xu = apply_scaling(xu, scaling_changes)
1010
1052
 
@@ -1033,13 +1075,19 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1033
1075
  if exit_info is None and maxfun <= 0:
1034
1076
  exit_info = ExitInformation(EXIT_INPUT_ERROR, "maxfun must be strictly positive")
1035
1077
 
1036
- if exit_info is None and np.shape(x0) != (n,):
1037
- exit_info = ExitInformation(EXIT_INPUT_ERROR, "x0 must be a vector")
1078
+ if exit_info is None:
1079
+ if x0_is_eval_database:
1080
+ for i in range(len(x0)):
1081
+ if np.shape(x0.get_x(i)) != (n,):
1082
+ exit_info = ExitInformation(EXIT_INPUT_ERROR, "All input vectors x0 must have the same shape")
1083
+ else:
1084
+ if np.shape(x0) != (n,):
1085
+ exit_info = ExitInformation(EXIT_INPUT_ERROR, "x0 must be a vector")
1038
1086
 
1039
- if exit_info is None and np.shape(x0) != np.shape(xl):
1087
+ if exit_info is None and np.shape(xl) != (n,):
1040
1088
  exit_info = ExitInformation(EXIT_INPUT_ERROR, "lower bounds must have same shape as x0")
1041
1089
 
1042
- if exit_info is None and np.shape(x0) != np.shape(xu):
1090
+ if exit_info is None and np.shape(xu) != (n,):
1043
1091
  exit_info = ExitInformation(EXIT_INPUT_ERROR, "upper bounds must have same shape as x0")
1044
1092
 
1045
1093
  if exit_info is None and np.min(xu - xl) < 2.0 * rhobeg:
@@ -1090,22 +1138,24 @@ def solve(objfun, x0, h=None, lh=None, prox_uh=None, argsf=(), argsh=(), argspro
1090
1138
  return results
1091
1139
 
1092
1140
  # Enforce arbitrary constraint bounds on x0
1093
- if projections:
1094
- xp = dykstra(projections,x0,max_iter=params("dykstra.max_iters"),tol=params("dykstra.d_tol"))
1095
- if not np.allclose(xp,x0):
1096
- warnings.warn("x0 not feasible w.r.t given constraints, adjusting", RuntimeWarning)
1097
- x0 = xp.copy()
1098
-
1099
- # Enforce lower & upper bounds on x0
1100
- idx = (x0 < xl)
1101
- if np.any(idx):
1102
- warnings.warn("x0 below lower bound, adjusting", RuntimeWarning)
1103
- x0[idx] = xl[idx]
1104
-
1105
- idx = (x0 > xu)
1106
- if np.any(idx):
1107
- warnings.warn("x0 above upper bound, adjusting", RuntimeWarning)
1108
- x0[idx] = xu[idx]
1141
+ if not x0_is_eval_database:
1142
+ # Don't need to enforce any constraints for pre-existing evaluations (since we already have the objective value)
1143
+ if projections:
1144
+ xp = dykstra(projections,x0,max_iter=params("dykstra.max_iters"),tol=params("dykstra.d_tol"))
1145
+ if not np.allclose(xp,x0):
1146
+ warnings.warn("x0 not feasible w.r.t given constraints, adjusting", RuntimeWarning)
1147
+ x0 = xp.copy()
1148
+
1149
+ # Enforce lower & upper bounds on x0
1150
+ idx = (x0 < xl)
1151
+ if np.any(idx):
1152
+ warnings.warn("x0 below lower bound, adjusting", RuntimeWarning)
1153
+ x0[idx] = xl[idx]
1154
+
1155
+ idx = (x0 > xu)
1156
+ if np.any(idx):
1157
+ warnings.warn("x0 above upper bound, adjusting", RuntimeWarning)
1158
+ x0[idx] = xu[idx]
1109
1159
 
1110
1160
  # Call main solver (first time)
1111
1161
  diagnostic_info = DiagnosticInfo()
@@ -1,14 +0,0 @@
1
- dfols/__init__.py,sha256=wOAEAlWyt7AEg1wLDbhf5cfdoL2cXGoR8BzF4_2rUMA,1621
2
- dfols/controller.py,sha256=Jffyao_z7wcQf1WEQtv2smnNew8HXGguWuUPLbgVuCc,52487
3
- dfols/diagnostic_info.py,sha256=kEcFCjD2rk39XRa90ocEaQvJWc0wj_ZPpQkOulVIM-k,6106
4
- dfols/hessian.py,sha256=sExx4J4KoGwHItbthX2odosB2ONbQFvLdlcod7PIh4k,4262
5
- dfols/model.py,sha256=1Npj3fJvMv66bKu_RIzLLI-2tyzPWOsKuyv-YUjcv2c,20711
6
- dfols/params.py,sha256=GzJGO0TByH1X3B0NbLOCOqmYG8dRiKPKjjX7or_fOqI,18342
7
- dfols/solver.py,sha256=psKOBi9F8PKdECyu7aS6fpzhN86DnYjFRvzX7XAFoPA,66788
8
- dfols/trust_region.py,sha256=JbHLBDw7H88a3cIMuialh7kpMNGjL3Lp9JsjrBNpDWQ,28231
9
- dfols/util.py,sha256=XYb42bc5X9nJtFT27sx6_tD_EcBbqOnCCjKy-1wLJxY,10725
10
- DFO_LS-1.5.4.dist-info/LICENSE.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
11
- DFO_LS-1.5.4.dist-info/METADATA,sha256=Nm2dZQMPC3fa0U5MR_TXFpt_MuucKuMGSpAlWeco81c,8063
12
- DFO_LS-1.5.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
13
- DFO_LS-1.5.4.dist-info/top_level.txt,sha256=UfxRhaDN8HQx2_l17KbrDrERJ90OCN7VKkDMpYYbRLU,6
14
- DFO_LS-1.5.4.dist-info/RECORD,,