DFO-LS 1.2__py3-none-any.whl → 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of DFO-LS might be problematic. Click here for more details.
- {DFO_LS-1.2.dist-info → DFO_LS-1.4.1.dist-info}/METADATA +61 -33
- DFO_LS-1.4.1.dist-info/RECORD +14 -0
- {DFO_LS-1.2.dist-info → DFO_LS-1.4.1.dist-info}/WHEEL +1 -1
- {DFO_LS-1.2.dist-info → DFO_LS-1.4.1.dist-info}/top_level.txt +0 -0
- dfols/__init__.py +4 -5
- dfols/controller.py +148 -24
- dfols/hessian.py +1 -1
- dfols/model.py +20 -6
- dfols/params.py +14 -0
- dfols/solver.py +84 -47
- dfols/trust_region.py +156 -5
- dfols/util.py +53 -3
- DFO_LS-1.2.dist-info/RECORD +0 -16
- DFO_LS-1.2.dist-info/zip-safe +0 -1
- dfols/version.py +0 -25
- {DFO_LS-1.2.dist-info → DFO_LS-1.4.1.dist-info}/LICENSE.txt +0 -0
|
@@ -1,41 +1,53 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: DFO-LS
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: A flexible derivative-free solver for (bound constrained) nonlinear least-squares minimization
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
Author-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
|
|
6
|
+
Maintainer-email: Lindon Roberts <lindon.roberts@sydney.edu.au>
|
|
7
|
+
License: GPL-3.0-or-later
|
|
8
|
+
Project-URL: Homepage, https://github.com/numericalalgorithmsgroup/dfols
|
|
9
|
+
Project-URL: Download, https://github.com/numericalalgorithmsgroup/dfols/releases/
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/numericalalgorithmsgroup/dfols/issues/
|
|
11
|
+
Project-URL: Documentation, https://numericalalgorithmsgroup.github.io/dfols/
|
|
12
|
+
Project-URL: Source Code, https://github.com/numericalalgorithmsgroup/dfols
|
|
13
|
+
Keywords: mathematics,optimization,least squares,derivative free optimization,nonlinear least squares
|
|
12
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
15
|
Classifier: Environment :: Console
|
|
14
16
|
Classifier: Framework :: IPython
|
|
15
17
|
Classifier: Framework :: Jupyter
|
|
16
|
-
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
17
18
|
Classifier: Intended Audience :: Science/Research
|
|
18
|
-
Classifier: License :: OSI Approved :: GNU General Public License (
|
|
19
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
19
20
|
Classifier: Operating System :: MacOS
|
|
20
21
|
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
-
Classifier: Operating System :: POSIX
|
|
22
22
|
Classifier: Operating System :: Unix
|
|
23
23
|
Classifier: Programming Language :: Python
|
|
24
|
-
Classifier: Programming Language :: Python :: 2
|
|
25
24
|
Classifier: Programming Language :: Python :: 3
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
29
|
Classifier: Topic :: Scientific/Engineering
|
|
27
30
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
28
|
-
Requires-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/x-rst
|
|
33
|
+
License-File: LICENSE.txt
|
|
34
|
+
Requires-Dist: setuptools
|
|
35
|
+
Requires-Dist: numpy
|
|
36
|
+
Requires-Dist: scipy >=1.11
|
|
37
|
+
Requires-Dist: pandas
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
40
|
+
Requires-Dist: Sphinx ; extra == 'dev'
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme ; extra == 'dev'
|
|
42
|
+
Provides-Extra: trustregion
|
|
43
|
+
Requires-Dist: trustregion >=1.1 ; extra == 'trustregion'
|
|
32
44
|
|
|
33
45
|
===================================================
|
|
34
46
|
DFO-LS: Derivative-Free Optimizer for Least-Squares
|
|
35
47
|
===================================================
|
|
36
48
|
|
|
37
|
-
.. image:: https://
|
|
38
|
-
:target: https://
|
|
49
|
+
.. image:: https://github.com/numericalalgorithmsgroup/dfols/actions/workflows/python_testing.yml/badge.svg
|
|
50
|
+
:target: https://github.com/numericalalgorithmsgroup/dfols/actions
|
|
39
51
|
:alt: Build Status
|
|
40
52
|
|
|
41
53
|
.. image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg
|
|
@@ -49,6 +61,10 @@ DFO-LS: Derivative-Free Optimizer for Least-Squares
|
|
|
49
61
|
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.2630426.svg
|
|
50
62
|
:target: https://doi.org/10.5281/zenodo.2630426
|
|
51
63
|
:alt: DOI:10.5281/zenodo.2630426
|
|
64
|
+
|
|
65
|
+
.. image:: https://static.pepy.tech/personalized-badge/dfo-ls?period=total&units=international_system&left_color=black&right_color=green&left_text=Downloads
|
|
66
|
+
:target: https://pepy.tech/project/dfo-ls
|
|
67
|
+
:alt: Total downloads
|
|
52
68
|
|
|
53
69
|
DFO-LS is a flexible package for solving nonlinear least-squares minimization, without requiring derivatives of the objective. It is particularly useful when evaluations of the objective function are expensive and/or noisy. DFO-LS is more flexible version of `DFO-GN <https://github.com/numericalalgorithmsgroup/dfogn>`_.
|
|
54
70
|
|
|
@@ -66,36 +82,49 @@ If you use DFO-LS in a paper, please cite:
|
|
|
66
82
|
|
|
67
83
|
Cartis, C., Fiala, J., Marteau, B. and Roberts, L., `Improving the Flexibility and Robustness of Model-Based Derivative-Free Optimization Solvers <https://doi.org/10.1145/3338517>`_, *ACM Transactions on Mathematical Software*, 45:3 (2019), pp. 32:1-32:41.
|
|
68
84
|
|
|
85
|
+
If you use DFO-LS for problems with constraints, including bound constraints, please also cite:
|
|
86
|
+
|
|
87
|
+
Hough, M. and Roberts, L., `Model-Based Derivative-Free Methods for Convex-Constrained Optimization <https://doi.org/10.1137/21M1460971>`_, *SIAM Journal on Optimization*, 21:4 (2022), pp. 2552-2579.
|
|
88
|
+
|
|
69
89
|
Requirements
|
|
70
90
|
------------
|
|
71
91
|
DFO-LS requires the following software to be installed:
|
|
72
92
|
|
|
73
|
-
* Python
|
|
74
|
-
* Fortran compiler (e.g. `gfortran <https://gcc.gnu.org/wiki/GFortran>`_), required by the `trustregion <https://github.com/lindonroberts/trust-region>`_ package.
|
|
93
|
+
* Python 3.9 or higher (http://www.python.org/)
|
|
75
94
|
|
|
76
95
|
Additionally, the following python packages should be installed (these will be installed automatically if using *pip*, see `Installation using pip`_):
|
|
77
96
|
|
|
78
|
-
* NumPy
|
|
79
|
-
* SciPy
|
|
80
|
-
* Pandas
|
|
97
|
+
* NumPy (http://www.numpy.org/)
|
|
98
|
+
* SciPy version 1.11 or higher (http://www.scipy.org/)
|
|
99
|
+
* Pandas (http://pandas.pydata.org/)
|
|
100
|
+
|
|
101
|
+
**Optional package:** DFO-LS versions 1.2 and higher also support the `trustregion <https://github.com/lindonroberts/trust-region>`_ package for fast trust-region subproblem solutions. To install this, make sure you have a Fortran compiler (e.g. `gfortran <https://gcc.gnu.org/wiki/GFortran>`_) and NumPy installed, then run :code:`pip install trustregion`. You do not have to have trustregion installed for DFO-LS to work, and it is not installed by default.
|
|
102
|
+
|
|
103
|
+
Installation using conda
|
|
104
|
+
------------------------
|
|
105
|
+
DFO-LS can be directly installed in Anaconda environments using `conda-forge <https://anaconda.org/conda-forge/dfo-ls>`_:
|
|
106
|
+
|
|
107
|
+
.. code-block:: bash
|
|
108
|
+
|
|
109
|
+
$ conda install -c conda-forge dfo-ls
|
|
81
110
|
|
|
82
111
|
Installation using pip
|
|
83
112
|
----------------------
|
|
84
113
|
For easy installation, use `pip <http://www.pip-installer.org/>`_ as root:
|
|
85
114
|
|
|
86
|
-
|
|
115
|
+
.. code-block:: bash
|
|
87
116
|
|
|
88
117
|
$ [sudo] pip install DFO-LS
|
|
89
118
|
|
|
90
119
|
or alternatively *easy_install*:
|
|
91
120
|
|
|
92
|
-
|
|
121
|
+
.. code-block:: bash
|
|
93
122
|
|
|
94
123
|
$ [sudo] easy_install DFO-LS
|
|
95
124
|
|
|
96
125
|
If you do not have root privileges or you want to install DFO-LS for your private use, you can use:
|
|
97
126
|
|
|
98
|
-
|
|
127
|
+
.. code-block:: bash
|
|
99
128
|
|
|
100
129
|
$ pip install --user DFO-LS
|
|
101
130
|
|
|
@@ -103,7 +132,7 @@ which will install DFO-LS in your home directory.
|
|
|
103
132
|
|
|
104
133
|
Note that if an older install of DFO-LS is present on your system you can use:
|
|
105
134
|
|
|
106
|
-
|
|
135
|
+
.. code-block:: bash
|
|
107
136
|
|
|
108
137
|
$ [sudo] pip install --upgrade DFO-LS
|
|
109
138
|
|
|
@@ -132,7 +161,7 @@ If you do not have root privileges or you want to install DFO-LS for your privat
|
|
|
132
161
|
|
|
133
162
|
instead.
|
|
134
163
|
|
|
135
|
-
To upgrade DFO-LS to the latest version, navigate to the top-level directory (i.e. the one containing :code:`
|
|
164
|
+
To upgrade DFO-LS to the latest version, navigate to the top-level directory (i.e. the one containing :code:`pyproject.toml`) and rerun the installation using :code:`pip`, as above:
|
|
136
165
|
|
|
137
166
|
.. code-block:: bash
|
|
138
167
|
|
|
@@ -141,11 +170,12 @@ To upgrade DFO-LS to the latest version, navigate to the top-level directory (i.
|
|
|
141
170
|
|
|
142
171
|
Testing
|
|
143
172
|
-------
|
|
144
|
-
If you installed DFO-LS manually, you can test your installation
|
|
173
|
+
If you installed DFO-LS manually, you can test your installation using the pytest package:
|
|
145
174
|
|
|
146
175
|
.. code-block:: bash
|
|
147
176
|
|
|
148
|
-
$
|
|
177
|
+
$ pip install pytest
|
|
178
|
+
$ python -m pytest --pyargs dfols
|
|
149
179
|
|
|
150
180
|
Alternatively, the HTML documentation provides some simple examples of how to run DFO-LS.
|
|
151
181
|
|
|
@@ -165,10 +195,8 @@ If DFO-LS was installed manually you have to remove the installed files by hand
|
|
|
165
195
|
|
|
166
196
|
Bugs
|
|
167
197
|
----
|
|
168
|
-
Please report any bugs using GitHub's issue tracker.
|
|
198
|
+
Please report any bugs using `GitHub's issue tracker <https://github.com/numericalalgorithmsgroup/dfols/issues>`_.
|
|
169
199
|
|
|
170
200
|
License
|
|
171
201
|
-------
|
|
172
202
|
This algorithm is released under the GNU GPL license. Please `contact NAG <http://www.nag.com/content/worldwide-contact-information>`_ for alternative licensing.
|
|
173
|
-
|
|
174
|
-
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
dfols/__init__.py,sha256=D-x5glfZFfJ8-bdjA-4k4JFTDu1Eylaz3EL4GSH28eI,1605
|
|
2
|
+
dfols/controller.py,sha256=LSeHZoKaKUEYgB1_2subjKskHJ8mWccMbn-LOpxJ7LM,42769
|
|
3
|
+
dfols/diagnostic_info.py,sha256=2kEUkL-MS4eDENUf1r2hOWsntP8OxMDKi_kyHmrC9V4,6081
|
|
4
|
+
dfols/hessian.py,sha256=sExx4J4KoGwHItbthX2odosB2ONbQFvLdlcod7PIh4k,4262
|
|
5
|
+
dfols/model.py,sha256=q70zuqocNtsaXzNjWHcTdrS209BdQt4uY0GNtp0qlI8,18809
|
|
6
|
+
dfols/params.py,sha256=_Va1ybnQDIzWaXvImcSeH8xnNE_A2zpAfBgDG74sc5c,17557
|
|
7
|
+
dfols/solver.py,sha256=IKg3xWPLYlOW_zuTc_-HY_3ZvdDEfkyxARerERUQHlU,61264
|
|
8
|
+
dfols/trust_region.py,sha256=hRKQx0fpSxol7dLZO0yrT7O5IDptPPSnDvxKQNZ3r0M,24603
|
|
9
|
+
dfols/util.py,sha256=ysdIHTkrkWwCRKuGffofehKl-t5dT3sD9dfy0muI4ZI,9852
|
|
10
|
+
DFO_LS-1.4.1.dist-info/LICENSE.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
|
11
|
+
DFO_LS-1.4.1.dist-info/METADATA,sha256=RR6KhJi4Ae_1PES8Bpzqm3AYK2w12V-2MyDyjaCDe80,8552
|
|
12
|
+
DFO_LS-1.4.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
13
|
+
DFO_LS-1.4.1.dist-info/top_level.txt,sha256=UfxRhaDN8HQx2_l17KbrDrERJ90OCN7VKkDMpYYbRLU,6
|
|
14
|
+
DFO_LS-1.4.1.dist-info/RECORD,,
|
|
File without changes
|
dfols/__init__.py
CHANGED
|
@@ -7,8 +7,7 @@ nonlinear least-squares solver which only requires function values.
|
|
|
7
7
|
|
|
8
8
|
It solves the nonlinear least-squares problem:
|
|
9
9
|
min_{x} f(x) = r1(x)**2 + ... + rm(x)**2,
|
|
10
|
-
subject to
|
|
11
|
-
lb <= x <= ub,
|
|
10
|
+
(optionally) subject to finitely many convex constraints,
|
|
12
11
|
where each function ri(x) is differentiable, possibly nonconvex.
|
|
13
12
|
Since the derivatives of ri(x) are never required or approximated,
|
|
14
13
|
the solver works when the evaluation of ri(x) is noisy.
|
|
@@ -39,10 +38,10 @@ alternative licensing.
|
|
|
39
38
|
# Ensure compatibility with Python 2
|
|
40
39
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
# DFO-LS version
|
|
42
|
+
__version__ = '1.4.1'
|
|
44
43
|
|
|
45
44
|
# Main solver & exit flags
|
|
46
45
|
from .solver import *
|
|
47
|
-
__all__
|
|
46
|
+
__all__ = ['solve']
|
|
48
47
|
|
dfols/controller.py
CHANGED
|
@@ -41,8 +41,11 @@ from .util import *
|
|
|
41
41
|
|
|
42
42
|
__all__ = ['Controller', 'ExitInformation', 'EXIT_SLOW_WARNING', 'EXIT_MAXFUN_WARNING', 'EXIT_SUCCESS',
|
|
43
43
|
'EXIT_INPUT_ERROR', 'EXIT_TR_INCREASE_ERROR', 'EXIT_LINALG_ERROR', 'EXIT_FALSE_SUCCESS_WARNING',
|
|
44
|
-
'EXIT_AUTO_DETECT_RESTART_WARNING']
|
|
44
|
+
'EXIT_AUTO_DETECT_RESTART_WARNING', 'EXIT_EVAL_ERROR']
|
|
45
45
|
|
|
46
|
+
module_logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
EXIT_TR_INCREASE_WARNING = 5 # warning, TR increase in proj constrained case - likely due to multiple active constraints
|
|
46
49
|
EXIT_AUTO_DETECT_RESTART_WARNING = 4 # warning, auto-detected restart criteria
|
|
47
50
|
EXIT_FALSE_SUCCESS_WARNING = 3 # warning, maximum fake successful steps reached
|
|
48
51
|
EXIT_SLOW_WARNING = 2 # warning, maximum number of slow (successful) iterations reached
|
|
@@ -51,6 +54,7 @@ EXIT_SUCCESS = 0 # successful finish (rho=rhoend, sufficient objective reductio
|
|
|
51
54
|
EXIT_INPUT_ERROR = -1 # error, bad inputs
|
|
52
55
|
EXIT_TR_INCREASE_ERROR = -2 # error, trust region step increased model value
|
|
53
56
|
EXIT_LINALG_ERROR = -3 # error, linalg error (singular matrix encountered)
|
|
57
|
+
EXIT_EVAL_ERROR = -4 # error, objective evaluation error (e.g. nan result received)
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
class ExitInformation(object):
|
|
@@ -70,6 +74,8 @@ class ExitInformation(object):
|
|
|
70
74
|
return "Warning (slow progress): " + self.msg
|
|
71
75
|
elif self.flag == EXIT_MAXFUN_WARNING:
|
|
72
76
|
return "Warning (max evals): " + self.msg
|
|
77
|
+
elif self.flag == EXIT_TR_INCREASE_WARNING:
|
|
78
|
+
return "Warning (trust region increase): " + self.msg
|
|
73
79
|
elif self.flag == EXIT_INPUT_ERROR:
|
|
74
80
|
return "Error (bad input): " + self.msg
|
|
75
81
|
elif self.flag == EXIT_TR_INCREASE_ERROR:
|
|
@@ -78,11 +84,13 @@ class ExitInformation(object):
|
|
|
78
84
|
return "Error (linear algebra): " + self.msg
|
|
79
85
|
elif self.flag == EXIT_FALSE_SUCCESS_WARNING:
|
|
80
86
|
return "Warning (max false good steps): " + self.msg
|
|
87
|
+
elif self.flag == EXIT_EVAL_ERROR:
|
|
88
|
+
return "Error (function evaluation): " + self.msg
|
|
81
89
|
else:
|
|
82
90
|
return "Unknown exit flag: " + self.msg
|
|
83
91
|
|
|
84
92
|
def able_to_do_restart(self):
|
|
85
|
-
if self.flag in [EXIT_TR_INCREASE_ERROR, EXIT_LINALG_ERROR, EXIT_SLOW_WARNING, EXIT_AUTO_DETECT_RESTART_WARNING]:
|
|
93
|
+
if self.flag in [EXIT_TR_INCREASE_ERROR, EXIT_TR_INCREASE_WARNING, EXIT_LINALG_ERROR, EXIT_SLOW_WARNING, EXIT_AUTO_DETECT_RESTART_WARNING, EXIT_EVAL_ERROR]:
|
|
86
94
|
return True
|
|
87
95
|
elif self.flag in [EXIT_MAXFUN_WARNING, EXIT_INPUT_ERROR]:
|
|
88
96
|
return False
|
|
@@ -92,13 +100,13 @@ class ExitInformation(object):
|
|
|
92
100
|
|
|
93
101
|
|
|
94
102
|
class Controller(object):
|
|
95
|
-
def __init__(self, objfun, args, x0, r0, r0_nsamples, xl, xu, npt, rhobeg, rhoend, nf, nx, maxfun, params,
|
|
103
|
+
def __init__(self, objfun, args, x0, r0, r0_nsamples, xl, xu, projections, npt, rhobeg, rhoend, nf, nx, maxfun, params,
|
|
96
104
|
scaling_changes, do_logging):
|
|
97
105
|
self.do_logging = do_logging
|
|
98
106
|
self.objfun = objfun
|
|
99
107
|
self.args = args
|
|
100
108
|
self.maxfun = maxfun
|
|
101
|
-
self.model = Model(npt, x0, r0, xl, xu, r0_nsamples, precondition=params("interpolation.precondition"),
|
|
109
|
+
self.model = Model(npt, x0, r0, xl, xu, projections, r0_nsamples, precondition=params("interpolation.precondition"),
|
|
102
110
|
abs_tol = params("model.abs_tol"), rel_tol = params("model.rel_tol"), do_logging=do_logging)
|
|
103
111
|
self.nf = nf
|
|
104
112
|
self.nx = nx
|
|
@@ -107,9 +115,6 @@ class Controller(object):
|
|
|
107
115
|
self.rho = rhobeg
|
|
108
116
|
self.rhoend = rhoend
|
|
109
117
|
self.diffs = [0.0, 0.0, 0.0]
|
|
110
|
-
self.last_iters_step_taken = []
|
|
111
|
-
self.last_fopts_step_taken = []
|
|
112
|
-
self.num_slow_iters = 0
|
|
113
118
|
self.finished_growing = False
|
|
114
119
|
self.finished_halfway_growing = False
|
|
115
120
|
# For measuing slow iterations
|
|
@@ -134,12 +139,113 @@ class Controller(object):
|
|
|
134
139
|
|
|
135
140
|
def initialise_coordinate_directions(self, number_of_samples, num_directions, params):
|
|
136
141
|
if self.do_logging:
|
|
137
|
-
|
|
142
|
+
module_logger.debug("Initialising with coordinate directions")
|
|
138
143
|
# self.model already has x0 evaluated, so only need to initialise the other points
|
|
139
144
|
# num_directions = params("growing.ndirs_initial")
|
|
140
145
|
assert self.model.num_pts <= (self.n() + 1) * (self.n() + 2) // 2, "prelim: must have npt <= (n+1)(n+2)/2"
|
|
141
146
|
assert 1 <= num_directions < self.model.num_pts, "Initialisation: must have 1 <= ndirs_initial < npt"
|
|
142
147
|
|
|
148
|
+
|
|
149
|
+
if self.model.projections:
|
|
150
|
+
D = np.zeros((self.n(),self.n()))
|
|
151
|
+
k = 0
|
|
152
|
+
while k < self.n():
|
|
153
|
+
ek = np.zeros(self.n())
|
|
154
|
+
ek[k] = 1
|
|
155
|
+
p = np.dot(ek,min(1,self.delta))
|
|
156
|
+
yk = dykstra(self.model.projections, self.model.xbase + p, max_iter=params("dykstra.max_iters"), tol=params("dykstra.d_tol"))
|
|
157
|
+
D[k,:] = yk - self.model.xbase
|
|
158
|
+
|
|
159
|
+
k += 1 # move on to next point
|
|
160
|
+
|
|
161
|
+
# Have at least one L.D. vector, try negative direction on bad one first
|
|
162
|
+
k = 0
|
|
163
|
+
mr_tol = params("matrix_rank.r_tol")
|
|
164
|
+
D_rank, diag = qr_rank(D,tol=mr_tol)
|
|
165
|
+
while D_rank != num_directions and k < self.n():
|
|
166
|
+
if diag[k] < mr_tol:
|
|
167
|
+
ek = np.zeros(self.n())
|
|
168
|
+
ek[k] = 1
|
|
169
|
+
p = -np.dot(ek,min(1,self.delta))
|
|
170
|
+
yk = dykstra(self.model.projections, self.model.xbase + p, max_iter=params("dykstra.max_iters"), tol=params("dykstra.d_tol"))
|
|
171
|
+
dk = D[k,:].copy()
|
|
172
|
+
D[k,:] = yk - self.model.xbase
|
|
173
|
+
D_rank2, _diag2 = qr_rank(D,tol=params("matrix_rank.r_tol"))
|
|
174
|
+
if D_rank2 <= D_rank:
|
|
175
|
+
# Did not improve rank, revert change
|
|
176
|
+
D[k,:] = dk
|
|
177
|
+
# rank was improved, update D_rank for next comparison
|
|
178
|
+
D_rank = D_rank2
|
|
179
|
+
k += 1
|
|
180
|
+
|
|
181
|
+
# Try random combination of negatives...
|
|
182
|
+
k = 0
|
|
183
|
+
slctr = np.random.randint(0, 1+1, self.n()) # generate rand binary "selector" array
|
|
184
|
+
D_rank, diag = qr_rank(D,tol=params("matrix_rank.r_tol"))
|
|
185
|
+
while D_rank != num_directions and k < 100*self.n():
|
|
186
|
+
if slctr[k%self.n()] == 1: # if selector says make -ve, make -ve
|
|
187
|
+
ek = np.zeros(self.n())
|
|
188
|
+
ek[k%self.n()] = 1
|
|
189
|
+
p = -np.dot(ek,min(1,self.delta))
|
|
190
|
+
yk = dykstra(self.model.projections, self.model.xbase + p, max_iter=params("dykstra.max_iters"), tol=params("dykstra.d_tol"))
|
|
191
|
+
dk = D[k%self.n(),:].copy()
|
|
192
|
+
D[k%self.n(),:] = yk - self.model.xbase
|
|
193
|
+
D_rank2, _diag2 = qr_rank(D,tol=params("matrix_rank.r_tol"))
|
|
194
|
+
if D_rank2 <= D_rank:
|
|
195
|
+
# Did not improve rank, revert change
|
|
196
|
+
D[k%self.n(),:] = dk
|
|
197
|
+
# rank was improved, update D_rank for next comparison
|
|
198
|
+
D_rank = D_rank2
|
|
199
|
+
|
|
200
|
+
# Go again
|
|
201
|
+
slctr = np.random.randint(0, 1+1, self.n())
|
|
202
|
+
k += 1
|
|
203
|
+
|
|
204
|
+
# Set still not L.I? Try random directions
|
|
205
|
+
i = 0
|
|
206
|
+
D_rank, diag = qr_rank(D,tol=params("matrix_rank.r_tol"))
|
|
207
|
+
while D_rank != num_directions and i <= 100*num_directions:
|
|
208
|
+
k = 0
|
|
209
|
+
while k < self.n():
|
|
210
|
+
if diag[k] < mr_tol:
|
|
211
|
+
p = np.random.normal(size=self.n())
|
|
212
|
+
p = p/np.linalg.norm(p)
|
|
213
|
+
p = np.dot(p,min(1,self.delta))
|
|
214
|
+
yk = dykstra(self.model.projections, self.model.xbase + p, max_iter=params("dykstra.max_iters"), tol=params("dykstra.d_tol"))
|
|
215
|
+
dk = D[k,:].copy()
|
|
216
|
+
D[k,:] = yk - self.model.xbase
|
|
217
|
+
D_rank2, _diag2 = qr_rank(D,tol=params("matrix_rank.r_tol"))
|
|
218
|
+
if D_rank2 <= D_rank:
|
|
219
|
+
# Did not improve rank, revert change
|
|
220
|
+
D[k,:] = dk
|
|
221
|
+
# rank was improved, update D_rank for next comparison
|
|
222
|
+
D_rank = D_rank2
|
|
223
|
+
k += 1
|
|
224
|
+
i += 1
|
|
225
|
+
|
|
226
|
+
if D_rank != num_directions:
|
|
227
|
+
raise RuntimeError("Unable to generate suitable initial directions")
|
|
228
|
+
|
|
229
|
+
# we have a L.I set of interpolation points
|
|
230
|
+
for k in range(0,self.n()):
|
|
231
|
+
# Evaluate objective at this new point
|
|
232
|
+
x = self.model.as_absolute_coordinates(D[k, :])
|
|
233
|
+
rvec_list, f_list, num_samples_run, exit_info = self.evaluate_objective(x, number_of_samples, params)
|
|
234
|
+
|
|
235
|
+
# Handle exit conditions (f < min obj value or maxfun reached)
|
|
236
|
+
if exit_info is not None:
|
|
237
|
+
if num_samples_run > 0:
|
|
238
|
+
self.model.save_point(x, np.mean(rvec_list[:num_samples_run, :], axis=0), num_samples_run,
|
|
239
|
+
x_in_abs_coords=True)
|
|
240
|
+
return exit_info # return & quit
|
|
241
|
+
|
|
242
|
+
# Otherwise, add new results (increments model.npt_so_far)
|
|
243
|
+
self.model.change_point(k+1, x - self.model.xbase, rvec_list[0, :]) # expect step, not absolute x
|
|
244
|
+
for i in range(1, num_samples_run):
|
|
245
|
+
self.model.add_new_sample(k+1, rvec_extra=rvec_list[i, :])
|
|
246
|
+
|
|
247
|
+
return None # return & continue
|
|
248
|
+
|
|
143
249
|
at_lower_boundary = (self.model.sl > -0.01 * self.delta) # sl = xl - x0, should be -ve, actually < -rhobeg
|
|
144
250
|
at_upper_boundary = (self.model.su < 0.01 * self.delta) # su = xu - x0, should be +ve, actually > rhobeg
|
|
145
251
|
|
|
@@ -150,17 +256,19 @@ class Controller(object):
|
|
|
150
256
|
# k = 2n+1, ..., (n+1)(n+2)/2 --> off-diagonal directions
|
|
151
257
|
if 1 <= k < self.n() + 1: # first step along coord directions
|
|
152
258
|
dirn = k - 1 # direction to move in (0,...,n-1)
|
|
153
|
-
stepa = self.delta if not at_upper_boundary[dirn] else -self.delta
|
|
259
|
+
stepa = self.delta if not at_upper_boundary[dirn] else -self.delta # take a +delta step if at lower, -delta if at upper
|
|
154
260
|
stepb = None
|
|
155
|
-
xpts_added[k, dirn] = stepa
|
|
261
|
+
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.
|
|
156
262
|
|
|
157
263
|
elif self.n() + 1 <= k < 2 * self.n() + 1: # second step along coord directions
|
|
158
264
|
dirn = k - self.n() - 1 # direction to move in (0,...,n-1)
|
|
159
|
-
stepa = xpts_added[k - self.n(), dirn]
|
|
160
|
-
stepb = -self.delta
|
|
265
|
+
stepa = xpts_added[k - self.n(), dirn] # previous step
|
|
266
|
+
stepb = -self.delta # new step
|
|
161
267
|
if at_lower_boundary[dirn]:
|
|
268
|
+
# if at lower boundary, set the second step to be +ve
|
|
162
269
|
stepb = min(2.0 * self.delta, self.model.su[dirn]) # su = xu - x0, should be +ve
|
|
163
270
|
if at_upper_boundary[dirn]:
|
|
271
|
+
# if at upper boundary, set the second step to be -ve
|
|
164
272
|
stepb = max(-2.0 * self.delta, self.model.sl[dirn]) # sl = xl - x0, should be -ve
|
|
165
273
|
xpts_added[k, dirn] = stepb
|
|
166
274
|
|
|
@@ -208,7 +316,7 @@ class Controller(object):
|
|
|
208
316
|
|
|
209
317
|
def initialise_random_directions(self, number_of_samples, num_directions, params):
|
|
210
318
|
if self.do_logging:
|
|
211
|
-
|
|
319
|
+
module_logger.debug("Initialising with random orthogonal directions")
|
|
212
320
|
# self.model already has x0 evaluated, so only need to initialise the other points
|
|
213
321
|
assert 1 <= num_directions < self.model.num_pts, "Initialisation: must have 1 <= ndirs_initial < npt"
|
|
214
322
|
|
|
@@ -328,20 +436,28 @@ class Controller(object):
|
|
|
328
436
|
|
|
329
437
|
return dirn * (step_length / LA.norm(dirn))
|
|
330
438
|
|
|
331
|
-
def trust_region_step(self):
|
|
439
|
+
def trust_region_step(self, params):
|
|
332
440
|
# Build model for full least squares objectives
|
|
333
441
|
gopt, H = self.model.build_full_model()
|
|
334
|
-
|
|
442
|
+
if self.model.projections:
|
|
443
|
+
d, gnew, crvmin = ctrsbox(self.model.xopt(abs_coordinates=True), gopt, H, self.model.projections, self.delta, d_max_iters=params("dykstra.max_iters"), d_tol=params("dykstra.d_tol"))
|
|
444
|
+
else:
|
|
445
|
+
d, gnew, crvmin = trsbox(self.model.xopt(), gopt, H, self.model.sl, self.model.su, self.delta)
|
|
335
446
|
return d, gopt, H, gnew, crvmin
|
|
336
447
|
|
|
337
448
|
def geometry_step(self, knew, adelt, number_of_samples, params):
|
|
338
449
|
if self.do_logging:
|
|
339
|
-
|
|
450
|
+
module_logger.debug("Running geometry-fixing step")
|
|
340
451
|
try:
|
|
341
452
|
c, g = self.model.lagrange_gradient(knew)
|
|
342
453
|
# c = 1.0 if knew == self.model.kopt else 0.0 # based at xopt, just like d
|
|
343
|
-
|
|
344
|
-
|
|
454
|
+
if self.model.projections:
|
|
455
|
+
# Solve problem: use projection onto arbitrary constraints, and ||xnew-xopt|| <= adelt
|
|
456
|
+
step = ctrsbox_geometry(self.model.xopt(abs_coordinates=True), c, g, self.model.projections, adelt, d_max_iters=params("dykstra.max_iters"), d_tol=params("dykstra.d_tol"))
|
|
457
|
+
xnew = self.model.xopt() + step
|
|
458
|
+
else:
|
|
459
|
+
# Solve problem: bounds are sl <= xnew <= su, and ||xnew-xopt|| <= adelt
|
|
460
|
+
xnew = trsbox_geometry(self.model.xopt(), c, g, np.minimum(self.model.sl, 0.0), np.maximum(self.model.su, 0.0), adelt)
|
|
345
461
|
except LA.LinAlgError:
|
|
346
462
|
exit_info = ExitInformation(EXIT_LINALG_ERROR, "Singular matrix encountered in geometry step")
|
|
347
463
|
return exit_info # didn't fix geometry - return & quit
|
|
@@ -502,13 +618,16 @@ class Controller(object):
|
|
|
502
618
|
def calculate_ratio(self, current_iter, rvec_list, d, gopt, H):
|
|
503
619
|
exit_info = None
|
|
504
620
|
f = sumsq(np.mean(rvec_list, axis=0)) # estimate actual objective value
|
|
505
|
-
pred_reduction = - model_value(gopt, H, d)
|
|
621
|
+
pred_reduction = - model_value(gopt, H, d) # negative of m since m(0) = 0
|
|
506
622
|
actual_reduction = self.model.fopt() - f
|
|
507
623
|
self.diffs = [abs(actual_reduction - pred_reduction), self.diffs[0], self.diffs[1]]
|
|
508
624
|
if min(sqrt(sumsq(d)), self.delta) > self.rho: # if ||d|| >= rho, successful!
|
|
509
625
|
self.last_successful_iter = current_iter
|
|
510
626
|
if pred_reduction < 0.0:
|
|
511
|
-
|
|
627
|
+
if len(self.model.projections) > 1: # if we are using multiple projections, only warn since likely due to constraint intersection
|
|
628
|
+
exit_info = ExitInformation(EXIT_TR_INCREASE_WARNING, "Either multiple constraints are active or trust region step gave model increase")
|
|
629
|
+
else:
|
|
630
|
+
exit_info = ExitInformation(EXIT_TR_INCREASE_ERROR, "Either rust region step gave model increase")
|
|
512
631
|
|
|
513
632
|
ratio = actual_reduction / pred_reduction
|
|
514
633
|
return ratio, exit_info
|
|
@@ -529,12 +648,12 @@ class Controller(object):
|
|
|
529
648
|
if this_iter_slow:
|
|
530
649
|
self.num_slow_iters += 1
|
|
531
650
|
if self.do_logging:
|
|
532
|
-
|
|
651
|
+
module_logger.info("Slow iteration (%g consecutive so far, max allowed %g)"
|
|
533
652
|
% (self.num_slow_iters, params("slow.max_slow_iters")))
|
|
534
653
|
else:
|
|
535
654
|
self.num_slow_iters = 0
|
|
536
655
|
if self.do_logging:
|
|
537
|
-
|
|
656
|
+
module_logger.debug("Non-slow iteration")
|
|
538
657
|
return this_iter_slow, self.num_slow_iters >= params("slow.max_slow_iters")
|
|
539
658
|
|
|
540
659
|
def soft_restart(self, number_of_samples, nruns_so_far, params, x_in_abs_coords_to_save=None, rvec_to_save=None,
|
|
@@ -563,12 +682,17 @@ class Controller(object):
|
|
|
563
682
|
self.model.nsamples[self.model.kopt], x_in_abs_coords=True)
|
|
564
683
|
|
|
565
684
|
if self.do_logging:
|
|
566
|
-
|
|
685
|
+
module_logger.info("Soft restart [currently, f = %g after %g function evals]" % (self.model.fopt(), self.nf))
|
|
567
686
|
# Resetting method: reset delta and rho, then move the closest 'num_steps' points to xk to improve geometry
|
|
568
687
|
# Note: closest points because we are suddenly increasing delta & rho, so we want to encourage spreading out points
|
|
569
688
|
self.delta = self.rhobeg
|
|
570
689
|
self.rho = self.rhobeg
|
|
571
690
|
self.diffs = [0.0, 0.0, 0.0]
|
|
691
|
+
|
|
692
|
+
# Forget history of slow iterations
|
|
693
|
+
self.last_iters_step_taken = []
|
|
694
|
+
self.last_fopts_step_taken = []
|
|
695
|
+
self.num_slow_iters = 0
|
|
572
696
|
|
|
573
697
|
all_sq_dist = self.model.distances_to_xopt()[:self.model.npt()]
|
|
574
698
|
closest_points = np.argsort(all_sq_dist)
|
|
@@ -615,7 +739,7 @@ class Controller(object):
|
|
|
615
739
|
self.model.add_new_sample(self.model.npt() - 1, rvec_extra=rvec_list[i, :])
|
|
616
740
|
|
|
617
741
|
if self.do_logging:
|
|
618
|
-
|
|
742
|
+
module_logger.info("Soft restart: added %g new directions, npt is now %g" % (num_pts_to_add, self.model.npt()))
|
|
619
743
|
|
|
620
744
|
# Otherwise, we are doing a restart
|
|
621
745
|
self.last_successful_iter = 0
|
dfols/hessian.py
CHANGED
|
@@ -39,7 +39,7 @@ class Hessian(object):
|
|
|
39
39
|
def __init__(self, n, vals=None):
|
|
40
40
|
self.n = n
|
|
41
41
|
if vals is None:
|
|
42
|
-
self.hq = np.zeros((n * (n + 1) // 2,), dtype=
|
|
42
|
+
self.hq = np.zeros((n * (n + 1) // 2,), dtype=float)
|
|
43
43
|
else:
|
|
44
44
|
assert isinstance(vals, np.ndarray), "Can only set Hessian from NumPy array"
|
|
45
45
|
assert len(vals.shape) in [1, 2], "Can only set Hessian from vector or matrix"
|
dfols/model.py
CHANGED
|
@@ -36,12 +36,15 @@ import numpy as np
|
|
|
36
36
|
import scipy.linalg as LA
|
|
37
37
|
|
|
38
38
|
from .trust_region import trsbox_geometry
|
|
39
|
-
from .util import sumsq
|
|
39
|
+
from .util import sumsq, dykstra
|
|
40
40
|
|
|
41
41
|
__all__ = ['Model']
|
|
42
42
|
|
|
43
|
+
module_logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
43
46
|
class Model(object):
|
|
44
|
-
def __init__(self, npt, x0, r0, xl, xu, r0_nsamples, n=None, m=None, abs_tol=1e-12, rel_tol=1e-20, precondition=True,
|
|
47
|
+
def __init__(self, npt, x0, r0, xl, xu, projections, r0_nsamples, n=None, m=None, abs_tol=1e-12, rel_tol=1e-20, precondition=True,
|
|
45
48
|
do_logging=True):
|
|
46
49
|
if n is None:
|
|
47
50
|
n = len(x0)
|
|
@@ -63,6 +66,7 @@ class Model(object):
|
|
|
63
66
|
self.xbase = x0.copy()
|
|
64
67
|
self.sl = xl - self.xbase # lower bound w.r.t. xbase (require xpt >= sl)
|
|
65
68
|
self.su = xu - self.xbase # upper bound w.r.t. xbase (require xpt <= su)
|
|
69
|
+
self.projections = projections
|
|
66
70
|
self.points = np.zeros((npt, n)) # interpolation points w.r.t. xbase
|
|
67
71
|
|
|
68
72
|
# Function values
|
|
@@ -71,7 +75,7 @@ class Model(object):
|
|
|
71
75
|
self.fval = np.inf * np.ones((npt, )) # overall objective value for each xpt
|
|
72
76
|
self.fval[0] = sumsq(r0)
|
|
73
77
|
self.kopt = 0 # index of current iterate (should be best value so far)
|
|
74
|
-
self.nsamples = np.zeros((npt,), dtype=
|
|
78
|
+
self.nsamples = np.zeros((npt,), dtype=int) # number of samples used to evaluate objective at each point
|
|
75
79
|
self.nsamples[0] = r0_nsamples
|
|
76
80
|
self.fbeg = self.fval[0] # f(x0), saved to check for sufficient reduction
|
|
77
81
|
|
|
@@ -123,6 +127,8 @@ class Model(object):
|
|
|
123
127
|
return np.minimum(np.maximum(self.sl, self.points[k, :].copy()), self.su)
|
|
124
128
|
else:
|
|
125
129
|
# Apply bounds and convert back to absolute coordinates
|
|
130
|
+
if self.projections:
|
|
131
|
+
return dykstra(self.projections, self.xbase + self.points[k,:])
|
|
126
132
|
return self.xbase + np.minimum(np.maximum(self.sl, self.points[k, :]), self.su)
|
|
127
133
|
|
|
128
134
|
def rvec(self, k):
|
|
@@ -133,8 +139,10 @@ class Model(object):
|
|
|
133
139
|
assert 0 <= k < self.npt(), "Invalid index %g" % k
|
|
134
140
|
return self.fval[k]
|
|
135
141
|
|
|
136
|
-
def as_absolute_coordinates(self, x):
|
|
142
|
+
def as_absolute_coordinates(self, x, full_dykstra=False):
|
|
137
143
|
# If x were an interpolation point, get the absolute coordinates of x
|
|
144
|
+
if self.projections:
|
|
145
|
+
return dykstra(self.projections, self.xbase + x)
|
|
138
146
|
return self.xbase + np.minimum(np.maximum(self.sl, x), self.su)
|
|
139
147
|
|
|
140
148
|
def xpt_directions(self, include_kopt=True):
|
|
@@ -301,12 +309,12 @@ class Model(object):
|
|
|
301
309
|
return col_scale(LA.solve_triangular(self.R, Qb), self.right_scaling)
|
|
302
310
|
else:
|
|
303
311
|
if self.do_logging:
|
|
304
|
-
|
|
312
|
+
module_logger.warning("model.solve_geom_system not using factorisation")
|
|
305
313
|
W, left_scaling, right_scaling = self.interpolation_matrix()
|
|
306
314
|
return col_scale(LA.lstsq(W, col_scale(rhs * left_scaling))[0], right_scaling)
|
|
307
315
|
|
|
308
316
|
def interpolate_mini_models_svd(self, verbose=False, make_full_rank=False, min_sing_val=1e-6, sing_val_frac=1.0, max_jac_cond=1e8,
|
|
309
|
-
get_chg_J=False):
|
|
317
|
+
get_chg_J=False, throw_error_on_nans=False):
|
|
310
318
|
W, left_scaling, right_scaling = self.interpolation_matrix()
|
|
311
319
|
self.factorise_geom_system()
|
|
312
320
|
ls_interp_cond_num = np.linalg.cond(W) if verbose else 0.0 # scipy.linalg does not have condition number!
|
|
@@ -327,12 +335,18 @@ class Model(object):
|
|
|
327
335
|
self.model_jac = np.dot(self.model_jac, np.dot(Qhat, Qhat.T))
|
|
328
336
|
|
|
329
337
|
rhs = self.fval_v[fval_row_idx, :] # size npt * m
|
|
338
|
+
if np.any(np.isnan(rhs)) and throw_error_on_nans:
|
|
339
|
+
if self.do_logging:
|
|
340
|
+
module_logger.warning("model.interpolate_mini_models_svd: NaNs encountered in objective evaluations, raising error")
|
|
341
|
+
raise np.linalg.LinAlgError("NaN encountered in objective evaluations")
|
|
330
342
|
try:
|
|
331
343
|
dg = self.solve_geom_system(rhs) # size (n+1)*m
|
|
332
344
|
except LA.LinAlgError:
|
|
333
345
|
return False, None, None, None, None # flag error
|
|
334
346
|
except ValueError:
|
|
335
347
|
return False, None, None, None, None # flag error (e.g. inf or NaN encountered)
|
|
348
|
+
if not np.all(np.isfinite(dg)): # another check for inf or NaN
|
|
349
|
+
return False, None, None, None, None
|
|
336
350
|
J_old = self.model_jac.copy()
|
|
337
351
|
self.model_jac = dg[1:,:].T
|
|
338
352
|
self.model_const = dg[0,:] - np.dot(self.model_jac, xopt) # shift base to xbase
|