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.
- {DFO_LS-1.5.4.dist-info → dfo_ls-1.6.dist-info}/METADATA +3 -2
- dfo_ls-1.6.dist-info/RECORD +15 -0
- {DFO_LS-1.5.4.dist-info → dfo_ls-1.6.dist-info}/WHEEL +1 -1
- dfols/__init__.py +3 -1
- dfols/controller.py +42 -0
- dfols/evaluations_database.py +208 -0
- dfols/params.py +5 -0
- dfols/solver.py +118 -68
- DFO_LS-1.5.4.dist-info/RECORD +0 -14
- {DFO_LS-1.5.4.dist-info → dfo_ls-1.6.dist-info/licenses}/LICENSE.txt +0 -0
- {DFO_LS-1.5.4.dist-info → dfo_ls-1.6.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: DFO-LS
|
|
3
|
-
Version: 1.
|
|
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,,
|
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.
|
|
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) <
|
|
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) <
|
|
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
|
+
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 <
|
|
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,
|
|
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
|
|
233
|
-
if
|
|
234
|
-
module_logger.
|
|
235
|
-
exit_info = control.
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
946
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1037
|
-
|
|
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(
|
|
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(
|
|
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
|
|
1094
|
-
|
|
1095
|
-
if
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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()
|
DFO_LS-1.5.4.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|