Source code for openmdao.solvers.linear.linear_block_gs
"""Define the LinearBlockGS class."""
import numpy as np
from openmdao.solvers.solver import BlockLinearSolver
[docs]
class LinearBlockGS(BlockLinearSolver):
    """
    Linear block Gauss-Seidel solver.
    Parameters
    ----------
    **kwargs : dict
        Options dictionary.
    Attributes
    ----------
    _delta_d_n_1 : ndarray
        Cached change in the d_output vectors for the previous iteration. Only used if the
        aitken acceleration option is turned on.
    _theta_n_1 : float
        Cached relaxation factor from previous iteration. Only used if the aitken acceleration
        option is turned on.
    """
    SOLVER = 'LN: LNBGS'
[docs]
    def __init__(self, **kwargs):
        """
        Initialize all attributes.
        """
        super().__init__(**kwargs)
        self._theta_n_1 = None
        self._delta_d_n_1 = None 
    def _declare_options(self):
        """
        Declare options before kwargs are processed in the init method.
        """
        super()._declare_options()
        self.options.declare('use_aitken', types=bool, default=False,
                             desc='set to True to use Aitken relaxation')
        self.options.declare('aitken_min_factor', default=0.1,
                             desc='lower limit for Aitken relaxation factor')
        self.options.declare('aitken_max_factor', default=1.5,
                             desc='upper limit for Aitken relaxation factor')
        self.options.declare('aitken_initial_factor', default=1.0,
                             desc='initial value for Aitken relaxation factor')
    def _iter_initialize(self):
        """
        Perform any necessary pre-processing operations.
        Returns
        -------
        float
            initial error.
        float
            error at the first iteration.
        """
        if self.options['use_aitken']:
            if self._mode == 'fwd':
                self._delta_d_n_1 = self._system()._doutputs.asarray(copy=True)
            else:
                self._delta_d_n_1 = self._system()._dresiduals.asarray(copy=True)
            self._theta_n_1 = 1.0
        return super()._iter_initialize()
    def _single_iteration(self):
        """
        Perform the operations in the iteration loop.
        """
        system = self._system()
        mode = self._mode
        use_aitken = self.options['use_aitken']
        if use_aitken:
            aitken_min_factor = self.options['aitken_min_factor']
            aitken_max_factor = self.options['aitken_max_factor']
            # some variables that are used for Aitken's relaxation
            delta_d_n_1 = self._delta_d_n_1
            theta_n_1 = self._theta_n_1
            # store a copy of the outputs, used to compute the change in outputs later
            if self._mode == 'fwd':
                d_out_vec = system._doutputs
            else:
                d_out_vec = system._dresiduals
            d_n = d_out_vec.asarray(copy=True)
            delta_d_n = d_out_vec.asarray(copy=True)
        relevance = system._relevance
        if mode == 'fwd':
            for subsys in relevance.filter(system._all_subsystem_iter()):
                # must always do the transfer on all procs even if subsys not local
                system._transfer('linear', mode, subsys.name)
                if not subsys._is_local:
                    continue
                b_vec = subsys._dresiduals
                subslice = b_vec._parent_slice
                scope_out, scope_in = system._get_matvec_scope(subsys)
                # we use _union_matvec_scope to combine relevant variables from the current solve
                # with those of the subsystem solve, because for recursive block linear solves
                # we'll be skipping a direct call to _apply_linear and instead counting on
                # _apply_linear to be called once at the bottom of the recursive block linear
                # solve on the component, using the full set of relevant variables from the
                # top group in the block linear solve and all intervening groups (assuming all
                # of those groups are doing block linear solves).
                scope_out = self._union_matvec_scope(self._scope_out, scope_out)
                scope_in = self._union_matvec_scope(self._scope_in, scope_in)
                if subsys._iter_call_apply_linear():
                    subsys._apply_linear(mode, scope_out, scope_in)
                    b_vec *= -1.0
                    b_vec += self._rhs_vec[subslice]
                else:
                    b_vec.set_val(self._rhs_vec[subslice])
                subsys._solve_linear(mode, scope_out, scope_in)
        else:  # rev
            subsystems = list(relevance.filter(system._all_subsystem_iter()))
            subsystems.reverse()
            for subsys in subsystems:
                if subsys._is_local:
                    b_vec = subsys._doutputs
                    subslice = b_vec._parent_slice
                    b_vec.set_val(0.0)
                    system._transfer('linear', mode, subsys.name)
                    b_vec *= -1.0
                    b_vec += self._rhs_vec[subslice]
                    scope_out, scope_in = system._get_matvec_scope(subsys)
                    scope_out = self._union_matvec_scope(self._scope_out, scope_out)
                    scope_in = self._union_matvec_scope(self._scope_in, scope_in)
                    subsys._solve_linear(mode, scope_out, scope_in)
                    if subsys._iter_call_apply_linear():
                        subsys._apply_linear(mode, scope_out, scope_in)
                    else:
                        b_vec.set_val(0.0)
                else:   # subsys not local
                    system._transfer('linear', mode, subsys.name)
        if use_aitken:
            if self._mode == 'fwd':
                d_resid_vec = system._dresiduals
                d_out_vec = system._doutputs
            else:
                d_resid_vec = system._doutputs
                d_out_vec = system._dresiduals
            theta_n = self.options['aitken_initial_factor']
            # compute the change in the outputs after the NLBGS iteration
            delta_d_n -= d_out_vec.asarray()
            delta_d_n *= -1
            if self._iter_count >= 2:
                # Compute relaxation factor. This method is used by Kenway et al. in
                # "Scalable Parallel Approach for High-Fidelity Steady-State Aero-
                # elastic Analysis and Adjoint Derivative Computations" (ln 22 of Algo 1)
                temp = delta_d_n.copy()
                temp -= delta_d_n_1
                # If MPI, piggyback on the residual vector to perform a distributed norm.
                if system.comm.size > 1:
                    backup_r = d_resid_vec.asarray(copy=True)
                    d_resid_vec.set_val(temp)
                    temp_norm = d_resid_vec.get_norm()
                else:
                    temp_norm = np.linalg.norm(temp)
                if temp_norm == 0.:
                    temp_norm = 1e-12  # prevent division by 0 below
                # If MPI, piggyback on the output and residual vectors to perform a distributed
                # dot product.
                if system.comm.size > 1:
                    backup_o = d_out_vec.asarray(copy=True)
                    d_out_vec.set_val(delta_d_n)
                    tddo = d_resid_vec.dot(d_out_vec)
                    d_resid_vec.set_val(backup_r)
                    d_out_vec.set_val(backup_o)
                else:
                    tddo = temp.dot(delta_d_n)
                theta_n = theta_n_1 * (1 - tddo / temp_norm ** 2)
            else:
                # keep the initial the relaxation factor
                pass
            # limit relaxation factor to the specified range
            theta_n = max(aitken_min_factor, min(aitken_max_factor, theta_n))
            # save relaxation factor for the next iteration
            self._theta_n_1 = theta_n
            d_out_vec.set_val(d_n)
            # compute relaxed outputs
            d_out_vec += theta_n * delta_d_n
            # save update to use in next iteration
            delta_d_n_1[:] = delta_d_n