diff --git a/dumux/assembly/cvfelocalassembler.hh b/dumux/assembly/cvfelocalassembler.hh new file mode 100644 index 0000000000000000000000000000000000000000..44ef08fcc45bd3795222f5b316e066cbcefac422 --- /dev/null +++ b/dumux/assembly/cvfelocalassembler.hh @@ -0,0 +1,778 @@ +// -*- 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 3 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 + * \ingroup CVFEDiscretization + * \brief An assembler for Jacobian and residual contribution per element (CVFE methods) + */ +#ifndef DUMUX_CVFE_LOCAL_ASSEMBLER_HH +#define DUMUX_CVFE_LOCAL_ASSEMBLER_HH + +#include <dune/common/exceptions.hh> +#include <dune/common/hybridutilities.hh> +#include <dune/common/reservedvector.hh> +#include <dune/grid/common/gridenums.hh> +#include <dune/istl/matrixindexset.hh> +#include <dune/istl/bvector.hh> + +#include <dumux/common/properties.hh> +#include <dumux/common/parameters.hh> +#include <dumux/common/numericdifferentiation.hh> + +#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> + +#include <dumux/discretization/cvfe/elementsolution.hh> + +#include "volvardeflectionhelper_.hh" + +namespace Dumux { + +#ifndef DOXYGEN +namespace Detail::CVFE { + +struct NoOperator +{ + template<class... Args> + constexpr void operator()(Args&&...) const {} +}; + +template<class X, class Y> +using Impl = std::conditional_t<!std::is_same_v<X, void>, X, Y>; + +} // end namespace Detail +#endif // DOXYGEN + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief A base class for all local CVFE assemblers + * \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) + */ +template<class TypeTag, class Assembler, class Implementation, bool implicit> +class CVFELocalAssemblerBase : public FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit> +{ + using ParentType = FVLocalAssemblerBase<TypeTag, Assembler, Implementation, implicit>; + using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>; + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using ElementVolumeVariables = typename GridVariables::GridVolumeVariables::LocalView; + using SolutionVector = typename Assembler::SolutionVector; + + static constexpr int numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq(); + static constexpr int dim = GetPropType<TypeTag, Properties::GridGeometry>::GridView::dimension; + +public: + + using ParentType::ParentType; + + void bindLocalViews() + { + std::cout << "hi" << std::endl; + ParentType::bindLocalViews(); + this->elemBcTypes().update(this->asImp_().problem(), this->element(), this->fvGeometry()); + } + + + /*! + * \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. + */ + template <class ResidualVector, class PartialReassembler = DefaultPartialReassembler, class CouplingFunction = Detail::CVFE::NoOperator> + void assembleJacobianAndResidual(JacobianMatrix& jac, ResidualVector& res, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr, + const CouplingFunction& maybeAssembleCouplingBlocks = {}) + { + this->asImp_().bindLocalViews(); + const auto eIdxGlobal = this->asImp_().problem().gridGeometry().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.localDofIndex()]; + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else if (!this->elementIsGhost()) + { + 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.localDofIndex()]; + + // assemble the coupling blocks for coupled models (does nothing if not coupled) + maybeAssembleCouplingBlocks(residual); + } + else + { + // Treatment of ghost elements + assert(this->elementIsGhost()); + + // handle dofs per codimension + const auto& gridGeometry = this->asImp_().problem().gridGeometry(); + Dune::Hybrid::forEach(std::make_integer_sequence<int, dim>{}, [&](auto d) + { + constexpr int codim = dim - d; + const auto& localCoeffs = gridGeometry.feCache().get(this->element().type()).localCoefficients(); + for (int idx = 0; idx < localCoeffs.size(); ++idx) + { + const auto& localKey = localCoeffs.localKey(idx); + + // skip if we are not handling this codim right now + if (localKey.codim() != codim) + continue; + + // do not change the non-ghost entities + auto entity = this->element().template subEntity<codim>(localKey.subEntity()); + if (entity.partitionType() == Dune::InteriorEntity || entity.partitionType() == Dune::BorderEntity) + continue; + + // WARNING: this only works if the mapping from codim+subEntity to + // global dofIndex is unique (on dof per entity of this codim). + // For more general mappings, we should use a proper local-global mapping here. + // For example through dune-functions. + const auto dofIndex = gridGeometry.dofMapper().index(entity); + + // this might be a vector-valued dof + using BlockType = typename JacobianMatrix::block_type; + BlockType &J = jac[dofIndex][dofIndex]; + for (int j = 0; j < BlockType::rows; ++j) + J[j][j] = 1.0; + + // set residual for the ghost dof + res[dofIndex] = 0; + } + }); + } + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + + // if a periodic dof has Dirichlet values also apply the same Dirichlet values to the other dof + if (this->asImp_().problem().gridGeometry().dofOnPeriodicBoundary(scvI.dofIndex())) + { + const auto periodicDof = this->asImp_().problem().gridGeometry().periodicallyMappedDof(scvI.dofIndex()); + res[periodicDof][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + const auto end = jac[periodicDof].end(); + for (auto it = jac[periodicDof].begin(); it != end; ++it) + (*it) = periodicDof != it.index() ? 0.0 : 1.0; + } + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + */ + void assembleJacobian(JacobianMatrix& jac, GridVariables& gridVariables) + { + this->asImp_().bindLocalViews(); + this->asImp_().assembleJacobianAndResidualImpl(jac, gridVariables); // forward to the internal implementation + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + auto& row = jac[scvI.dofIndex()]; + for (auto col = row.begin(); col != row.end(); ++col) + row[col.index()][eqIdx] = 0.0; + + jac[scvI.dofIndex()][scvI.dofIndex()][eqIdx][pvIdx] = 1.0; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Assemble the residual only + */ + template <class ResidualVector> + void assembleResidual(ResidualVector& res) + { + this->asImp_().bindLocalViews(); + const auto residual = this->evalLocalResidual(); + + for (const auto& scv : scvs(this->fvGeometry())) + res[scv.dofIndex()] += residual[scv.localDofIndex()]; + + auto applyDirichlet = [&] (const auto& scvI, + const auto& dirichletValues, + const auto eqIdx, + const auto pvIdx) + { + res[scvI.dofIndex()][eqIdx] = this->curElemVolVars()[scvI].priVars()[pvIdx] - dirichletValues[pvIdx]; + }; + + this->asImp_().enforceDirichletConstraints(applyDirichlet); + } + + //! Enforce Dirichlet constraints + template<typename ApplyFunction> + void enforceDirichletConstraints(const ApplyFunction& applyDirichlet) + { + // enforce Dirichlet boundary conditions + this->asImp_().evalDirichletBoundaries(applyDirichlet); + // take care of internal Dirichlet constraints (if enabled) + this->asImp_().enforceInternalDirichletConstraints(applyDirichlet); + } + + /*! + * \brief Evaluates Dirichlet boundaries + */ + template< typename ApplyDirichletFunctionType > + void evalDirichletBoundaries(ApplyDirichletFunctionType applyDirichlet) + { + // enforce Dirichlet boundaries by overwriting partial derivatives with 1 or 0 + // and set the residual to (privar - dirichletvalue) + if (this->elemBcTypes().hasDirichlet()) + { + for (const auto& scvI : scvs(this->fvGeometry())) + { + const auto bcTypes = this->elemBcTypes().get(this->fvGeometry(), scvI); + if (bcTypes.hasDirichlet()) + { + const auto dirichletValues = this->asImp_().problem().dirichlet(this->element(), scvI); + + // set the Dirichlet conditions in residual and jacobian + for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) + { + if (bcTypes.isDirichlet(eqIdx)) + { + const auto pvIdx = bcTypes.eqToDirichletIndex(eqIdx); + assert(0 <= pvIdx && pvIdx < numEq); + applyDirichlet(scvI, dirichletValues, eqIdx, pvIdx); + } + } + } + } + } + } + + /*! + * \brief Update the coupling context for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template<class... Args> + void maybeUpdateCouplingContext(Args&&...) {} + + /*! + * \brief Update the additional domain derivatives for coupled models. + * \note This does nothing per default (not a coupled model). + */ + template<class... Args> + void maybeEvalAdditionalDomainDerivatives(Args&&...) {} +}; + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief An assembler for Jacobian and residual contribution per element (CVFE methods) + * \tparam TypeTag The TypeTag + * \tparam diffMethod The differentiation method to residual compute derivatives + * \tparam implicit Specifies whether the time discretization is implicit or not not (i.e. explicit) + * \tparam Implementation via CRTP + */ +template<class TypeTag, class Assembler, DiffMethod diffMethod = DiffMethod::numeric, bool implicit = true, class Implementation = void> +class CVFELocalAssembler; + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using numeric differentiation and implicit time discretization + */ +template<class TypeTag, class Assembler, class Implementation> +class CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/true, Implementation> +: public CVFELocalAssemblerBase<TypeTag, Assembler, + Detail::CVFE::Impl<Implementation, CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true, Implementation>>, + true> +{ + using ThisType = CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, true, Implementation>; + using ParentType = CVFELocalAssemblerBase<TypeTag, Assembler, Detail::CVFE::Impl<Implementation, ThisType>, true>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>; + using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>; + + static constexpr int numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq(); + static constexpr int dim = GetPropType<TypeTag, Properties::GridGeometry>::GridView::dimension; + + static constexpr bool enableGridFluxVarsCache + = GridVariables::GridFluxVariablesCache::cachingEnabled; + static constexpr bool solutionDependentFluxVarsCache + = GridVariables::GridFluxVariablesCache::FluxVariablesCache::isSolDependent; + +public: + + using LocalResidual = GetPropType<TypeTag, Properties::LocalResidual>; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + template <class PartialReassembler = DefaultPartialReassembler> + ElementResidualVector assembleJacobianAndResidualImpl(JacobianMatrix& A, GridVariables& gridVariables, + const PartialReassembler* partialReassembler = nullptr) + { + // get some aliases for convenience + const auto& element = this->element(); + const auto& fvGeometry = this->fvGeometry(); + const auto& curSol = this->asImp_().curSol(); + + auto&& curElemVolVars = this->curElemVolVars(); + auto&& elemFluxVarsCache = this->elemFluxVarsCache(); + + // get the vector of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // if all volvars in the stencil have to be updated or if it's enough to only update the + // volVars for the scv whose associated dof has been deflected + static const bool updateAllVolVars = getParamFromGroup<bool>( + this->asImp_().problem().paramGroup(), "Assembly.BoxVolVarsDependOnAllElementDofs", false + ); + + // create the element solution + auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry()); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(fvGeometry.numScv()); + + Detail::VolVarsDeflectionHelper deflectionHelper( + [&] (const auto& scv) -> VolumeVariables& { + return this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv); + }, + fvGeometry, + updateAllVolVars + ); + + // calculation of the derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + deflectionHelper.setCurrent(scv); + + // calculate derivatives w.r.t to the privars at the dof at hand + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + + auto evalResiduals = [&](Scalar priVar) + { + // update the volume variables and compute element residual + elemSol[scv.localDofIndex()][pvIdx] = priVar; + deflectionHelper.deflect(elemSol, scv, this->asImp_().problem()); + if constexpr (solutionDependentFluxVarsCache) + { + elemFluxVarsCache.update(element, fvGeometry, curElemVolVars); + if constexpr (enableGridFluxVarsCache) + gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars); + } + this->asImp_().maybeUpdateCouplingContext(scv, elemSol, pvIdx); + return this->evalLocalResidual(); + }; + + // derive the residuals numerically + static const NumericEpsilon<Scalar, numEq> eps_{this->asImp_().problem().paramGroup()}; + static const int numDiffMethod = getParamFromGroup<int>(this->asImp_().problem().paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalResiduals, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origResiduals, + eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + for (const auto& scvJ : scvs(fvGeometry)) + { + // don't add derivatives for green dofs + if (!partialReassembler + || partialReassembler->dofColor(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.localDofIndex()][eqIdx]; + } + } + } + + // restore the original state of the scv's volume variables + deflectionHelper.restore(scv); + + // restore the original element solution + elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx]; + this->asImp_().maybeUpdateCouplingContext(scv, elemSol, pvIdx); + } + } + + // restore original state of the flux vars cache in case of global caching. + // In the case of local caching this is obsolete because the elemFluxVarsCache used here goes out of scope after this. + if constexpr (enableGridFluxVarsCache) + gridVariables.gridFluxVarsCache().updateElement(element, fvGeometry, curElemVolVars); + + // evaluate additional derivatives that might arise from the coupling (no-op if not coupled) + this->asImp_().maybeEvalAdditionalDomainDerivatives(origResiduals, A, gridVariables); + + return origResiduals; + } + +}; // implicit CVFEAssembler with numeric Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using numeric differentiation and explicit time discretization + */ +template<class TypeTag, class Assembler, class Implementation> +class CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, /*implicit=*/false, Implementation> +: public CVFELocalAssemblerBase<TypeTag, Assembler, + Detail::CVFE::Impl<Implementation, CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false, Implementation>>, + false> +{ + using ThisType = CVFELocalAssembler<TypeTag, Assembler, DiffMethod::numeric, false, Implementation>; + using ParentType = CVFELocalAssemblerBase<TypeTag, Assembler, Detail::CVFE::Impl<Implementation, ThisType>, false>; + using Scalar = GetPropType<TypeTag, Properties::Scalar>; + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using VolumeVariables = GetPropType<TypeTag, Properties::VolumeVariables>; + using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>; + + static constexpr int numEq = GetPropType<TypeTag, Properties::ModelTraits>::numEq(); + static constexpr int dim = GetPropType<TypeTag, Properties::GridGeometry>::GridView::dimension; + +public: + + using LocalResidual = GetPropType<TypeTag, Properties::LocalResidual>; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + 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(); + const auto& curSol = this->asImp_().curSol(); + auto&& curElemVolVars = this->curElemVolVars(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + const auto origStorageResiduals = this->evalLocalStorageResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // create the element solution + auto elemSol = elementSolution(element, curSol, fvGeometry.gridGeometry()); + + // create the vector storing the partial derivatives + ElementResidualVector partialDerivs(fvGeometry.numScv()); + + // calculation of the derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + auto& curVolVars = this->getVolVarAccess(gridVariables.curGridVolVars(), curElemVolVars, scv); + const VolumeVariables origVolVars(curVolVars); + + // calculate derivatives w.r.t to the privars at the dof at hand + for (int pvIdx = 0; pvIdx < numEq; pvIdx++) + { + partialDerivs = 0.0; + + auto evalStorage = [&](Scalar priVar) + { + // auto partialDerivsTmp = partialDerivs; + elemSol[scv.localDofIndex()][pvIdx] = priVar; + curVolVars.update(elemSol, this->asImp_().problem(), element, scv); + return this->evalLocalStorageResidual(); + }; + + // derive the residuals numerically + static const NumericEpsilon<Scalar, numEq> eps_{this->asImp_().problem().paramGroup()}; + static const int numDiffMethod = getParamFromGroup<int>(this->asImp_().problem().paramGroup(), "Assembly.NumericDifferenceMethod"); + NumericDifferentiation::partialDerivative(evalStorage, elemSol[scv.localDofIndex()][pvIdx], partialDerivs, origStorageResiduals, + eps_(elemSol[scv.localDofIndex()][pvIdx], pvIdx), numDiffMethod); + + // update the global stiffness matrix with the current partial derivatives + 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[dofIdx][dofIdx][eqIdx][pvIdx] += partialDerivs[scv.localDofIndex()][eqIdx]; + } + + // restore the original state of the scv's volume variables + curVolVars = origVolVars; + + // restore the original element solution + elemSol[scv.localDofIndex()][pvIdx] = curSol[scv.dofIndex()][pvIdx]; + // TODO additional dof dependencies + } + } + + return origResiduals; + } +}; // explicit CVFEAssembler with numeric Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using analytic differentiation and implicit time discretization + */ +template<class TypeTag, class Assembler, class Implementation> +class CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/true, Implementation> +: public CVFELocalAssemblerBase<TypeTag, Assembler, + Detail::CVFE::Impl<Implementation, CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true, Implementation>>, + true> +{ + using ThisType = CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, true, Implementation>; + using ParentType = CVFELocalAssemblerBase<TypeTag, Assembler, Detail::CVFE::Impl<Implementation, ThisType>, true>; + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>; + +public: + + using LocalResidual = GetPropType<TypeTag, Properties::LocalResidual>; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + 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(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + const auto& elemFluxVarsCache = this->elemFluxVarsCache(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + // TODO if e.g. porosity depends on all dofs in the element, we would have off-diagonal matrix entries!? + if (!this->assembler().isStationaryProblem()) + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // add source term derivatives + this->localResidual().addSourceDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + // localJacobian[scvIdx][otherScvIdx][eqIdx][priVarIdx] of the fluxes + for (const auto& scvf : scvfs(fvGeometry)) + { + if (!scvf.boundary()) + { + // add flux term derivatives + this->localResidual().addFluxDerivatives(A, + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + + // the boundary gets special treatment to simplify + // for the user + else + { + const auto& insideScv = fvGeometry.scv(scvf.insideScvIdx()); + if (this->elemBcTypes().get(fvGeometry, insideScv).hasNeumann()) + { + // add flux term derivatives + this->localResidual().addRobinFluxDerivatives(A[insideScv.dofIndex()], + problem, + element, + fvGeometry, + curElemVolVars, + elemFluxVarsCache, + scvf); + } + } + } + + return origResiduals; + } + +}; // implicit CVFEAssembler with analytic Jacobian + +/*! + * \ingroup Assembly + * \ingroup CVFEDiscretization + * \brief Control volume finite element local assembler using analytic differentiation and explicit time discretization + */ +template<class TypeTag, class Assembler, class Implementation> +class CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, /*implicit=*/false, Implementation> +: public CVFELocalAssemblerBase<TypeTag, Assembler, + Detail::CVFE::Impl<Implementation, CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false, Implementation>>, + false> +{ + using ThisType = CVFELocalAssembler<TypeTag, Assembler, DiffMethod::analytic, false, Implementation>; + using ParentType = CVFELocalAssemblerBase<TypeTag, Assembler, Detail::CVFE::Impl<Implementation, ThisType>, false>; + using GridVariables = GetPropType<TypeTag, Properties::GridVariables>; + using JacobianMatrix = GetPropType<TypeTag, Properties::JacobianMatrix>; + +public: + + using LocalResidual = GetPropType<TypeTag, Properties::LocalResidual>; + using ElementResidualVector = typename LocalResidual::ElementResidualVector; + using ParentType::ParentType; + + /*! + * \brief Computes the derivatives with respect to the given element and adds them + * to the global matrix. + * + * \return The element residual at the current solution. + */ + 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(); + const auto& problem = this->asImp_().problem(); + const auto& curElemVolVars = this->curElemVolVars(); + + // get the vecor of the actual element residuals + const auto origResiduals = this->evalLocalResidual(); + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // // + // Calculate derivatives of all dofs in stencil with respect to the dofs in the element. In the // + // neighboring elements we do so by computing the derivatives of the fluxes which depend on the // + // actual element. In the actual element we evaluate the derivative of the entire residual. // + // // + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculation of the source and storage derivatives + for (const auto& scv : scvs(fvGeometry)) + { + // dof index and corresponding actual pri vars + const auto dofIdx = scv.dofIndex(); + const auto& volVars = curElemVolVars[scv]; + + // derivative of this scv residual w.r.t the d.o.f. of the same scv (because of mass lumping) + // only if the problem is instationary we add derivative of storage term + this->localResidual().addStorageDerivatives(A[dofIdx][dofIdx], + problem, + element, + fvGeometry, + volVars, + scv); + } + + return origResiduals; + } + +}; // explicit CVFEAssembler with analytic Jacobian + +} // end namespace Dumux + +#endif