/
newton.py
279 lines (218 loc) · 9.08 KB
/
newton.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
"""Define the NewtonSolver class."""
import numpy as np
from openmdao.solvers.linesearch.backtracking import BoundsEnforceLS
from openmdao.solvers.solver import NonlinearSolver
from openmdao.recorders.recording_iteration_stack import Recording
from openmdao.utils.mpi import MPI
class NewtonSolver(NonlinearSolver):
"""
Newton solver.
The default linear solver is the linear_solver in the containing system.
Parameters
----------
**kwargs : dict
Options dictionary.
Attributes
----------
linear_solver : LinearSolver
Linear solver to use to find the Newton search direction. The default
is the parent system's linear solver.
_linesearch : NonlinearSolver
Line search algorithm. Default is None for no line search.
"""
SOLVER = 'NL: Newton'
def __init__(self, **kwargs):
"""
Initialize all attributes.
"""
super().__init__(**kwargs)
# Slot for linear solver
self.linear_solver = None
# Slot for linesearch
self.supports['linesearch'] = True
self._linesearch = BoundsEnforceLS()
def _declare_options(self):
"""
Declare options before kwargs are processed in the init method.
"""
super()._declare_options()
self.options.declare('solve_subsystems', types=bool,
desc='Set to True to turn on sub-solvers (Hybrid Newton).')
self.options.declare('max_sub_solves', types=int, default=10,
desc='Maximum number of subsystem solves.')
self.options.declare('cs_reconverge', types=bool, default=True,
desc='When True, when this driver solves under a complex step, nudge '
'the Solution vector by a small amount so that it reconverges.')
self.options.declare('reraise_child_analysiserror', types=bool, default=False,
desc='When the option is true, a solver will reraise any '
'AnalysisError that arises during subsolve; when false, it will '
'continue solving.')
self.supports['gradients'] = True
self.supports['implicit_components'] = True
def _setup_solvers(self, system, depth):
"""
Assign system instance, set depth, and optionally perform setup.
Parameters
----------
system : System
pointer to the owning system.
depth : int
depth of the current system (already incremented).
"""
super()._setup_solvers(system, depth)
self._disallow_discrete_outputs()
if not isinstance(self.options._dict['solve_subsystems']['val'], bool):
msg = '{}: solve_subsystems must be set by the user.'
raise ValueError(msg.format(self.msginfo))
if self.linear_solver is not None:
self.linear_solver._setup_solvers(system, self._depth + 1)
else:
self.linear_solver = system.linear_solver
if self.linesearch is not None:
self.linesearch._setup_solvers(system, self._depth + 1)
def _assembled_jac_solver_iter(self):
"""
Return a generator of linear solvers using assembled jacs.
"""
if self.linear_solver is not None:
for s in self.linear_solver._assembled_jac_solver_iter():
yield s
def _set_solver_print(self, level=2, type_='all'):
"""
Control printing for solvers and subsolvers in the model.
Parameters
----------
level : int
iprint level. Set to 2 to print residuals each iteration; set to 1
to print just the iteration totals; set to 0 to disable all printing
except for failures, and set to -1 to disable all printing including failures.
type_ : str
Type of solver to set: 'LN' for linear, 'NL' for nonlinear, or 'all' for all.
"""
super()._set_solver_print(level=level, type_=type_)
if self.linear_solver is not None and type_ != 'NL':
self.linear_solver._set_solver_print(level=level, type_=type_)
if self.linesearch is not None:
self.linesearch._set_solver_print(level=level, type_=type_)
def _run_apply(self):
"""
Run the apply_nonlinear method on the system.
"""
self._recording_iter.push(('_run_apply', 0))
system = self._system()
# Disable local fd
approx_status = system._owns_approx_jac
system._owns_approx_jac = False
try:
system._apply_nonlinear()
finally:
self._recording_iter.pop()
# Enable local fd
system._owns_approx_jac = approx_status
def _linearize_children(self):
"""
Return a flag that is True when we need to call linearize on our subsystems' solvers.
Returns
-------
bool
Flag for indicating child linerization
"""
return (self.options['solve_subsystems'] and not system.under_complex_step
and self._iter_count <= self.options['max_sub_solves'])
def _linearize(self):
"""
Perform any required linearization operations such as matrix factorization.
"""
if self.linear_solver is not None:
self.linear_solver._linearize()
if self.linesearch is not None:
self.linesearch._linearize()
def _iter_initialize(self):
"""
Perform any necessary pre-processing operations.
Returns
-------
float
initial error.
float
error at the first iteration.
"""
system = self._system()
solve_subsystems = self.options['solve_subsystems'] and not system.under_complex_step
if self.options['debug_print']:
self._err_cache['inputs'] = system._inputs._copy_views()
self._err_cache['outputs'] = system._outputs._copy_views()
# Execute guess_nonlinear if specified and
# we have not restarted from a saved point
if not self._restarted:
system._guess_nonlinear()
with Recording('Newton_subsolve', 0, self) as rec:
if solve_subsystems and self._iter_count <= self.options['max_sub_solves']:
self._solver_info.append_solver()
# should call the subsystems solve before computing the first residual
self._gs_iter()
self._solver_info.pop()
self._run_apply()
norm = self._iter_get_norm()
rec.abs = norm
norm0 = norm if norm != 0.0 else 1.0
rec.rel = norm / norm0
return norm0, norm
def _single_iteration(self):
"""
Perform the operations in the iteration loop.
"""
system = self._system()
self._solver_info.append_subsolver()
do_subsolve = self.options['solve_subsystems'] and not system.under_complex_step and \
(self._iter_count < self.options['max_sub_solves'])
do_sub_ln = self.linear_solver._linearize_children()
# Disable local fd
approx_status = system._owns_approx_jac
system._owns_approx_jac = False
try:
system._dresiduals.set_vec(system._residuals)
system._dresiduals *= -1.0
my_asm_jac = self.linear_solver._assembled_jac
system._linearize(my_asm_jac, sub_do_ln=do_sub_ln)
if (my_asm_jac is not None and system.linear_solver._assembled_jac is not my_asm_jac):
my_asm_jac._update(system)
self._linearize()
self.linear_solver.solve('fwd')
if self.linesearch and not system.under_complex_step:
self.linesearch._do_subsolve = do_subsolve
self.linesearch.solve()
else:
system._outputs += system._doutputs
self._solver_info.pop()
# Hybrid newton support.
if do_subsolve:
with Recording('Newton_subsolve', 0, self):
self._solver_info.append_solver()
self._gs_iter()
self._solver_info.pop()
finally:
# Enable local fd
system._owns_approx_jac = approx_status
def _set_complex_step_mode(self, active):
"""
Turn on or off complex stepping mode.
Recurses to turn on or off complex stepping mode in all subsystems and their vectors.
Parameters
----------
active : bool
Complex mode flag; set to True prior to commencing complex step.
"""
if self.linear_solver is not None:
self.linear_solver._set_complex_step_mode(active)
if self.linear_solver._assembled_jac is not None:
self.linear_solver._assembled_jac.set_complex_step_mode(active)
def cleanup(self):
"""
Clean up resources prior to exit.
"""
super().cleanup()
if self.linear_solver:
self.linear_solver.cleanup()
if self.linesearch:
self.linesearch.cleanup()