pyGSTi 0.9.12.1__cp310-cp310-win32.whl → 0.9.13__cp310-cp310-win32.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. pyGSTi-0.9.13.dist-info/METADATA +197 -0
  2. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/RECORD +207 -217
  3. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/WHEEL +1 -1
  4. pygsti/_version.py +2 -2
  5. pygsti/algorithms/contract.py +1 -1
  6. pygsti/algorithms/core.py +42 -28
  7. pygsti/algorithms/fiducialselection.py +17 -8
  8. pygsti/algorithms/gaugeopt.py +2 -2
  9. pygsti/algorithms/germselection.py +87 -77
  10. pygsti/algorithms/mirroring.py +0 -388
  11. pygsti/algorithms/randomcircuit.py +165 -1333
  12. pygsti/algorithms/rbfit.py +0 -234
  13. pygsti/baseobjs/basis.py +94 -396
  14. pygsti/baseobjs/errorgenbasis.py +0 -132
  15. pygsti/baseobjs/errorgenspace.py +0 -10
  16. pygsti/baseobjs/label.py +52 -168
  17. pygsti/baseobjs/opcalc/fastopcalc.cp310-win32.pyd +0 -0
  18. pygsti/baseobjs/opcalc/fastopcalc.pyx +2 -2
  19. pygsti/baseobjs/polynomial.py +13 -595
  20. pygsti/baseobjs/statespace.py +1 -0
  21. pygsti/circuits/__init__.py +1 -1
  22. pygsti/circuits/circuit.py +682 -505
  23. pygsti/circuits/circuitconstruction.py +0 -4
  24. pygsti/circuits/circuitlist.py +47 -5
  25. pygsti/circuits/circuitparser/__init__.py +8 -8
  26. pygsti/circuits/circuitparser/fastcircuitparser.cp310-win32.pyd +0 -0
  27. pygsti/circuits/circuitstructure.py +3 -3
  28. pygsti/circuits/cloudcircuitconstruction.py +1 -1
  29. pygsti/data/datacomparator.py +2 -7
  30. pygsti/data/dataset.py +46 -44
  31. pygsti/data/hypothesistest.py +0 -7
  32. pygsti/drivers/bootstrap.py +0 -49
  33. pygsti/drivers/longsequence.py +2 -1
  34. pygsti/evotypes/basereps_cython.cp310-win32.pyd +0 -0
  35. pygsti/evotypes/chp/opreps.py +0 -61
  36. pygsti/evotypes/chp/statereps.py +0 -32
  37. pygsti/evotypes/densitymx/effectcreps.cpp +9 -10
  38. pygsti/evotypes/densitymx/effectreps.cp310-win32.pyd +0 -0
  39. pygsti/evotypes/densitymx/effectreps.pyx +1 -1
  40. pygsti/evotypes/densitymx/opreps.cp310-win32.pyd +0 -0
  41. pygsti/evotypes/densitymx/opreps.pyx +2 -2
  42. pygsti/evotypes/densitymx/statereps.cp310-win32.pyd +0 -0
  43. pygsti/evotypes/densitymx/statereps.pyx +1 -1
  44. pygsti/evotypes/densitymx_slow/effectreps.py +7 -23
  45. pygsti/evotypes/densitymx_slow/opreps.py +16 -23
  46. pygsti/evotypes/densitymx_slow/statereps.py +10 -3
  47. pygsti/evotypes/evotype.py +39 -2
  48. pygsti/evotypes/stabilizer/effectreps.cp310-win32.pyd +0 -0
  49. pygsti/evotypes/stabilizer/effectreps.pyx +0 -4
  50. pygsti/evotypes/stabilizer/opreps.cp310-win32.pyd +0 -0
  51. pygsti/evotypes/stabilizer/opreps.pyx +0 -4
  52. pygsti/evotypes/stabilizer/statereps.cp310-win32.pyd +0 -0
  53. pygsti/evotypes/stabilizer/statereps.pyx +1 -5
  54. pygsti/evotypes/stabilizer/termreps.cp310-win32.pyd +0 -0
  55. pygsti/evotypes/stabilizer/termreps.pyx +0 -7
  56. pygsti/evotypes/stabilizer_slow/effectreps.py +0 -22
  57. pygsti/evotypes/stabilizer_slow/opreps.py +0 -4
  58. pygsti/evotypes/stabilizer_slow/statereps.py +0 -4
  59. pygsti/evotypes/statevec/effectreps.cp310-win32.pyd +0 -0
  60. pygsti/evotypes/statevec/effectreps.pyx +1 -1
  61. pygsti/evotypes/statevec/opreps.cp310-win32.pyd +0 -0
  62. pygsti/evotypes/statevec/opreps.pyx +2 -2
  63. pygsti/evotypes/statevec/statereps.cp310-win32.pyd +0 -0
  64. pygsti/evotypes/statevec/statereps.pyx +1 -1
  65. pygsti/evotypes/statevec/termreps.cp310-win32.pyd +0 -0
  66. pygsti/evotypes/statevec/termreps.pyx +0 -7
  67. pygsti/evotypes/statevec_slow/effectreps.py +0 -3
  68. pygsti/evotypes/statevec_slow/opreps.py +0 -5
  69. pygsti/extras/__init__.py +0 -1
  70. pygsti/extras/drift/stabilityanalyzer.py +3 -1
  71. pygsti/extras/interpygate/__init__.py +12 -0
  72. pygsti/extras/interpygate/core.py +0 -36
  73. pygsti/extras/interpygate/process_tomography.py +44 -10
  74. pygsti/extras/rpe/rpeconstruction.py +0 -2
  75. pygsti/forwardsims/__init__.py +1 -0
  76. pygsti/forwardsims/forwardsim.py +14 -55
  77. pygsti/forwardsims/mapforwardsim.py +69 -18
  78. pygsti/forwardsims/mapforwardsim_calc_densitymx.cp310-win32.pyd +0 -0
  79. pygsti/forwardsims/mapforwardsim_calc_densitymx.pyx +65 -66
  80. pygsti/forwardsims/mapforwardsim_calc_generic.py +91 -13
  81. pygsti/forwardsims/matrixforwardsim.py +63 -15
  82. pygsti/forwardsims/termforwardsim.py +8 -110
  83. pygsti/forwardsims/termforwardsim_calc_stabilizer.cp310-win32.pyd +0 -0
  84. pygsti/forwardsims/termforwardsim_calc_statevec.cp310-win32.pyd +0 -0
  85. pygsti/forwardsims/termforwardsim_calc_statevec.pyx +0 -651
  86. pygsti/forwardsims/torchfwdsim.py +265 -0
  87. pygsti/forwardsims/weakforwardsim.py +2 -2
  88. pygsti/io/__init__.py +1 -2
  89. pygsti/io/mongodb.py +0 -2
  90. pygsti/io/stdinput.py +6 -22
  91. pygsti/layouts/copalayout.py +10 -12
  92. pygsti/layouts/distlayout.py +0 -40
  93. pygsti/layouts/maplayout.py +103 -25
  94. pygsti/layouts/matrixlayout.py +99 -60
  95. pygsti/layouts/prefixtable.py +1534 -52
  96. pygsti/layouts/termlayout.py +1 -1
  97. pygsti/modelmembers/instruments/instrument.py +3 -3
  98. pygsti/modelmembers/instruments/tpinstrument.py +2 -2
  99. pygsti/modelmembers/modelmember.py +0 -17
  100. pygsti/modelmembers/operations/__init__.py +2 -4
  101. pygsti/modelmembers/operations/affineshiftop.py +1 -0
  102. pygsti/modelmembers/operations/composederrorgen.py +1 -1
  103. pygsti/modelmembers/operations/composedop.py +1 -24
  104. pygsti/modelmembers/operations/denseop.py +5 -5
  105. pygsti/modelmembers/operations/eigpdenseop.py +2 -2
  106. pygsti/modelmembers/operations/embeddederrorgen.py +1 -1
  107. pygsti/modelmembers/operations/embeddedop.py +0 -1
  108. pygsti/modelmembers/operations/experrorgenop.py +2 -2
  109. pygsti/modelmembers/operations/fullarbitraryop.py +1 -0
  110. pygsti/modelmembers/operations/fullcptpop.py +2 -2
  111. pygsti/modelmembers/operations/fulltpop.py +28 -6
  112. pygsti/modelmembers/operations/fullunitaryop.py +5 -4
  113. pygsti/modelmembers/operations/lindbladcoefficients.py +93 -78
  114. pygsti/modelmembers/operations/lindbladerrorgen.py +268 -441
  115. pygsti/modelmembers/operations/linearop.py +7 -27
  116. pygsti/modelmembers/operations/opfactory.py +1 -1
  117. pygsti/modelmembers/operations/repeatedop.py +1 -24
  118. pygsti/modelmembers/operations/staticstdop.py +1 -1
  119. pygsti/modelmembers/povms/__init__.py +3 -3
  120. pygsti/modelmembers/povms/basepovm.py +7 -36
  121. pygsti/modelmembers/povms/complementeffect.py +4 -9
  122. pygsti/modelmembers/povms/composedeffect.py +0 -320
  123. pygsti/modelmembers/povms/computationaleffect.py +1 -1
  124. pygsti/modelmembers/povms/computationalpovm.py +3 -1
  125. pygsti/modelmembers/povms/effect.py +3 -5
  126. pygsti/modelmembers/povms/marginalizedpovm.py +0 -79
  127. pygsti/modelmembers/povms/tppovm.py +74 -2
  128. pygsti/modelmembers/states/__init__.py +2 -5
  129. pygsti/modelmembers/states/composedstate.py +0 -317
  130. pygsti/modelmembers/states/computationalstate.py +3 -3
  131. pygsti/modelmembers/states/cptpstate.py +4 -4
  132. pygsti/modelmembers/states/densestate.py +6 -4
  133. pygsti/modelmembers/states/fullpurestate.py +0 -24
  134. pygsti/modelmembers/states/purestate.py +1 -1
  135. pygsti/modelmembers/states/state.py +5 -6
  136. pygsti/modelmembers/states/tpstate.py +28 -10
  137. pygsti/modelmembers/term.py +3 -6
  138. pygsti/modelmembers/torchable.py +50 -0
  139. pygsti/modelpacks/_modelpack.py +1 -1
  140. pygsti/modelpacks/smq1Q_ZN.py +3 -1
  141. pygsti/modelpacks/smq2Q_XXYYII.py +2 -1
  142. pygsti/modelpacks/smq2Q_XY.py +3 -3
  143. pygsti/modelpacks/smq2Q_XYI.py +2 -2
  144. pygsti/modelpacks/smq2Q_XYICNOT.py +3 -3
  145. pygsti/modelpacks/smq2Q_XYICPHASE.py +3 -3
  146. pygsti/modelpacks/smq2Q_XYXX.py +1 -1
  147. pygsti/modelpacks/smq2Q_XYZICNOT.py +3 -3
  148. pygsti/modelpacks/smq2Q_XYZZ.py +1 -1
  149. pygsti/modelpacks/stdtarget.py +0 -121
  150. pygsti/models/cloudnoisemodel.py +1 -2
  151. pygsti/models/explicitcalc.py +3 -3
  152. pygsti/models/explicitmodel.py +3 -13
  153. pygsti/models/fogistore.py +5 -3
  154. pygsti/models/localnoisemodel.py +1 -2
  155. pygsti/models/memberdict.py +0 -12
  156. pygsti/models/model.py +800 -65
  157. pygsti/models/modelconstruction.py +4 -4
  158. pygsti/models/modelnoise.py +2 -2
  159. pygsti/models/modelparaminterposer.py +1 -1
  160. pygsti/models/oplessmodel.py +1 -1
  161. pygsti/models/qutrit.py +15 -14
  162. pygsti/objectivefns/objectivefns.py +73 -138
  163. pygsti/objectivefns/wildcardbudget.py +2 -7
  164. pygsti/optimize/__init__.py +1 -0
  165. pygsti/optimize/arraysinterface.py +28 -0
  166. pygsti/optimize/customcg.py +0 -12
  167. pygsti/optimize/customlm.py +129 -323
  168. pygsti/optimize/customsolve.py +2 -2
  169. pygsti/optimize/optimize.py +0 -84
  170. pygsti/optimize/simplerlm.py +841 -0
  171. pygsti/optimize/wildcardopt.py +19 -598
  172. pygsti/protocols/confidenceregionfactory.py +28 -14
  173. pygsti/protocols/estimate.py +31 -14
  174. pygsti/protocols/gst.py +142 -68
  175. pygsti/protocols/modeltest.py +6 -10
  176. pygsti/protocols/protocol.py +9 -37
  177. pygsti/protocols/rb.py +450 -79
  178. pygsti/protocols/treenode.py +8 -2
  179. pygsti/protocols/vb.py +108 -206
  180. pygsti/protocols/vbdataframe.py +1 -1
  181. pygsti/report/factory.py +0 -15
  182. pygsti/report/fogidiagram.py +1 -17
  183. pygsti/report/modelfunction.py +12 -3
  184. pygsti/report/mpl_colormaps.py +1 -1
  185. pygsti/report/plothelpers.py +8 -2
  186. pygsti/report/reportables.py +41 -37
  187. pygsti/report/templates/offline/pygsti_dashboard.css +6 -0
  188. pygsti/report/templates/offline/pygsti_dashboard.js +12 -0
  189. pygsti/report/workspace.py +2 -14
  190. pygsti/report/workspaceplots.py +326 -504
  191. pygsti/tools/basistools.py +9 -36
  192. pygsti/tools/edesigntools.py +124 -96
  193. pygsti/tools/fastcalc.cp310-win32.pyd +0 -0
  194. pygsti/tools/fastcalc.pyx +35 -81
  195. pygsti/tools/internalgates.py +151 -15
  196. pygsti/tools/jamiolkowski.py +5 -5
  197. pygsti/tools/lindbladtools.py +19 -11
  198. pygsti/tools/listtools.py +0 -114
  199. pygsti/tools/matrixmod2.py +1 -1
  200. pygsti/tools/matrixtools.py +173 -339
  201. pygsti/tools/nameddict.py +1 -1
  202. pygsti/tools/optools.py +154 -88
  203. pygsti/tools/pdftools.py +0 -25
  204. pygsti/tools/rbtheory.py +3 -320
  205. pygsti/tools/slicetools.py +64 -12
  206. pyGSTi-0.9.12.1.dist-info/METADATA +0 -155
  207. pygsti/algorithms/directx.py +0 -711
  208. pygsti/evotypes/qibo/__init__.py +0 -33
  209. pygsti/evotypes/qibo/effectreps.py +0 -78
  210. pygsti/evotypes/qibo/opreps.py +0 -376
  211. pygsti/evotypes/qibo/povmreps.py +0 -98
  212. pygsti/evotypes/qibo/statereps.py +0 -174
  213. pygsti/extras/rb/__init__.py +0 -13
  214. pygsti/extras/rb/benchmarker.py +0 -957
  215. pygsti/extras/rb/dataset.py +0 -378
  216. pygsti/extras/rb/io.py +0 -814
  217. pygsti/extras/rb/simulate.py +0 -1020
  218. pygsti/io/legacyio.py +0 -385
  219. pygsti/modelmembers/povms/denseeffect.py +0 -142
  220. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/LICENSE +0 -0
  221. {pyGSTi-0.9.12.1.dist-info → pyGSTi-0.9.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,841 @@
1
+ """
2
+ Custom implementation of the Levenberg-Marquardt Algorithm (but simpler than customlm.py)
3
+ """
4
+ #***************************************************************************************************
5
+ # Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
6
+ # Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights
7
+ # in this software.
8
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
9
+ # in compliance with the License. You may obtain a copy of the License at
10
+ # http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
11
+ #***************************************************************************************************
12
+
13
+ import os as _os
14
+ import signal as _signal
15
+ import time as _time
16
+
17
+ import numpy as _np
18
+ import scipy as _scipy
19
+
20
+ from pygsti.optimize import arraysinterface as _ari
21
+ from pygsti.optimize.customsolve import custom_solve as _custom_solve
22
+ from pygsti.baseobjs.verbosityprinter import VerbosityPrinter as _VerbosityPrinter
23
+ from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation
24
+ from pygsti.baseobjs.nicelyserializable import NicelySerializable as _NicelySerializable
25
+ from pygsti.objectivefns.objectivefns import Chi2Function, TimeIndependentMDCObjectiveFunction
26
+ from typing import Callable
27
+
28
+ #Make sure SIGINT will generate a KeyboardInterrupt (even if we're launched in the background)
29
+ #This may be problematic for multithreaded parallelism above pyGSTi, e.g. Dask,
30
+ #so this can be turned off by setting the PYGSTI_NO_CUSTOMLM_SIGINT environment variable
31
+ if 'PYGSTI_NO_CUSTOMLM_SIGINT' not in _os.environ:
32
+ _signal.signal(_signal.SIGINT, _signal.default_int_handler)
33
+
34
+ #constants
35
+ _MACH_PRECISION = 1e-12
36
+
37
+
38
+ class OptimizerResult(object):
39
+ """
40
+ The result from an optimization.
41
+
42
+ Parameters
43
+ ----------
44
+ objective_func : ObjectiveFunction
45
+ The objective function that was optimized.
46
+
47
+ opt_x : numpy.ndarray
48
+ The optimal argument (x) value. Often a vector of parameters.
49
+
50
+ opt_f : numpy.ndarray
51
+ the optimal objective function (f) value. Often this is the least-squares
52
+ vector of objective function values.
53
+
54
+ opt_jtj : numpy.ndarray, optional
55
+ the optimial `dot(transpose(J),J)` value, where `J`
56
+ is the Jacobian matrix. This may be useful for computing
57
+ approximate error bars.
58
+
59
+ opt_unpenalized_f : numpy.ndarray, optional
60
+ the optimal objective function (f) value with any
61
+ penalty terms removed.
62
+
63
+ chi2_k_distributed_qty : float, optional
64
+ a value that is supposed to be chi2_k distributed.
65
+
66
+ optimizer_specific_qtys : dict, optional
67
+ a dictionary of additional optimization parameters.
68
+ """
69
+ def __init__(self, objective_func, opt_x, opt_f=None, opt_jtj=None,
70
+ opt_unpenalized_f=None, chi2_k_distributed_qty=None,
71
+ optimizer_specific_qtys=None):
72
+ self.objective_func = objective_func
73
+ self.x = opt_x
74
+ self.f = opt_f
75
+ self.jtj = opt_jtj # jacobian.T * jacobian
76
+ self.f_no_penalties = opt_unpenalized_f
77
+ self.optimizer_specific_qtys = optimizer_specific_qtys
78
+ self.chi2_k_distributed_qty = chi2_k_distributed_qty
79
+
80
+
81
+ class Optimizer(_NicelySerializable):
82
+ """
83
+ An optimizer. Optimizes an objective function.
84
+ """
85
+
86
+ @classmethod
87
+ def cast(cls, obj):
88
+ """
89
+ Cast `obj` to a :class:`Optimizer`.
90
+
91
+ If `obj` is already an `Optimizer` it is just returned,
92
+ otherwise this function tries to create a new object
93
+ using `obj` as a dictionary of constructor arguments.
94
+
95
+ Parameters
96
+ ----------
97
+ obj : Optimizer or dict
98
+ The object to cast.
99
+
100
+ Returns
101
+ -------
102
+ Optimizer
103
+ """
104
+ if isinstance(obj, cls):
105
+ return obj
106
+ else:
107
+ return cls(**obj) if obj else cls()
108
+
109
+ def __init__(self):
110
+ super().__init__()
111
+
112
+
113
+ class SimplerLMOptimizer(Optimizer):
114
+ """
115
+ A Levenberg-Marquardt optimizer customized for GST-like problems.
116
+
117
+ Parameters
118
+ ----------
119
+ maxiter : int, optional
120
+ The maximum number of (outer) interations.
121
+
122
+ maxfev : int, optional
123
+ The maximum function evaluations.
124
+
125
+ tol : float or dict, optional
126
+ The tolerance, specified as a single float or as a dict
127
+ with keys `{'relx', 'relf', 'jac', 'maxdx'}`. A single
128
+ float sets the `'relf'` and `'jac'` elemments and leaves
129
+ the others at their default values.
130
+
131
+ fditer : int optional
132
+ Internally compute the Jacobian using a finite-difference method
133
+ for the first `fditer` iterations. This is useful when the initial
134
+ point lies at a special or singular point where the analytic Jacobian
135
+ is misleading.
136
+
137
+ first_fditer : int, optional
138
+ Number of finite-difference iterations applied to the first
139
+ stage of the optimization (only). Unused.
140
+
141
+ init_munu : tuple, optional
142
+ If not None, a (mu, nu) tuple of 2 floats giving the initial values
143
+ for mu and nu.
144
+
145
+ oob_check_interval : int, optional
146
+ Every `oob_check_interval` outer iterations, the objective function
147
+ (`obj_fn`) is called with a second argument 'oob_check', set to True.
148
+ In this case, `obj_fn` can raise a ValueError exception to indicate
149
+ that it is Out Of Bounds. If `oob_check_interval` is 0 then this
150
+ check is never performed; if 1 then it is always performed.
151
+
152
+ oob_action : {"reject","stop"}
153
+ What to do when the objective function indicates (by raising a ValueError
154
+ as described above). `"reject"` means the step is rejected but the
155
+ optimization proceeds; `"stop"` means the optimization stops and returns
156
+ as converged at the last known-in-bounds point.
157
+
158
+ oob_check_mode : int, optional
159
+ An advanced option, expert use only. If 0 then the optimization is
160
+ halted as soon as an *attempt* is made to evaluate the function out of bounds.
161
+ If 1 then the optimization is halted only when a would-be *accepted* step
162
+ is out of bounds.
163
+
164
+ serial_solve_proc_threshold : int, optional
165
+ When there are fewer than this many processors, the optimizer will solve linear
166
+ systems serially, using SciPy on a single processor, rather than using a parallelized
167
+ Gaussian Elimination (with partial pivoting) algorithm coded in Python. Since SciPy's
168
+ implementation is more efficient, it's not worth using the parallel version until there
169
+ are many processors to spread the work among.
170
+
171
+ lsvec_mode : {'normal', 'percircuit'}
172
+ Whether the terms used in the least-squares optimization are the "elements" as computed
173
+ by the objective function's `.terms()` and `.lsvec()` methods (`'normal'` mode) or the
174
+ "per-circuit quantities" computed by the objective function's `.percircuit()` and
175
+ `.lsvec_percircuit()` methods (`'percircuit'` mode).
176
+ """
177
+
178
+ @classmethod
179
+ def cast(cls, obj):
180
+ if isinstance(obj, cls):
181
+ return obj
182
+ if obj:
183
+ try:
184
+ return cls(**obj)
185
+ except:
186
+ from pygsti.optimize.customlm import CustomLMOptimizer
187
+ return CustomLMOptimizer(**obj)
188
+ return cls()
189
+
190
+ def __init__(self, maxiter=100, maxfev=100, tol=1e-6, fditer=0, first_fditer=0, init_munu="auto", oob_check_interval=0,
191
+ oob_action="reject", oob_check_mode=0, serial_solve_proc_threshold=100, lsvec_mode="normal"):
192
+
193
+ super().__init__()
194
+ if isinstance(tol, float): tol = {'relx': 1e-8, 'relf': tol, 'f': 1.0, 'jac': tol, 'maxdx': 1.0}
195
+ self.maxiter = maxiter
196
+ self.maxfev = maxfev
197
+ self.tol = tol
198
+ self.fditer = fditer
199
+ self.first_fditer = first_fditer
200
+ self.init_munu = init_munu
201
+ self.oob_check_interval = oob_check_interval
202
+ self.oob_action = oob_action
203
+ self.oob_check_mode = oob_check_mode
204
+ self.array_types = 3 * ('p',) + ('e', 'ep') # see simplish_leastsq fn "-type"s -need to add 'jtj' type
205
+ self.called_objective_methods = ('lsvec', 'dlsvec') # the objective function methods we use (for mem estimate)
206
+ self.serial_solve_proc_threshold = serial_solve_proc_threshold
207
+ self.lsvec_mode = lsvec_mode
208
+
209
+ def _to_nice_serialization(self):
210
+ state = super()._to_nice_serialization()
211
+ state.update({
212
+ 'maximum_iterations': self.maxiter,
213
+ 'maximum_function_evaluations': self.maxfev,
214
+ 'tolerance': self.tol,
215
+ 'number_of_finite_difference_iterations': self.fditer,
216
+ 'number_of_first_stage_finite_difference_iterations': self.first_fditer,
217
+ 'initial_mu_and_nu': self.init_munu,
218
+ 'out_of_bounds_check_interval': self.oob_check_interval,
219
+ 'out_of_bounds_action': self.oob_action,
220
+ 'out_of_bounds_check_mode': self.oob_check_mode,
221
+ 'array_types': self.array_types,
222
+ 'called_objective_function_methods': self.called_objective_methods,
223
+ 'serial_solve_number_of_processors_threshold': self.serial_solve_proc_threshold,
224
+ 'lsvec_mode': self.lsvec_mode
225
+ })
226
+ return state
227
+
228
+ @classmethod
229
+ def _from_nice_serialization(cls, state):
230
+ return cls(maxiter=state['maximum_iterations'],
231
+ maxfev=state['maximum_function_evaluations'],
232
+ tol=state['tolerance'],
233
+ fditer=state['number_of_finite_difference_iterations'],
234
+ first_fditer=state['number_of_first_stage_finite_difference_iterations'],
235
+ init_munu=state['initial_mu_and_nu'],
236
+ oob_check_interval=state['out_of_bounds_check_interval'],
237
+ oob_action=state['out_of_bounds_action'],
238
+ oob_check_mode=state['out_of_bounds_check_mode'],
239
+ serial_solve_proc_threshold=state['serial_solve_number_of_processors_threshold'],
240
+ lsvec_mode=state.get('lsvec_mode', 'normal'))
241
+
242
+ def run(self, objective: TimeIndependentMDCObjectiveFunction, profiler, printer):
243
+
244
+ """
245
+ Perform the optimization.
246
+
247
+ Parameters
248
+ ----------
249
+ objective : ObjectiveFunction
250
+ The objective function to optimize.
251
+
252
+ profiler : Profiler
253
+ A profiler to track resource usage.
254
+
255
+ printer : VerbosityPrinter
256
+ printer to use for sending output to stdout.
257
+ """
258
+ nExtra = objective.ex # number of additional "extra" elements
259
+
260
+ if self.lsvec_mode == 'normal':
261
+ objective_func = objective.lsvec
262
+ jacobian = objective.dlsvec
263
+ nEls = objective.layout.num_elements + nExtra # 'e' for array types
264
+ elif self.lsvec_mode == 'percircuit':
265
+ objective_func = objective.lsvec_percircuit
266
+ jacobian = objective.dlsvec_percircuit
267
+ nEls = objective.layout.num_circuits + nExtra # 'e' for array types
268
+ else:
269
+ raise ValueError("Invalid `lsvec_mode`: %s" % str(self.lsvec_mode))
270
+
271
+ x0 = objective.model.to_vector()
272
+ x_limits = objective.model.parameter_bounds
273
+ # x_limits should be a (num_params, 2)-shaped array, holding on each row the (min, max) values for the
274
+ # corresponding parameter (element of the "x" vector) or `None`. If `None`, then no limits are imposed.
275
+
276
+ # Check memory limit can handle what simplish_leastsq will "allocate"
277
+ nP = len(x0) # 'p' for array types
278
+ objective.resource_alloc.check_can_allocate_memory(3 * nP + nEls + nEls * nP + nP * nP) # see array_types above
279
+
280
+ from ..layouts.distlayout import DistributableCOPALayout as _DL
281
+ if isinstance(objective.layout, _DL):
282
+ ari = _ari.DistributedArraysInterface(objective.layout, self.lsvec_mode, nExtra)
283
+ else:
284
+ ari = _ari.UndistributedArraysInterface(nEls, nP)
285
+
286
+ opt_x, converged, msg, mu, nu, norm_f, f = simplish_leastsq(
287
+ objective_func, jacobian, x0,
288
+ max_iter=self.maxiter,
289
+ num_fd_iters=self.fditer,
290
+ f_norm2_tol=self.tol.get('f', 1.0),
291
+ jac_norm_tol=self.tol.get('jac', 1e-6),
292
+ rel_ftol=self.tol.get('relf', 1e-6),
293
+ rel_xtol=self.tol.get('relx', 1e-8),
294
+ max_dx_scale=self.tol.get('maxdx', 1.0),
295
+ init_munu=self.init_munu,
296
+ oob_check_interval=self.oob_check_interval,
297
+ oob_action=self.oob_action,
298
+ oob_check_mode=self.oob_check_mode,
299
+ resource_alloc=objective.resource_alloc,
300
+ arrays_interface=ari,
301
+ serial_solve_proc_threshold=self.serial_solve_proc_threshold,
302
+ x_limits=x_limits,
303
+ verbosity=printer - 1, profiler=profiler)
304
+
305
+ printer.log("Least squares message = %s" % msg, 2)
306
+ assert(converged), "Failed to converge: %s" % msg
307
+ current_v = objective.model.to_vector()
308
+ if not _np.allclose(current_v, opt_x): # ensure the last model evaluation was at opt_x
309
+ objective_func(opt_x)
310
+ #objective.model.from_vector(opt_x) # performed within line above
311
+
312
+ #DEBUG CHECK SYNC between procs (especially for shared mem) - could REMOVE
313
+ # if objective.resource_alloc.comm is not None:
314
+ # comm = objective.resource_alloc.comm
315
+ # v_cmp = comm.bcast(objective.model.to_vector() if (comm.Get_rank() == 0) else None, root=0)
316
+ # v_matches_x = _np.allclose(objective.model.to_vector(), opt_x)
317
+ # same_as_root = _np.isclose(_np.linalg.norm(objective.model.to_vector() - v_cmp), 0.0)
318
+ # if not (v_matches_x and same_as_root):
319
+ # raise ValueError("Rank %d CUSTOMLM ERROR: END model vector-matches-x=%s and vector-is-same-as-root=%s"
320
+ # % (comm.rank, str(v_matches_x), str(same_as_root)))
321
+ # comm.barrier() # if we get past here, then *all* processors are OK
322
+ # if comm.rank == 0:
323
+ # print("OK - model vector == best_x and all vectors agree w/root proc's")
324
+
325
+ unpenalized_f = f[0:-objective.ex] if (objective.ex > 0) else f
326
+ unpenalized_normf = sum(unpenalized_f**2) # objective function without penalty factors
327
+ chi2k_qty = objective.chi2k_distributed_qty(norm_f)
328
+ optimizer_specific_qtys = {'msg': msg, 'mu': mu, 'nu': nu, 'fvec': f}
329
+ return OptimizerResult(objective, opt_x, norm_f, None, unpenalized_normf, chi2k_qty, optimizer_specific_qtys)
330
+
331
+
332
+
333
+ def damp_coeff_update(mu, nu, half_max_nu, reject_msg, printer):
334
+ ############################################################################################
335
+ #
336
+ # if this point is reached, either the linear solve failed
337
+ # or the error did not reduce. In either case, reject increment.
338
+ #
339
+ ############################################################################################
340
+ mu *= nu
341
+ if nu > half_max_nu: # watch for nu getting too large (&overflow)
342
+ msg = "Stopping after nu overflow!"
343
+ else:
344
+ msg = ""
345
+ nu = 2 * nu
346
+ printer.log(" Rejected%s! mu => mu*nu = %g, nu => 2*nu = %g" % (reject_msg, mu, nu), 2)
347
+ return mu, nu, msg
348
+
349
+
350
+ def jac_guarded(k: int, num_fd_iters: int, obj_fn: Callable, jac_fn: Callable, f, ari, global_x, fdJac_work):
351
+ if k >= num_fd_iters:
352
+ Jac = jac_fn(global_x) # 'EP'-type, but doesn't actually allocate any more mem (!)
353
+ else:
354
+ # Note: x holds only number of "fine"-division params - need to use global_x, and
355
+ # Jac only holds a subset of the derivative and element columns and rows, respectively.
356
+ f_fixed = f.copy() # a static part of the distributed `f` resturned by obj_fn - MUST copy this.
357
+
358
+ pslice = ari.jac_param_slice(only_if_leader=True)
359
+ eps = 1e-7
360
+ #Don't do this: for ii, i in enumerate(range(pslice.start, pslice.stop)): (must keep procs in sync)
361
+ for i in range(len(global_x)):
362
+ x_plus_dx = global_x.copy()
363
+ x_plus_dx[i] += eps
364
+ fd = (obj_fn(x_plus_dx) - f_fixed) / eps
365
+ if pslice.start <= i < pslice.stop:
366
+ fdJac_work[:, i - pslice.start] = fd
367
+ #if comm is not None: comm.barrier() # overkill for shared memory leader host barrier
368
+ Jac = fdJac_work
369
+ return Jac
370
+
371
+
372
+
373
+ def simplish_leastsq(
374
+ obj_fn, jac_fn, x0, f_norm2_tol=1e-6, jac_norm_tol=1e-6,
375
+ rel_ftol=1e-6, rel_xtol=1e-6, max_iter=100, num_fd_iters=0, max_dx_scale=1.0,
376
+ init_munu="auto", oob_check_interval=0, oob_action="reject", oob_check_mode=0,
377
+ resource_alloc=None, arrays_interface=None, serial_solve_proc_threshold=100,
378
+ x_limits=None, verbosity=0, profiler=None
379
+ ):
380
+ """
381
+ An implementation of the Levenberg-Marquardt least-squares optimization algorithm customized for use within pyGSTi.
382
+
383
+ This general purpose routine mimic to a large extent the interface used by
384
+ `scipy.optimize.leastsq`, though it implements a newer (and more robust) version
385
+ of the algorithm.
386
+
387
+ Parameters
388
+ ----------
389
+ obj_fn : function
390
+ The objective function. Must accept and return 1D numpy ndarrays of
391
+ length N and M respectively. Same form as scipy.optimize.leastsq.
392
+
393
+ jac_fn : function
394
+ The jacobian function (not optional!). Accepts a 1D array of length N
395
+ and returns an array of shape (M,N).
396
+
397
+ x0 : numpy.ndarray
398
+ Initial evaluation point.
399
+
400
+ f_norm2_tol : float, optional
401
+ Tolerace for `F^2` where `F = `norm( sum(obj_fn(x)**2) )` is the
402
+ least-squares residual. If `F**2 < f_norm2_tol`, then mark converged.
403
+
404
+ jac_norm_tol : float, optional
405
+ Tolerance for jacobian norm, namely if `infn(dot(J.T,f)) < jac_norm_tol`
406
+ then mark converged, where `infn` is the infinity-norm and
407
+ `f = obj_fn(x)`.
408
+
409
+ rel_ftol : float, optional
410
+ Tolerance on the relative reduction in `F^2`, that is, if
411
+ `d(F^2)/F^2 < rel_ftol` then mark converged.
412
+
413
+ rel_xtol : float, optional
414
+ Tolerance on the relative value of `|x|`, so that if
415
+ `d(|x|)/|x| < rel_xtol` then mark converged.
416
+
417
+ max_iter : int, optional
418
+ The maximum number of (outer) interations.
419
+
420
+ num_fd_iters : int optional
421
+ Internally compute the Jacobian using a finite-difference method
422
+ for the first `num_fd_iters` iterations. This is useful when `x0`
423
+ lies at a special or singular point where the analytic Jacobian is
424
+ misleading.
425
+
426
+ max_dx_scale : float, optional
427
+ If not None, impose a limit on the magnitude of the step, so that
428
+ `|dx|^2 < max_dx_scale^2 * len(dx)` (so elements of `dx` should be,
429
+ roughly, less than `max_dx_scale`).
430
+
431
+ init_munu : tuple, optional
432
+ If not None, a (mu, nu) tuple of 2 floats giving the initial values
433
+ for mu and nu.
434
+
435
+ oob_check_interval : int, optional
436
+ Every `oob_check_interval` outer iterations, the objective function
437
+ (`obj_fn`) is called with a second argument 'oob_check', set to True.
438
+ In this case, `obj_fn` can raise a ValueError exception to indicate
439
+ that it is Out Of Bounds. If `oob_check_interval` is 0 then this
440
+ check is never performed; if 1 then it is always performed.
441
+
442
+ oob_action : {"reject","stop"}
443
+ What to do when the objective function indicates (by raising a ValueError
444
+ as described above). `"reject"` means the step is rejected but the
445
+ optimization proceeds; `"stop"` means the optimization stops and returns
446
+ as converged at the last known-in-bounds point.
447
+
448
+ oob_check_mode : int, optional
449
+ An advanced option, expert use only. If 0 then the optimization is
450
+ halted as soon as an *attempt* is made to evaluate the function out of bounds.
451
+ If 1 then the optimization is halted only when a would-be *accepted* step
452
+ is out of bounds.
453
+
454
+ resource_alloc : ResourceAllocation, optional
455
+ When not None, an resource allocation object used for distributing the computation
456
+ across multiple processors.
457
+
458
+ arrays_interface : ArraysInterface
459
+ An object that provides an interface for creating and manipulating data arrays.
460
+
461
+ serial_solve_proc_threshold : int optional
462
+ When there are fewer than this many processors, the optimizer will solve linear
463
+ systems serially, using SciPy on a single processor, rather than using a parallelized
464
+ Gaussian Elimination (with partial pivoting) algorithm coded in Python. Since SciPy's
465
+ implementation is more efficient, it's not worth using the parallel version until there
466
+ are many processors to spread the work among.
467
+
468
+ x_limits : numpy.ndarray, optional
469
+ A (num_params, 2)-shaped array, holding on each row the (min, max) values for the corresponding
470
+ parameter (element of the "x" vector). If `None`, then no limits are imposed.
471
+
472
+ verbosity : int, optional
473
+ Amount of detail to print to stdout.
474
+
475
+ profiler : Profiler, optional
476
+ A profiler object used for to track timing and memory usage.
477
+
478
+ Returns
479
+ -------
480
+ x : numpy.ndarray
481
+ The optimal solution.
482
+ converged : bool
483
+ Whether the solution converged.
484
+ msg : str
485
+ A message indicating why the solution converged (or didn't).
486
+ """
487
+ resource_alloc = _ResourceAllocation.cast(resource_alloc)
488
+ comm = resource_alloc.comm
489
+ printer = _VerbosityPrinter.create_printer(verbosity, comm)
490
+ ari = arrays_interface # shorthand
491
+
492
+ msg = ""
493
+ converged = False
494
+ half_max_nu = 2**62 # what should this be??
495
+ tau = 1e-3
496
+
497
+ #Allocate potentially shared memory used in loop
498
+ JTJ = ari.allocate_jtj()
499
+ minus_JTf = ari.allocate_jtf()
500
+ x = ari.allocate_jtf()
501
+ best_x = ari.allocate_jtf()
502
+ dx = ari.allocate_jtf()
503
+ new_x = ari.allocate_jtf()
504
+ optional_jtj_buff = ari.allocate_jtj_shared_mem_buf()
505
+ fdJac = ari.allocate_jac() if num_fd_iters > 0 else None
506
+
507
+ global_x = x0.copy()
508
+ ari.allscatter_x(global_x, x)
509
+ global_new_x = global_x.copy()
510
+ best_x[:] = x[:]
511
+ # ^ like x.copy() -the x-value corresponding to min_norm_f ('P'-type)
512
+
513
+ if x_limits is not None:
514
+ x_lower_limits = ari.allocate_jtf()
515
+ x_upper_limits = ari.allocate_jtf()
516
+ ari.allscatter_x(x_limits[:, 0], x_lower_limits)
517
+ ari.allscatter_x(x_limits[:, 1], x_upper_limits)
518
+ max_norm_dx = (max_dx_scale**2) * len(global_x) if max_dx_scale else None
519
+ # ^ don't let any component change by more than ~max_dx_scale
520
+
521
+
522
+ f = obj_fn(global_x) # 'E'-type array
523
+ norm_f = ari.norm2_f(f)
524
+ if not _np.isfinite(norm_f):
525
+ msg = "Infinite norm of objective function at initial point!"
526
+
527
+ if len(global_x) == 0: # a model with 0 parameters - nothing to optimize
528
+ msg = "No parameters to optimize"
529
+ converged = True
530
+
531
+ mu, nu = (1, 2) if init_munu == 'auto' else init_munu
532
+ # ^ We have to set some *some* values in case we exit at the start of the first
533
+ # iteration. mu will almost certainly be overwritten before being read.
534
+ min_norm_f = 1e100 # sentinel
535
+ best_x_state = (mu, nu, norm_f, f.copy())
536
+ # ^ here and elsewhere, need f.copy() b/c f is objfn mem
537
+
538
+ try:
539
+
540
+ for k in range(max_iter): # outer loop
541
+ # assume global_x, x, f, fnorm hold valid values
542
+
543
+ if len(msg) > 0:
544
+ break # exit outer loop if an exit-message has been set
545
+
546
+ if norm_f < f_norm2_tol:
547
+ if oob_check_interval <= 1:
548
+ msg = "Sum of squares is at most %g" % f_norm2_tol
549
+ converged = True
550
+ break
551
+ else:
552
+ printer.log(("** Converged with out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
553
+ oob_check_interval = 1
554
+ x[:] = best_x[:]
555
+ mu, nu, norm_f, f[:] = best_x_state
556
+ continue
557
+
558
+ if profiler: profiler.memory_check("simplish_leastsq: begin outer iter")
559
+
560
+ Jac = jac_guarded(k, num_fd_iters, obj_fn, jac_fn, f, ari, global_x, fdJac)
561
+
562
+ if profiler:
563
+ jac_gb = Jac.nbytes/(1024.0**3) if hasattr(Jac, 'nbytes') else _np.NaN
564
+ vals = ((f.size, global_x.size), jac_gb)
565
+ profiler.memory_check("simplish_leastsq: after jacobian: shape=%s, GB=%.2f" % vals)
566
+
567
+ Jnorm = _np.sqrt(ari.norm2_jac(Jac))
568
+ xnorm = _np.sqrt(ari.norm2_x(x))
569
+ printer.log("--- Outer Iter %d: norm_f = %g, mu=%g, |x|=%g, |J|=%g" % (k, norm_f, mu, xnorm, Jnorm))
570
+
571
+ tm = _time.time()
572
+
573
+ # Riley note: fill_JTJ is the first place where we try to access J as a dense matrix.
574
+ ari.fill_jtj(Jac, JTJ, optional_jtj_buff)
575
+ ari.fill_jtf(Jac, f, minus_JTf) # 'P'-type
576
+ minus_JTf *= -1
577
+
578
+ if profiler: profiler.add_time("simplish_leastsq: dotprods", tm)
579
+
580
+ norm_JTf = ari.infnorm_x(minus_JTf)
581
+ norm_x = ari.norm2_x(x)
582
+ pre_reg_data = ari.jtj_pre_regularization_data(JTJ)
583
+
584
+ if norm_JTf < jac_norm_tol:
585
+ if oob_check_interval <= 1:
586
+ msg = "norm(jacobian) is at most %g" % jac_norm_tol
587
+ converged = True
588
+ break
589
+ else:
590
+ printer.log(("** Converged with out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
591
+ oob_check_interval = 1
592
+ x[:] = best_x[:]
593
+ mu, nu, norm_f, f[:] = best_x_state
594
+ continue
595
+
596
+ if k == 0:
597
+ max_jtj_diag = ari.jtj_max_diagonal_element(JTJ)
598
+ mu, nu = (tau * max_jtj_diag, 2) if init_munu == 'auto' else init_munu
599
+ best_x_state = (mu, nu, norm_f, f.copy())
600
+
601
+ #determing increment using adaptive damping
602
+ while True: # inner loop
603
+
604
+ if profiler: profiler.memory_check("simplish_leastsq: begin inner iter")
605
+
606
+ # ok if assume fine-param-proc.size == 1 (otherwise need to sync setting local JTJ)
607
+ ari.jtj_update_regularization(JTJ, pre_reg_data, mu)
608
+
609
+ #assert(_np.isfinite(JTJ).all()), "Non-finite JTJ (inner)!" # NaNs tracking
610
+ #assert(_np.isfinite(minus_JTf).all()), "Non-finite minus_JTf (inner)!" # NaNs tracking
611
+
612
+ try:
613
+ if profiler: profiler.memory_check("simplish_leastsq: before linsolve")
614
+ tm = _time.time()
615
+ _custom_solve(JTJ, minus_JTf, dx, ari, resource_alloc, serial_solve_proc_threshold)
616
+ if profiler: profiler.add_time("simplish_leastsq: linsolve", tm)
617
+ except _scipy.linalg.LinAlgError:
618
+ reject_msg = " (LinSolve Failure)"
619
+ mu, nu, msg = damp_coeff_update(mu, nu, half_max_nu, reject_msg, printer)
620
+ if len(msg) == 0:
621
+ continue
622
+ else:
623
+ break
624
+
625
+ reject_msg = ""
626
+ if profiler: profiler.memory_check("simplish_leastsq: after linsolve")
627
+
628
+ new_x[:] = x + dx
629
+ norm_dx = ari.norm2_x(dx)
630
+
631
+ #ensure dx isn't too large - don't let any component change by more than ~max_dx_scale
632
+ if max_norm_dx and norm_dx > max_norm_dx:
633
+ dx *= _np.sqrt(max_norm_dx / norm_dx)
634
+ new_x[:] = x + dx
635
+ norm_dx = ari.norm2_x(dx)
636
+
637
+ #apply x limits (bounds)
638
+ if x_limits is not None:
639
+ # Approach 1: project x into valid space by simply clipping out-of-bounds values
640
+ for i, (x_el, lower, upper) in enumerate(zip(x, x_lower_limits, x_upper_limits)):
641
+ if new_x[i] < lower:
642
+ new_x[i] = lower
643
+ dx[i] = lower - x_el
644
+ elif new_x[i] > upper:
645
+ new_x[i] = upper
646
+ dx[i] = upper - x_el
647
+ norm_dx = ari.norm2_x(dx)
648
+
649
+ printer.log(" - Inner Loop: mu=%g, norm_dx=%g" % (mu, norm_dx), 2)
650
+
651
+ if norm_dx < (rel_xtol**2) * norm_x:
652
+ if oob_check_interval <= 1:
653
+ msg = "Relative change, |dx|/|x|, is at most %g" % rel_xtol
654
+ converged = True
655
+ break
656
+ else:
657
+ printer.log(("** Converged with out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
658
+ oob_check_interval = 1
659
+ x[:] = best_x[:]
660
+ mu, nu, norm_f, f[:] = best_x_state
661
+ break
662
+ elif (norm_x + rel_xtol) < norm_dx * (_MACH_PRECISION**2):
663
+ msg = "(near-)singular linear system"
664
+ break
665
+
666
+ if oob_check_mode == 0 and oob_check_interval > 0:
667
+ if k % oob_check_interval == 0:
668
+ #Check to see if objective function is out of bounds
669
+
670
+ in_bounds = []
671
+ ari.allgather_x(new_x, global_new_x)
672
+ try:
673
+ new_f = obj_fn(global_new_x, oob_check=True)
674
+ except ValueError: # Use this to mean - "not allowed, but don't stop"
675
+ in_bounds.append(False)
676
+ else:
677
+ in_bounds.append(True)
678
+
679
+ if any(in_bounds): # In adaptive mode, proceed if *any* cases are in-bounds
680
+ new_x_is_known_inbounds = True
681
+ else:
682
+ MIN_STOP_ITER = 1 # the minimum iteration where an OOB objective stops the optimization
683
+ if oob_action == "reject" or k < MIN_STOP_ITER:
684
+ reject_msg = " (out-of-bounds)"
685
+ mu, nu, msg = damp_coeff_update(mu, nu, half_max_nu, reject_msg, printer)
686
+ if len(msg) == 0:
687
+ continue
688
+ else:
689
+ break
690
+ elif oob_action == "stop":
691
+ if oob_check_interval == 1:
692
+ msg = "Objective function out-of-bounds! STOP"
693
+ converged = True
694
+ break
695
+ else: # reset to last know in-bounds point and not do oob check every step
696
+ printer.log(("** Hit out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
697
+ oob_check_interval = 1
698
+ x[:] = best_x[:]
699
+ mu, nu, norm_f, f[:] = best_x_state
700
+ break # restart next outer loop
701
+ else:
702
+ raise ValueError("Invalid `oob_action`: '%s'" % oob_action)
703
+ else: # don't check this time
704
+ ari.allgather_x(new_x, global_new_x)
705
+ new_f = obj_fn(global_new_x, oob_check=False)
706
+ new_x_is_known_inbounds = False
707
+ else:
708
+ #Just evaluate objective function normally; never check for in-bounds condition
709
+ ari.allgather_x(new_x, global_new_x)
710
+ new_f = obj_fn(global_new_x)
711
+ new_x_is_known_inbounds = oob_check_interval == 0
712
+ # ^ assume in bounds if we have no out-of-bounds checks.
713
+
714
+ norm_new_f = ari.norm2_f(new_f)
715
+ if not _np.isfinite(norm_new_f): # avoid infinite loop...
716
+ msg = "Infinite norm of objective function!"
717
+ break
718
+
719
+ # dL = expected decrease in ||F||^2 from linear model
720
+ dL = ari.dot_x(dx, mu * dx + minus_JTf)
721
+ dF = norm_f - norm_new_f # actual decrease in ||F||^2
722
+
723
+ printer.log(" (cont): norm_new_f=%g, dL=%g, dF=%g, reldL=%g, reldF=%g" % (norm_new_f, dL, dF, dL / norm_f, dF / norm_f), 2)
724
+
725
+ if dL / norm_f < rel_ftol and dF >= 0 and dF / norm_f < rel_ftol and dF / dL < 2.0:
726
+ if oob_check_interval <= 1: # (if 0 then no oob checking is done)
727
+ msg = "Both actual and predicted relative reductions in the sum of squares are at most %g" % rel_ftol
728
+ converged = True
729
+ break
730
+ else:
731
+ printer.log(("** Converged with out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
732
+ oob_check_interval = 1
733
+ x[:] = best_x[:]
734
+ mu, nu, norm_f, f[:] = best_x_state
735
+ break
736
+
737
+ if (dL <= 0 or dF <= 0):
738
+ reject_msg = " (out-of-bounds)"
739
+ mu, nu, msg = damp_coeff_update(mu, nu, half_max_nu, reject_msg, printer)
740
+ if len(msg) == 0:
741
+ continue
742
+ else:
743
+ break
744
+
745
+ #Check whether an otherwise acceptable solution is in-bounds
746
+ if oob_check_mode == 1 and oob_check_interval > 0 and k % oob_check_interval == 0:
747
+ #Check to see if objective function is out of bounds
748
+ try:
749
+ obj_fn(global_new_x, oob_check=True) # don't actually need return val (== new_f)
750
+ new_x_is_known_inbounds = True
751
+ except ValueError: # Use this to mean - "not allowed, but don't stop"
752
+ MIN_STOP_ITER = 1 # the minimum iteration where an OOB objective can stops the opt.
753
+ if oob_action == "reject" or k < MIN_STOP_ITER:
754
+ reject_msg = " (out-of-bounds)"
755
+ mu, nu, msg = damp_coeff_update(mu, nu, half_max_nu, reject_msg, printer)
756
+ if len(msg) == 0:
757
+ continue
758
+ else:
759
+ break
760
+ elif oob_action == "stop":
761
+ if oob_check_interval == 1:
762
+ msg = "Objective function out-of-bounds! STOP"
763
+ converged = True
764
+ break
765
+ else: # reset to last know in-bounds point and not do oob check every step
766
+ printer.log(("** Hit out-of-bounds with check interval=%d, reverting to last know in-bounds point and setting interval=1 **") % oob_check_interval, 2)
767
+ oob_check_interval = 1
768
+ x[:] = best_x[:]
769
+ mu, nu, norm_f, f[:] = best_x_state
770
+ break # restart next outer loop
771
+ else:
772
+ raise ValueError("Invalid `oob_action`: '%s'" % oob_action)
773
+
774
+ # reduction in error: increment accepted!
775
+ # ^ Note: if we ever reach this line, then we know that we'll be breaking from the loop.
776
+ t = 1.0 - (2 * dF / dL - 1.0)**3 # dF/dL == gain ratio
777
+ # always reduce mu for accepted step when |dx| is small
778
+ mu_factor = max(t, 1.0 / 3.0) if norm_dx > 1e-8 else 0.3
779
+ mu *= mu_factor
780
+ nu = 2
781
+ x[:] = new_x[:]
782
+ f[:] = new_f[:]
783
+ norm_f = norm_new_f
784
+ global_x[:] = global_new_x[:]
785
+ printer.log(" Accepted%s! gain ratio=%g mu * %g => %g" % ("", dF / dL, mu_factor, mu), 2)
786
+ if new_x_is_known_inbounds and norm_f < min_norm_f:
787
+ min_norm_f = norm_f
788
+ best_x[:] = x[:]
789
+ best_x_state = (mu, nu, norm_f, f.copy())
790
+
791
+ #assert(_np.isfinite(x).all()), "Non-finite x!" # NaNs tracking
792
+ #assert(_np.isfinite(f).all()), "Non-finite f!" # NaNs tracking
793
+
794
+ break
795
+ # ^ exit inner loop normally ...
796
+ # end of inner loop
797
+ # end of outer loop
798
+ else:
799
+ #if no break stmt hit, then we've exceeded max_iter
800
+ msg = "Maximum iterations (%d) exceeded" % max_iter
801
+ converged = True # call result "converged" even in this case, but issue warning:
802
+ printer.warning("Treating result as *converged* after maximum iterations (%d) were exceeded." % max_iter)
803
+
804
+ except KeyboardInterrupt:
805
+ if comm is not None:
806
+ # ensure all procs agree on what best_x is (in case the interrupt occurred around x being updated)
807
+ comm.Bcast(best_x, root=0)
808
+ printer.log("Rank %d caught keyboard interrupt! Returning the current solution as being *converged*."
809
+ % comm.Get_rank())
810
+ else:
811
+ printer.log("Caught keyboard interrupt! Returning the current solution as being *converged*.")
812
+ msg = "Keyboard interrupt!"
813
+ converged = True
814
+
815
+ if comm is not None:
816
+ comm.barrier() # Just to be safe, so procs stay synchronized and we don't free anything too soon
817
+
818
+ ari.deallocate_jtj(JTJ)
819
+ ari.deallocate_jtf(minus_JTf)
820
+ ari.deallocate_jtf(x)
821
+ ari.deallocate_jtj_shared_mem_buf(optional_jtj_buff)
822
+
823
+ if x_limits is not None:
824
+ ari.deallocate_jtf(x_lower_limits)
825
+ ari.deallocate_jtf(x_upper_limits)
826
+
827
+ ari.deallocate_jtf(dx)
828
+ ari.deallocate_jtf(new_x)
829
+
830
+ if fdJac is not None:
831
+ ari.deallocate_jac(fdJac)
832
+
833
+ ari.allgather_x(best_x, global_x)
834
+ ari.deallocate_jtf(best_x)
835
+
836
+ mu, nu, norm_f, f[:] = best_x_state
837
+
838
+ global_f = _np.empty(ari.global_num_elements(), 'd')
839
+ ari.allgather_f(f, global_f)
840
+
841
+ return global_x, converged, msg, mu, nu, norm_f, global_f