# -*- coding: utf-8 -*-
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Contributors:
# INITIAL AUTHORS - API and implementation and/or documentation
# :author: Francois Gallard
# OTHER AUTHORS - MACROSCOPIC CHANGES
"""A Gauss Seidel algorithm for solving MDAs."""
from __future__ import division, unicode_literals
from typing import Any, Mapping, Optional, Sequence
from gemseo.core.chain import MDOChain
from gemseo.core.coupling_structure import MDOCouplingStructure
from gemseo.core.discipline import MDODiscipline
from gemseo.mda.mda import MDA
[docs]class MDAGaussSeidel(MDA):
"""An MDA analysis based on the Gauss-Seidel algorithm.
This algorithm is an iterative technique to solve the linear system:
.. math::
Ax = b
by decomposing the matrix :math:`A`
into the sum of a lower triangular matrix :math:`L_*`
and a strictly upper triangular matrix :math:`U`.
The new iterate is given by:
.. math::
x_{k+1} = L_*^{-1}(b-Ux_k)
"""
_ATTR_TO_SERIALIZE = MDA._ATTR_TO_SERIALIZE + (
"strong_couplings",
"over_relax_factor",
"normed_residual",
)
def __init__(
self,
disciplines, # type: Sequence[MDODiscipline]
name=None, # type: Optional[str]
max_mda_iter=10, # type: int
grammar_type=MDODiscipline.JSON_GRAMMAR_TYPE, # type: str
tolerance=1e-6, # type: float
linear_solver_tolerance=1e-12, # type: float
warm_start=False, # type: bool
use_lu_fact=False, # type: bool
over_relax_factor=1.0, # type: float
coupling_structure=None, # type: Optional[MDOCouplingStructure]
log_convergence=False, # type: bool
linear_solver="DEFAULT", # type: str
linear_solver_options=None, # type: Mapping[str,Any]
): # type: (...) -> None
"""
Args:
over_relax_factor: The relaxation coefficient,
used to make the method more robust,
if ``0<over_relax_factor<1`` or faster if ``1<over_relax_factor<=2``.
If ``over_relax_factor =1.``, it is deactivated.
"""
self.chain = MDOChain(disciplines, grammar_type=grammar_type)
super(MDAGaussSeidel, self).__init__(
disciplines,
max_mda_iter=max_mda_iter,
name=name,
grammar_type=grammar_type,
tolerance=tolerance,
linear_solver_tolerance=linear_solver_tolerance,
warm_start=warm_start,
use_lu_fact=use_lu_fact,
coupling_structure=coupling_structure,
log_convergence=log_convergence,
linear_solver=linear_solver,
linear_solver_options=linear_solver_options,
)
assert over_relax_factor > 0.0
assert over_relax_factor <= 2.0
self.over_relax_factor = over_relax_factor
self._set_default_inputs()
self._compute_input_couplings()
def _initialize_grammars(self):
self.input_grammar.update_from(self.chain.input_grammar)
self.output_grammar.update_from(self.chain.output_grammar)
def _run(self):
# Run the disciplines in a sequential way
# until the difference between outputs is under tolerance.
if self.warm_start:
self._couplings_warm_start()
current_couplings = 0.0
relax = self.over_relax_factor
use_relax = relax != 1.0
# store initial residual
current_iter = 0
while not self._termination(current_iter) or current_iter == 0:
for discipline in self.disciplines:
discipline.execute(self.local_data)
outs = discipline.get_output_data()
if use_relax:
# First time this output is computed, update directly local data
self.local_data.update(
{k: v for k, v in outs.items() if k not in self.local_data}
)
# The couplings already exist in the local data,
# so the over relaxation can be applied
self.local_data.update(
{
k: relax * v + (1.0 - relax) * self.local_data[k]
for k, v in outs.items()
if k in self.local_data
}
)
else:
self.local_data.update(outs)
new_couplings = self._current_strong_couplings()
self._compute_residual(
current_couplings,
new_couplings,
current_iter,
first=current_iter == 0,
log_normed_residual=self.log_convergence,
)
# store current residuals
current_iter += 1
current_couplings = new_couplings
for discipline in self.disciplines: # Update all outputs without relax
self.local_data.update(discipline.get_output_data())