Source code for openmdao.solvers.nonlinear.newton

"""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

[docs]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'
[docs] def __init__(self, **kwargs): """ Initialize all attributes. """ super().__init__(**kwargs) # Slot for linear solver self.linear_solver = None # Slot for linesearch 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) rank = MPI.COMM_WORLD.rank if MPI is not None else 0 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 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() if self.options['debug_print']: self._err_cache['inputs'] = system._inputs._copy_views() self._err_cache['outputs'] = system._outputs._copy_views() # When under a complex step from higher in the hierarchy, sometimes the step is too small # to trigger reconvergence, so nudge the outputs slightly so that we always get at least # one iteration of Newton. if system.under_complex_step and self.options['cs_reconverge']: system._outputs += np.linalg.norm(system._outputs.asarray()) * 1e-10 # Execute guess_nonlinear if specified. system._guess_nonlinear() with Recording('Newton_subsolve', 0, self) as rec: if self.options['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 \ (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 system._vectors['residual']['linear'].set_vec(system._residuals) system._vectors['residual']['linear'] *= -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: self.linesearch._do_subsolve = do_subsolve self.linesearch.solve() else: system._outputs += system._vectors['output']['linear'] 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() # 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)
[docs] 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()