diff --git a/dumux/assembly/CMakeLists.txt b/dumux/assembly/CMakeLists.txt index c8210e26b71d2cb8abd48c67ee76b5e36395c934..bd2ebb628d681a0c1f904bbd7bd1e3b195791f25 100644 --- a/dumux/assembly/CMakeLists.txt +++ b/dumux/assembly/CMakeLists.txt @@ -5,10 +5,12 @@ boxlocalresidual.hh cclocalassembler.hh cclocalresidual.hh diffmethod.hh +entitycolor.hh fvassembler.hh fvlocalassemblerbase.hh fvlocalresidual.hh jacobianpattern.hh +partialreassembler.hh staggeredfvassembler.hh staggeredlocalassembler.hh staggeredlocalresidual.hh diff --git a/dumux/assembly/boxlocalassembler.hh b/dumux/assembly/boxlocalassembler.hh index 104e2189294622b223061866396c55485bc4f534..2601c5d90a88216d347584828a9e162612ec562e 100644 --- a/dumux/assembly/boxlocalassembler.hh +++ b/dumux/assembly/boxlocalassembler.hh @@ -34,6 +34,8 @@ #include <dumux/assembly/numericepsilon.hh> #include <dumux/assembly/diffmethod.hh> #include <dumux/assembly/fvlocalassemblerbase.hh> +#include <dumux/assembly/partialreassembler.hh> +#include <dumux/assembly/entitycolor.hh> namespace Dumux { @@ -72,14 +74,25 @@ public: * \brief Computes the derivatives with respect to the given element and adds them * to the global matrix. The element residual is written into the right hand side. */ - void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) { this->asImp_().bindLocalViews(); - - const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); - - for (const auto& scv : scvs(this->fvGeometry())) - res[scv.dofIndex()] += residual[scv.indexInElement()]; + const auto eIdxGlobal = this->assembler().fvGridGeometry().elementMapper().index(this->element()); + if (partialReassembler + && partialReassembler->elementColor(eIdxGlobal) == EntityColor::green) + { + const auto residual = this->asImp_().evalLocalResidual(); // forward to the internal implementation + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.indexInElement()]; + } + else + { + const auto residual = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables, partialReassembler); // forward to the internal implementation + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.indexInElement()]; + } auto applyDirichlet = [&] (const auto& scvI, const auto& dirichletValues, @@ -225,7 +238,9 @@ public: * * \return The element residual at the current solution. */ - ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) { // get some aliases for convenience const auto& element = this->element(); @@ -233,7 +248,7 @@ public: const auto& curSol = this->curSol(); auto&& curElemVolVars = this->curElemVolVars(); - // get the vecor of the acutal element residuals + // get the vector of the actual element residuals const auto origResiduals = this->evalLocalResidual(); ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -280,14 +295,19 @@ public: // update the global stiffness matrix with the current partial derivatives for (auto&& scvJ : scvs(fvGeometry)) { - for (int eqIdx = 0; eqIdx < numEq; eqIdx++) - { - // A[i][col][eqIdx][pvIdx] is the rate of change of - // the residual of equation 'eqIdx' at dof 'i' - // depending on the primary variable 'pvIdx' at dof - // 'col'. - A[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDerivs[scvJ.indexInElement()][eqIdx]; - } + // don't add derivatives for green vertices + if (!partialReassembler + || partialReassembler->vertexColor(scvJ.dofIndex()) != EntityColor::green) + { + for (int eqIdx = 0; eqIdx < numEq; eqIdx++) + { + // A[i][col][eqIdx][pvIdx] is the rate of change of + // the residual of equation 'eqIdx' at dof 'i' + // depending on the primary variable 'pvIdx' at dof + // 'col'. + A[scvJ.dofIndex()][dofIdx][eqIdx][pvIdx] += partialDerivs[scvJ.indexInElement()][eqIdx]; + } + } } // restore the original state of the scv's volume variables @@ -336,8 +356,13 @@ public: * * \return The element residual at the current solution. */ - ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + // get some aliases for convenience const auto& element = this->element(); const auto& fvGeometry = this->fvGeometry(); @@ -437,8 +462,13 @@ public: * * \return The element residual at the current solution. */ - ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for analytic differentiation"); + // get some aliases for convenience const auto& element = this->element(); const auto& fvGeometry = this->fvGeometry(); @@ -551,8 +581,13 @@ public: * * \return The element residual at the current solution. */ - ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) { + if (partialReassembler) + DUNE_THROW(Dune::NotImplemented, "partial reassembly for explicit time discretization"); + // get some aliases for convenience const auto& element = this->element(); const auto& fvGeometry = this->fvGeometry(); diff --git a/dumux/assembly/cclocalassembler.hh b/dumux/assembly/cclocalassembler.hh index b046e732d03db6554f5673fa99c0971d98cb4c7f..13dcf1acf746f4b0dd11529521a0c3fbaea28800 100644 --- a/dumux/assembly/cclocalassembler.hh +++ b/dumux/assembly/cclocalassembler.hh @@ -36,6 +36,8 @@ #include <dumux/assembly/numericepsilon.hh> #include <dumux/assembly/diffmethod.hh> #include <dumux/assembly/fvlocalassemblerbase.hh> +#include <dumux/assembly/entitycolor.hh> +#include <dumux/assembly/partialreassembler.hh> #include <dumux/discretization/fluxstencil.hh> namespace Dumux { @@ -47,7 +49,7 @@ namespace Dumux { * \tparam TypeTag The TypeTag * \tparam Assembler The assembler type * \tparam Implementation The actual implementation - * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit) + * \tparam implicit Specifies whether the time discretization is implicit or not (i.e. explicit) */ template<class TypeTag, class Assembler, class Implementation, bool implicit> class CCLocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit> @@ -68,11 +70,21 @@ public: * \brief Computes the derivatives with respect to the given element and adds them * to the global matrix. The element residual is written into the right hand side. */ - void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables) + template <class PartialReassembler = DefaultPartialReassembler> + void assembleJacobianAndResidual(JacobianMatrix& jac, SolutionVector& res, GridVariables& gridVariables, + const PartialReassembler* partialReassembler) { this->asImp_().bindLocalViews(); const auto globalI = this->assembler().fvGridGeometry().elementMapper().index(this->element()); - res[globalI] = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation + if (partialReassembler + && partialReassembler->elementColor(globalI) == EntityColor::green) + { + res[globalI] = this->asImp_().evalLocalResidual()[0]; // forward to the internal implementation + } + else + { + res[globalI] = this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation + } } /*! diff --git a/dumux/assembly/entitycolor.hh b/dumux/assembly/entitycolor.hh new file mode 100644 index 0000000000000000000000000000000000000000..4988ec347c0d17e25b97cace2e1e6922f31c8f6c --- /dev/null +++ b/dumux/assembly/entitycolor.hh @@ -0,0 +1,57 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup Assembly + * \brief An enum class to define the colors of elements and vertices required + * for partial Jacobian reassembly. + */ +#ifndef DUMUX_ENTITY_COLOR_HH +#define DUMUX_ENTITY_COLOR_HH + +namespace Dumux { + +/*! + * \ingroup Assembly + * \brief The colors of elements and vertices required for partial + * Jacobian reassembly. + */ +enum class EntityColor { + //! distance from last linearization is above the tolerance + red, + + //! neighboring entity is red + yellow, + + /*! + * A yellow entity that has only non-green neighbor elements. + * + * This means that its relative error is below the tolerance, + * but its defect can be linearized without any additional + * cost. + */ + orange, + + //! does not need to be reassembled + green +}; + +} // end namespace Dumux + +#endif diff --git a/dumux/assembly/fvassembler.hh b/dumux/assembly/fvassembler.hh index 032bbe8b14bcfed75fedcd9a2e1e7b1023937f87..efb670db12bdeabfbb04e087651527ed34495757 100644 --- a/dumux/assembly/fvassembler.hh +++ b/dumux/assembly/fvassembler.hh @@ -58,7 +58,8 @@ class FVAssembler using TimeLoop = TimeLoopBase<typename GET_PROP_TYPE(TypeTag, Scalar)>; using SolutionVector = typename GET_PROP_TYPE(TypeTag, SolutionVector); - static constexpr bool isBox = GET_PROP_VALUE(TypeTag, DiscretizationMethod) == DiscretizationMethods::Box; + static constexpr DiscretizationMethods discMethod = GET_PROP_VALUE(TypeTag, DiscretizationMethod); + static constexpr bool isBox = discMethod == DiscretizationMethods::Box; using ThisType = FVAssembler<TypeTag, diffMethod, isImplicit>; using LocalAssembler = std::conditional_t<isBox, BoxLocalAssembler<TypeTag, ThisType, diffMethod, isImplicit>, @@ -68,6 +69,7 @@ public: using Scalar = typename GET_PROP_TYPE(TypeTag, Scalar); using JacobianMatrix = typename GET_PROP_TYPE(TypeTag, JacobianMatrix); using FVGridGeometry = typename GET_PROP_TYPE(TypeTag, FVGridGeometry); + using ResidualType = SolutionVector; /*! @@ -107,16 +109,17 @@ public: * \brief Assembles the global Jacobian of the residual * and the residual for the current solution. */ - void assembleJacobianAndResidual(const SolutionVector& curSol) + template<class PartialReassembler = DefaultPartialReassembler> + void assembleJacobianAndResidual(const SolutionVector& curSol, const PartialReassembler* partialReassembler = nullptr) { checkAssemblerState_(); - resetJacobian_(); + resetJacobian_(partialReassembler); resetResidual_(); assemble_([&](const Element& element) { LocalAssembler localAssembler(*this, element, curSol); - localAssembler.assembleJacobianAndResidual(*jacobian_, *residual_, *gridVariables_); + localAssembler.assembleJacobianAndResidual(*jacobian_, *residual_, *gridVariables_, partialReassembler); }); } @@ -329,8 +332,9 @@ private: (*residual_) = 0.0; } - // reset the jacobian vector to 0.0 - void resetJacobian_() + // reset the Jacobian matrix to 0.0 + template <class PartialReassembler = DefaultPartialReassembler> + void resetJacobian_(const PartialReassembler *partialReassembler = nullptr) { if(!jacobian_) { @@ -339,7 +343,10 @@ private: setJacobianPattern(); } - (*jacobian_) = 0.0; + if (partialReassembler) + partialReassembler->resetJacobian(*jacobian_, fvGridGeometry()); + else + *jacobian_ = 0.0; } // check if the assembler is in a correct state for assembly diff --git a/dumux/assembly/partialreassembler.hh b/dumux/assembly/partialreassembler.hh new file mode 100644 index 0000000000000000000000000000000000000000..0daad5cb67e5b75bbcbf14f5fd0e4233e3dccbec --- /dev/null +++ b/dumux/assembly/partialreassembler.hh @@ -0,0 +1,480 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/***************************************************************************** + * See the file COPYING for full copying permissions. * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 2 of the License, or * + * (at your option) any later version. * + * * + * 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + *****************************************************************************/ +/*! + * \file + * \ingroup Assembly + * \brief detects which entries in the Jacobian have to be recomputed + */ +#ifndef DUMUX_PARTIAL_REASSEMBLER_HH +#define DUMUX_PARTIAL_REASSEMBLER_HH + +#include <algorithm> +#include <vector> + +#include <dune/grid/common/gridenums.hh> +#include <dune/istl/multitypeblockvector.hh> + +#include <dumux/common/typetraits/isvalid.hh> +#include <dumux/discretization/methods.hh> +#include <dumux/parallel/vertexhandles.hh> + +#include "entitycolor.hh" + +namespace Dumux { + +class DefaultPartialReassembler +{ +public: + DefaultPartialReassembler() = delete; + + template<typename... Args> + void resetJacobian(Args&&... args) const {} + + EntityColor elementColor(size_t idx) const + { return EntityColor::red; } + + EntityColor vertexColor(size_t idx) const + { return EntityColor::red; } +}; + +//! the partial reassembler engine specialized for discretization methods +template<class Assembler, DiscretizationMethods discMethod> +class PartialReassemblerEngine +{ +public: + PartialReassemblerEngine(const typename Assembler::FVGridGeometry&) + { DUNE_THROW(Dune::NotImplemented, "PartialReassembler for this discretization method!"); } + + EntityColor elementColor(size_t idx) const + { return EntityColor::red; } + + EntityColor dofColor(size_t idx) const + { return EntityColor::red; } + + template<typename... Args> + std::size_t computeColors(Args&&... args) { return 0; } + + template<typename... Args> + void resetJacobian(Args&&... args) const {} + + template<typename... Args> + void resetColors(Args&&... args) {} +}; + +//! the partial reassembler engine specialized for the box method +template<class Assembler> +class PartialReassemblerEngine<Assembler, DiscretizationMethods::Box> +{ + using Scalar = typename Assembler::Scalar; + using FVGridGeometry = typename Assembler::FVGridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + using VertexMapper = typename FVGridGeometry::VertexMapper; + static constexpr int dim = FVGridGeometry::GridView::dimension; + +public: + PartialReassemblerEngine(const FVGridGeometry& fvGridGeometry) + : elementColor_(fvGridGeometry.elementMapper().size(), EntityColor::red) + , vertexColor_(fvGridGeometry.vertexMapper().size(), EntityColor::red) + {} + + // returns number of green elements + std::size_t computeColors(Scalar threshold, + const FVGridGeometry& fvGridGeometry, + const std::vector<Scalar>& distanceFromLastLinearization) + { + const auto& gridView = fvGridGeometry.gridView(); + const auto& elementMapper = fvGridGeometry.elementMapper(); + const auto& vertexMapper = fvGridGeometry.vertexMapper(); + + // set all vertices to green + vertexColor_.assign(vertexColor_.size(), EntityColor::green); + + // mark the red vertices + for (unsigned int i = 0; i < vertexColor_.size(); ++i) + { + using std::max; + if (distanceFromLastLinearization[i] > threshold) + // mark vertex as red if discrepancy is larger than + // the relative tolerance + vertexColor_[i] = EntityColor::red; + } + + // Mark all red elements + for (const auto& element : elements(gridView)) + { + // find out whether the current element features a red vertex + bool isRed = false; + + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + if (vertexColor_[globalI] == EntityColor::red) { + isRed = true; + break; + } + } + + int eIdx = elementMapper.index(element); + // if a vertex is red, the element color is also red, otherwise green + if (isRed) + elementColor_[eIdx] = EntityColor::red; + else + elementColor_[eIdx] = EntityColor::green; + } + + // mark orange vertices + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only red elements tint vertices yellow + if (elementColor_[eIdx] == EntityColor::red) + { + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + // red vertices don't become orange + if (vertexColor_[globalI] != EntityColor::red) + vertexColor_[globalI] = EntityColor::orange; + } + } + } + + // at this point we communicate the yellow vertices to the + // neighboring processes because a neigbor process may not see + // the red vertex for yellow border vertices + VertexHandleMin<EntityColor, std::vector<EntityColor>, VertexMapper> + minHandle(vertexColor_, vertexMapper); + gridView.communicate(minHandle, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + + // mark yellow elements + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only treat non-red elements + if (elementColor_[eIdx] != EntityColor::red) + { + // check whether the element features a orange vertex + bool isOrange = false; + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + if (vertexColor_[globalI] == EntityColor::orange) { + isOrange = true; + break; + } + } + + if (isOrange) + elementColor_[eIdx] = EntityColor::yellow; + } + } + + // change orange vertices to yellow ones if it has at least + // one green element as a neighbor + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + // only green elements are considered + if (elementColor_[eIdx] == EntityColor::green) + { + int numVertices = element.subEntities(dim); + + for (int i = 0; i < numVertices; ++i) { + int globalI = vertexMapper.subIndex(element, i, dim); + + // if a vertex is orange, recolor it to yellow + if (vertexColor_[globalI] == EntityColor::orange) + vertexColor_[globalI] = EntityColor::yellow; + } + } + } + + // demote the border orange vertices + VertexHandleMax<EntityColor, std::vector<EntityColor>, VertexMapper> + maxHandle(vertexColor_, vertexMapper); + gridView.communicate(maxHandle, + Dune::InteriorBorder_InteriorBorder_Interface, + Dune::ForwardCommunication); + + // promote the remaining orange vertices to red + for (unsigned int i=0; i < vertexColor_.size(); ++i) { + // if a vertex is green or yellow don't do anything + if (vertexColor_[i] == EntityColor::green || vertexColor_[i] == EntityColor::yellow) + continue; + + // set the vertex to red + vertexColor_[i] = EntityColor::red; + } + + // count green elements + return std::count_if(elementColor_.begin(), elementColor_.end(), + [](EntityColor c){ return c == EntityColor::green; }); + } + + void resetJacobian(JacobianMatrix& jacobian, + const FVGridGeometry& fvGridGeometry) const + { + // loop over all dofs + for (unsigned int rowIdx = 0; rowIdx < jacobian.N(); ++rowIdx) + { + // reset all entries corrosponding to a non-green vertex + if (vertexColor_[rowIdx] != EntityColor::green) + { + // set all matrix entries in the row to 0 + auto colIt = jacobian[rowIdx].begin(); + const auto& colEndIt = jacobian[rowIdx].end(); + for (; colIt != colEndIt; ++colIt) { + *colIt = 0.0; + } + } + } + } + + void resetColors() + { + elementColor_.assign(elementColor_.size(), EntityColor::red); + vertexColor_.assign(vertexColor_.size(), EntityColor::red); + } + + EntityColor elementColor(size_t idx) const + { return elementColor_[idx]; } + + EntityColor vertexColor(size_t idx) const + { return vertexColor_[idx]; } + + EntityColor dofColor(size_t idx) const + { return vertexColor_[idx]; } + +private: + //! entity colors for partial reassembly + std::vector<EntityColor> elementColor_; + std::vector<EntityColor> vertexColor_; +}; + +//! the partial reassembler engine specialized for the box method +template<class Assembler> +class PartialReassemblerEngine<Assembler, DiscretizationMethods::CCTpfa> +{ + using Scalar = typename Assembler::Scalar; + using FVGridGeometry = typename Assembler::FVGridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + +public: + PartialReassemblerEngine(const FVGridGeometry& fvGridGeometry) + : elementColor_(fvGridGeometry.elementMapper().size(), EntityColor::red) + {} + + // returns number of green elements + std::size_t computeColors(Scalar threshold, + const FVGridGeometry& fvGridGeometry, + const std::vector<Scalar>& distanceFromLastLinearization) + { + const auto& gridView = fvGridGeometry.gridView(); + const auto& elementMapper = fvGridGeometry.elementMapper(); + + // mark the red elements + for (const auto& element : elements(gridView)) + { + int eIdx = elementMapper.index(element); + + if (distanceFromLastLinearization[eIdx] > threshold) + { + // mark element as red if discrepancy is larger than + // the relative tolerance + elementColor_[eIdx] = EntityColor::red; + } + else + { + elementColor_[eIdx] = EntityColor::green; + } + } + + // mark the neighbors also red + const auto& connectivityMap = fvGridGeometry.connectivityMap(); + for (unsigned eIdx = 0; eIdx < elementColor_.size(); ++eIdx) + { + if (elementColor_[eIdx] == EntityColor::red) + continue; // element is red already! + + if (distanceFromLastLinearization[eIdx] > threshold) + { + for (const auto& connectedDof : connectivityMap[eIdx]) + elementColor_[connectedDof.globalJ] = EntityColor::red; + } + } + + // count green elements + return std::count_if(elementColor_.begin(), elementColor_.end(), + [](EntityColor c){return c == EntityColor::green;}); + + } + + void resetJacobian(JacobianMatrix& jacobian, + const FVGridGeometry& fvGridGeometry) const + { + // loop over all dofs + for (unsigned int colIdx = 0; colIdx < jacobian.M(); ++colIdx) + { + // reset all entries corresponding to a non-green element + if (elementColor_[colIdx] != EntityColor::green) + { + // set all matrix entries in the column to 0 + jacobian[colIdx][colIdx] = 0; + const auto& connectivityMap = fvGridGeometry.connectivityMap(); + for (const auto& dataJ : connectivityMap[colIdx]) + jacobian[dataJ.globalJ][colIdx] = 0; + } + } + } + + void resetColors() + { + elementColor_.assign(elementColor_.size(), EntityColor::red); + } + + EntityColor elementColor(size_t idx) const + { return elementColor_[idx]; } + + EntityColor dofColor(size_t idx) const + { return elementColor_[idx]; } + +private: + //! entity colors for partial reassembly + std::vector<EntityColor> elementColor_; +}; + +//! the partial reassembler engine specialized for the mpfa method +template<class Assembler> +class PartialReassemblerEngine<Assembler, DiscretizationMethods::CCMpfa> +: public PartialReassemblerEngine<Assembler, DiscretizationMethods::CCTpfa> +{ + using ParentType = PartialReassemblerEngine<Assembler, DiscretizationMethods::CCTpfa>; +public: + using ParentType::ParentType; +}; + +/*! + * \ingroup Assembly + * \brief detects which entries in the Jacobian have to be recomputed + * \tparam TypeTag The TypeTag + */ +template<class Assembler> +class PartialReassembler +{ + using Scalar = typename Assembler::Scalar; + using FVGridGeometry = typename Assembler::FVGridGeometry; + using JacobianMatrix = typename Assembler::JacobianMatrix; + using VertexMapper = typename FVGridGeometry::VertexMapper; + + static constexpr DiscretizationMethods discMethod = FVGridGeometry::discretizationMethod; + using Engine = PartialReassemblerEngine<Assembler, discMethod>; + + static constexpr auto hasVertexColor = Dumux::isValid([](auto&& a) -> decltype(a.vertexColor(0)) {}); + static constexpr bool engineHasVertexColor = decltype(hasVertexColor(std::declval<Engine>()))::value; + +public: + + /*! + * \brief constructor + * \param fvGridGeometry the employed FVGridGeometry + */ + PartialReassembler(const FVGridGeometry& fvGridGeometry) + : engine_(fvGridGeometry) + , greenElems_(0) + { + totalElems_ = fvGridGeometry.elementMapper().size(); + totalElems_ = fvGridGeometry.gridView().comm().sum(totalElems_); + } + + /*! + * \brief Determine the colors of entities for partial reassembly. + * + * The following approach is used: + * + * - Set all elements to 'green' + * - Mark all elements as 'red' which exhibit an relative error above + * the tolerance + * - Mark all neighbors of 'red' elements also 'red' + * + * \param threshold Reassemble only if the distance from the last + * linearization is above this value. + */ + void computeColors(Scalar threshold, + const FVGridGeometry& fvGridGeometry, + const std::vector<Scalar>& distanceFromLastLinearization) + { + greenElems_ = engine_.computeColors(threshold, fvGridGeometry, distanceFromLastLinearization); + } + + void resetColors() + { + engine_.resetColors(); + } + + void resetJacobian(JacobianMatrix& jacobian, const FVGridGeometry& fvGridGeometry) const + { + engine_.resetJacobian(jacobian, fvGridGeometry); + } + + /*! + * \brief called by the assembler after successful assembly + */ + template <class Communication> + void report(const Communication& comm, std::ostream& outStream) + { + if (comm.size() > 1) + greenElems_ = comm.sum(greenElems_); + + auto reassembledElems = totalElems_ - greenElems_; + auto width = std::to_string(totalElems_).size(); + outStream << ", reassembled " << std::setw(width) + << reassembledElems << " (" << std::setw(3) + << 100*reassembledElems/totalElems_ << "%) elements"; + } + + EntityColor elementColor(size_t idx) const + { return engine_.elementColor(idx); } + + EntityColor dofColor(size_t idx) const + { return engine_.dofColor(idx); } + + template<bool enable = engineHasVertexColor, typename std::enable_if_t<enable, int> = 0> + EntityColor vertexColor(size_t idx) const + { return engine_.vertexColor(idx); } + +private: + Engine engine_; + size_t totalElems_; + size_t greenElems_; +}; + +} // namespace Dumux + +#endif diff --git a/dumux/common/parameters.hh b/dumux/common/parameters.hh index 7655211a20516297f6d507b40e4229ea473d9bef..e4a75101e831fcdb2524ce11b892f91a6ae694f3 100644 --- a/dumux/common/parameters.hh +++ b/dumux/common/parameters.hh @@ -352,7 +352,6 @@ private: { // parameters in the implicit group params["Implicit.UpwindWeight"] = "1.0"; - params["Implicit.EnablePartialReassemble"] = "false"; params["Implicit.EnableJacobianRecycling"] = "false"; // parameters in the assembly group @@ -369,7 +368,7 @@ private: // parameters in the problem group params["Problem.EnableGravity"] = "true"; - // parameters in the newton group + // parameters in the Newton group params["Newton.MaxSteps"] = "18"; params["Newton.TargetSteps"] = "10"; params["Newton.UseLineSearch"] = "false"; @@ -381,6 +380,7 @@ private: params["Newton.EnableAbsoluteResidualCriterion"] = "false"; params["Newton.MaxAbsoluteResidual"] = "1e-5"; params["Newton.SatisfyResidualAndShiftCriterion"] = "false"; + params["Newton.EnablePartialReassembly"] = "false"; // parameters in the time loop group params["TimeLoop.MaxTimeStepSize"] = "1e300"; diff --git a/dumux/nonlinear/newtonsolver.hh b/dumux/nonlinear/newtonsolver.hh index 0a5e1a3a02caf5690df1c2f6c84afce3fc3327ce..aa7dfb5eb60fcd2142f6f2717565cc4fc2017bc4 100644 --- a/dumux/nonlinear/newtonsolver.hh +++ b/dumux/nonlinear/newtonsolver.hh @@ -41,8 +41,10 @@ #include <dumux/common/exceptions.hh> #include <dumux/common/timeloop.hh> #include <dumux/common/typetraits/vector.hh> +#include <dumux/common/typetraits/isvalid.hh> #include <dumux/linear/linearsolveracceptsmultitypematrix.hh> #include <dumux/linear/matrixconverter.hh> +#include <dumux/assembly/partialreassembler.hh> #include "newtonconvergencewriter.hh" @@ -65,7 +67,15 @@ class NewtonSolver using JacobianMatrix = typename Assembler::JacobianMatrix; using SolutionVector = typename Assembler::ResidualType; using ConvergenceWriter = ConvergenceWriterInterface<SolutionVector>; - + using Reassembler = PartialReassembler<Assembler>; + + // TODO: Require Reassembler argument from the standard interface + static constexpr auto supportsPartialReassembly = + Dumux::isValid([](auto&& a) -> decltype( + a.assembleJacobianAndResidual(std::declval<Assembler>(), + std::declval<const SolutionVector&>(), + std::declval<const Reassembler*>()) + ){}); public: using Communication = Comm; @@ -91,6 +101,13 @@ public: // set a different default for the linear solver residual reduction // within the Newton the linear solver doesn't need to solve too exact linearSolver_->setResidualReduction(getParamFromGroup<Scalar>(paramGroup, "LinearSolver.ResidualReduction", 1e-6)); + + // initialize the partial reassembler + if (enablePartialReassembly_) + { + partialReassembler_ = std::make_unique<Reassembler>(assembler_->fvGridGeometry()); + distanceFromLastLinearization_.resize(assembler_->fvGridGeometry().numDofs(), 0); + } } /*! @@ -116,6 +133,13 @@ public: // set a different default for the linear solver residual reduction // within the Newton the linear solver doesn't need to solve too exact linearSolver_->setResidualReduction(getParamFromGroup<Scalar>(paramGroup, "LinearSolver.ResidualReduction", 1e-6)); + + // initialize the partial reassembler + if (enablePartialReassembly_) + { + partialReassembler_ = std::make_unique<Reassembler>(assembler_->fvGridGeometry()); + distanceFromLastLinearization_.resize(assembler_->fvGridGeometry().numDofs(), 0); + } } //! the communicator for parallel runs @@ -188,6 +212,12 @@ public: Dune::Timer solveTimer(false); Dune::Timer updateTimer(false); + if (enablePartialReassembly_) + { + partialReassembler_->resetColors(); + std::fill(distanceFromLastLinearization_.begin(), + distanceFromLastLinearization_.end(), 0.0); + } newtonBegin(uCurrentIter); // execute the method as long as the controller thinks @@ -360,7 +390,10 @@ public: */ virtual void assembleLinearSystem(const SolutionVector& uCurrentIter) { - assembler_->assembleJacobianAndResidual(uCurrentIter); + assembleLinearSystem_(uCurrentIter); + + if (enablePartialReassembly_) + partialReassembler_->report(comm_, endIterMsgStream_); } /*! @@ -445,9 +478,44 @@ public: const SolutionVector &uLastIter, const SolutionVector &deltaU) { - if (enableShiftCriterion_) + if (enableShiftCriterion_ || enablePartialReassembly_) newtonUpdateShift_(uLastIter, deltaU); + if (enablePartialReassembly_) { + // Determine the threshold 'eps' that is used for the partial reassembly. + // Every entity where the primary variables exhibit a relative shift + // summed up since the last linearization above 'eps' will be colored + // red yielding a reassembly. + // The user can provide three parameters to influence the threshold: + // 'minEps' by 'Newton.ReassemblyMinThreshold' (1e-1*shiftTolerance_ default) + // 'maxEps' by 'Newton.ReassemblyMaxThreshold' (1e2*shiftTolerance_ default) + // 'omega' by 'Newton.ReassemblyShiftWeight' (1e-3 default) + // The threshold is calculated from the currently achieved maximum + // relative shift according to the formula + // eps = max( minEps, min(maxEps, omega*shift) ). + // Increasing/decreasing 'minEps' leads to less/more reassembly if + // 'omega*shift' is small, i.e., for the last Newton iterations. + // Increasing/decreasing 'maxEps' leads to less/more reassembly if + // 'omega*shift' is large, i.e., for the first Newton iterations. + // Increasing/decreasing 'omega' leads to more/less first and last + // iterations in this sense. + using std::max; + using std::min; + auto reassemblyThreshold = max(reassemblyMinThreshold_, + min(reassemblyMaxThreshold_, + shift_*reassemblyShiftWeight_)); + + updateDistanceFromLastLinearization_(uLastIter, deltaU); + partialReassembler_->computeColors(reassemblyThreshold, + assembler_->fvGridGeometry(), + distanceFromLastLinearization_); + + // set the discrepancy of the red entities to zero + for (unsigned int i = 0; i < distanceFromLastLinearization_.size(); i++) + if (partialReassembler_->dofColor(i) == EntityColor::red) + distanceFromLastLinearization_[i] = 0; + } + if (useLineSearch_) lineSearchUpdate_(uCurrentIter, uLastIter, deltaU); @@ -680,6 +748,22 @@ protected: private: + //! assembleLinearSystem_ for assemblers that support partial reassembly + template<class A = Assembler> + auto assembleLinearSystem_(const SolutionVector& uCurrentIter) + -> typename std::enable_if_t<decltype(supportsPartialReassembly(std::declval<A>()))::value, void> + { + assembler_->assembleJacobianAndResidual(uCurrentIter, partialReassembler_.get()); + } + + //! assembleLinearSystem_ for assemblers that don't support partial reassembly + template<class A = Assembler> + auto assembleLinearSystem_(const SolutionVector& uCurrentIter) + -> typename std::enable_if_t<!decltype(supportsPartialReassembly(std::declval<A>()))::value, void> + { + assembler_->assembleJacobianAndResidual(uCurrentIter); + } + /*! * \brief Update the maximum relative shift of the solution compared to * the previous iteration. Overload for "normal" solution vectors. @@ -928,10 +1012,37 @@ private: setTargetSteps(getParamFromGroup<int>(group, "Newton.TargetSteps")); setMaxSteps(getParamFromGroup<int>(group, "Newton.MaxSteps")); + enablePartialReassembly_ = getParamFromGroup<bool>(group, "Newton.EnablePartialReassembly"); + reassemblyMinThreshold_ = getParamFromGroup<Scalar>(group, "Newton.ReassemblyMinThreshold", 1e-1*shiftTolerance_); + reassemblyMaxThreshold_ = getParamFromGroup<Scalar>(group, "Newton.ReassemblyMaxThreshold", 1e2*shiftTolerance_); + reassemblyShiftWeight_ = getParamFromGroup<Scalar>(group, "Newton.ReassemblyShiftWeight", 1e-3); + verbose_ = comm_.rank() == 0; numSteps_ = 0; } + template<class SolVec> + void updateDistanceFromLastLinearization_(const SolVec &u, + const SolVec &uDelta) + { + for (size_t i = 0; i < u.size(); ++i) { + const auto& currentPriVars(u[i]); + auto nextPriVars(currentPriVars); + nextPriVars -= uDelta[i]; + + // add the current relative shift for this degree of freedom + auto shift = relativeShiftAtDof_(currentPriVars, nextPriVars); + distanceFromLastLinearization_[i] += shift; + } + } + + template<class ...Args> + void updateDistanceFromLastLinearization_(const Dune::MultiTypeBlockVector<Args...> &uLastIter, + const Dune::MultiTypeBlockVector<Args...> &deltaU) + { + //! \todo implement the function for MultiTypeBlockVectors + } + /*! * \brief Returns the maximum relative shift between two vectors of * primary variables. @@ -941,7 +1052,7 @@ private: */ template<class PrimaryVariables> Scalar relativeShiftAtDof_(const PrimaryVariables &priVars1, - const PrimaryVariables &priVars2) + const PrimaryVariables &priVars2) const { Scalar result = 0.0; using std::abs; @@ -973,7 +1084,6 @@ private: Scalar residualTolerance_; // further parameters - bool enablePartialReassemble_; bool useLineSearch_; bool useChop_; bool enableAbsoluteResidualCriterion_; @@ -984,6 +1094,13 @@ private: //! the parameter group for getting parameters from the parameter tree std::string paramGroup_; + // infrastructure for partial reassembly + bool enablePartialReassembly_; + std::unique_ptr<Reassembler> partialReassembler_; + std::vector<Scalar> distanceFromLastLinearization_; + Scalar reassemblyMinThreshold_; + Scalar reassemblyMaxThreshold_; + Scalar reassemblyShiftWeight_; }; } // end namespace Dumux diff --git a/test/porousmediumflow/2p/implicit/incompressible/test_2p.input b/test/porousmediumflow/2p/implicit/incompressible/test_2p.input index 93f7f1f1ee922e699bd27f853eda26be4521f705..336df6cf83b0f396fee77f0b57e0fabb6bcb1eaf 100644 --- a/test/porousmediumflow/2p/implicit/incompressible/test_2p.input +++ b/test/porousmediumflow/2p/implicit/incompressible/test_2p.input @@ -14,3 +14,6 @@ LensUpperRight = 4.0 3.0 # [m] coordinates of the upper right lens corner [Problem] Name = 2p EnableGravity = true + +[Newton] +EnablePartialReassembly = true