Source code for edelweissfe.linsolve.composition.twolevelcycle

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#  ---------------------------------------------------------------------
#
#  _____    _      _              _         _____ _____
# | ____|__| | ___| |_      _____(_)___ ___|  ___| ____|
# |  _| / _` |/ _ \ \ \ /\ / / _ \ / __/ __| |_  |  _|
# | |__| (_| |  __/ |\ V  V /  __/ \__ \__ \  _| | |___
# |_____\__,_|\___|_| \_/\_/ \___|_|___/___/_|   |_____|
#
#
#  Unit of Strength of Materials and Structural Analysis
#  University of Innsbruck,
#  2017 - today
#
#  Matthias Neuner matthias.neuner@uibk.ac.at
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  The full text of the license can be found in the file LICENSE.md at
#  the top level directory of EdelweissFE.
#  ---------------------------------------------------------------------
"""What the two-level cycles of a vector field (:class:`~edelweissfe.linsolve.composition.twolevel.TwoLevel`) share: the
corner-node two-level cycle (:mod:`~edelweissfe.linsolve.composition.cornernodetwolevelcycle`) and the rigid-body coarse
level (:mod:`~edelweissfe.linsolve.composition.rigidbodymodestwolevelcycle`).

Both are one two-level cycle -- fine Chebyshev sweeps on the field's scaled block, and the coarse correction on the
Galerkin coarse operator -- that differ in their coarse level and coarse solve: the rigid-body coarse level solves with
an AMGCL hierarchy of its own, the corner-node two-level cycle with a component of the composed solver. Both are built
and applied in C++: the shared cycle is ``TwoLevelCycle`` in ``amgcl-wrapper.hpp``, wrapped as ``_PyAMGCLTwoLevelCycle``
in ``amgcl.pyx``. :class:`TwoLevelCycle` mirrors those bases here: it creates the C++ object, which validates
every parameter, and passes the cycle, the fine spectral radius a build estimates, the timings and the report through.
A derived class chooses the parameters and builds its coarse level, in its ``build``, from what that coarse level
needs.
"""

import numpy as np


[docs] class TwoLevelCycle: """A two-level preconditioner of one vector field's equilibrated diagonal block, built once per hierarchy refresh and applied many times within the outer Krylov solve, with the ``build`` / ``applyPreconditioner`` life cycle of :class:`~edelweissfe.linsolve.amgcl.amgcl.PyAMGCLSolver`. Parameters ---------- cppClass The Cython class of the C++ preconditioner, created here from ``parameters``. parameters Its parameters, which its constructor checks -- the one place they are checked. Raises ------ ValueError For parameters the C++ class cannot use, before anything is built. """ def __init__(self, cppClass, parameters: dict): try: self._preconditioner = cppClass(parameters) except RuntimeError as error: raise ValueError(str(error)) from error
[docs] @staticmethod def cycleParameters(amgclTree: dict, backendBlockSize: int, backendPrecision: str): """The parameters of the cycle, in the one spelling both C++ classes read (``TwoLevelCycleParameters`` in ``amgcl-wrapper.hpp``). Parameters ---------- amgclTree The field's AMGCL tree. Its ``relax`` (a Chebyshev polynomial, without Jacobi scaling), ``npre`` and ``npost`` are the fine smoother, with AMGCL's defaults for what it leaves out; the default trees, the one place of EdelweissFE's defaults, are :func:`~edelweissfe.linsolve.composition.amg.defaultFieldTree` and, for the corner-node two-level cycle, :func:`~edelweissfe.linsolve.composition.amg.defaultCornerNodeTwoLevelTree`. backendBlockSize The storage of the fine operator: ``1`` (scalar entries), or ``2``/``3`` (node-wise blocks of the field's components: less memory traffic per product, the same cycle up to the order of summation). backendPrecision The value type of the stored fine operator and of :math:`P`, :math:`P^T`: ``"double"``, or ``"float"`` (not for 2x2 blocks) for half the bytes per entry; vectors and sums stay double, and the spectral radius is estimated on the double operator. """ return { "amgcl": amgclTree, "backendBlockSize": backendBlockSize, "backendPrecision": backendPrecision, }
@property def fineSpectralRadius(self) -> float: """The estimate of the fine spectral radius the smoother was built with: ``relax.spectralRadiusSteps`` Arnoldi steps from a start vector seeded per row, zero on the Dirichlet rows (``amgcl-spectral-radius.hpp``).""" return self._preconditioner.fineSpectralRadius
[docs] def applyPreconditioner(self, r: np.ndarray) -> np.ndarray: """One two-level cycle: fine Chebyshev sweeps and the coarse correction on the free dofs; the Dirichlet rows are solved exactly, ``x[i] = r[i] / A[i, i]``.""" return self._preconditioner.applyPreconditioner(r)
[docs] def timings(self) -> dict: """Wall time [s] of the last build's stages and the cumulative apply (fine vs. coarse) -- kept measured, not assumed, since the split decides where the next optimization goes.""" return self._preconditioner.timings()
[docs] def report(self) -> str: """The coarse level in figures, and the report of its coarse solve (diagnostics, not a convergence gate).""" return self._preconditioner.report()