// -*- 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 . * *****************************************************************************/ /*! * \file * \brief Caculates the Jacobian of the local residual for fully-implicit models */ #ifndef DUMUX_IMPLICIT_LOCAL_JACOBIAN_HH #define DUMUX_IMPLICIT_LOCAL_JACOBIAN_HH #include #include #include #include #include "implicitproperties.hh" namespace Dumux { /*! * \ingroup ImplicitLocalJacobian * \brief Calculates the Jacobian of the local residual for fully-implicit models * * The default behavior is to use numeric differentiation, i.e. * forward or backward differences (2nd order), or central * differences (3rd order). The method used is determined by the * "NumericDifferenceMethod" property: * * - if the value of this property is smaller than 0, backward * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x) - f(x - \epsilon)}{\epsilon} * \f] * * - if the value of this property is 0, central * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x + \epsilon) - f(x - \epsilon)}{2 \epsilon} * \f] * * - if the value of this property is larger than 0, forward * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x + \epsilon) - f(x)}{\epsilon} * \f] * * Here, \f$ f \f$ is the residual function for all equations, \f$x\f$ * is the value of a sub-control volume's primary variable at the * evaluation point and \f$\epsilon\f$ is a small value larger than 0. */ template class ImplicitLocalJacobian { private: typedef typename GET_PROP_TYPE(TypeTag, LocalJacobian) Implementation; typedef typename GET_PROP_TYPE(TypeTag, LocalResidual) LocalResidual; typedef typename GET_PROP_TYPE(TypeTag, Problem) Problem; typedef typename GET_PROP_TYPE(TypeTag, Model) Model; typedef typename GET_PROP_TYPE(TypeTag, GridView) GridView; typedef typename GridView::template Codim<0>::Entity Element; typedef typename GridView::Traits::template Codim<0>::EntityPointer ElementPointer; typedef typename GET_PROP_TYPE(TypeTag, JacobianAssembler) JacobianAssembler; enum { numEq = GET_PROP_VALUE(TypeTag, NumEq), dim = GridView::dimension, Green = JacobianAssembler::Green }; typedef typename GET_PROP_TYPE(TypeTag, FVElementGeometry) FVElementGeometry; typedef typename GET_PROP_TYPE(TypeTag, VertexMapper) VertexMapper; typedef typename GET_PROP_TYPE(TypeTag, ElementSolutionVector) ElementSolutionVector; typedef typename GET_PROP_TYPE(TypeTag, PrimaryVariables) PrimaryVariables; typedef typename GET_PROP_TYPE(TypeTag, VolumeVariables) VolumeVariables; typedef typename GET_PROP_TYPE(TypeTag, ElementVolumeVariables) ElementVolumeVariables; typedef typename GET_PROP_TYPE(TypeTag, ElementBoundaryTypes) ElementBoundaryTypes; typedef typename GET_PROP_TYPE(TypeTag, Scalar) Scalar; typedef Dune::FieldMatrix MatrixBlock; typedef Dune::Matrix LocalBlockMatrix; enum { isBox = GET_PROP_VALUE(TypeTag, ImplicitIsBox) }; // copying a local jacobian is not a good idea ImplicitLocalJacobian(const ImplicitLocalJacobian &); public: ImplicitLocalJacobian() { numericDifferenceMethod_ = GET_PARAM_FROM_GROUP(TypeTag, int, Implicit, NumericDifferenceMethod); Valgrind::SetUndefined(problemPtr_); } /*! * \brief Initialize the local Jacobian object. * * At this point we can assume that everything has been allocated, * although some objects may not yet be completely initialized. * * \param problem The problem which we want to simulate. */ void init(Problem &problem) { problemPtr_ = &problem; localResidual_.init(problem); // assume cubes as elements with most vertices if (isBox) { A_.setSize(1< entity which we look at. */ void assemble(const Element &element) { // set the current grid element and update the element's // finite volume geometry elemPtr_ = &element; fvElemGeom_.update(gridView_(), element); reset_(); bcTypes_.update(problem_(), element_(), fvElemGeom_); // set the hints for the volume variables model_().setHints(element, prevVolVars_, curVolVars_); // update the secondary variables for the element at the last // and the current time levels prevVolVars_.update(problem_(), element_(), fvElemGeom_, true /* isOldSol? */); curVolVars_.update(problem_(), element_(), fvElemGeom_, false /* isOldSol? */); // update the hints of the model model_().updateCurHints(element, curVolVars_); // calculate the local residual localResidual().eval(element_(), fvElemGeom_, prevVolVars_, curVolVars_, bcTypes_); residual_ = localResidual().residual(); storageTerm_ = localResidual().storageTerm(); model_().updatePVWeights(element_(), curVolVars_); // calculate the local jacobian matrix int numRows, numCols; if (isBox) { numRows = numCols = fvElemGeom_.numScv; // resize for hanging nodes or lower dimensional grids if(numRows > 1< 1< 2*dim + 1) A_.setSize(numRows, numCols); } ElementSolutionVector partialDeriv(numRows); PrimaryVariables storageDeriv(0.0); for (int col = 0; col < numCols; col++) { for (int pvIdx = 0; pvIdx < numEq; pvIdx++) { asImp_().evalPartialDerivative_(partialDeriv, storageDeriv, col, pvIdx); // update the local stiffness matrix with the current partial // derivatives updateLocalJacobian_(col, pvIdx, partialDeriv, storageDeriv); } } } /*! * \brief Returns a reference to the object which calculates the * local residual. */ const LocalResidual &localResidual() const { return localResidual_; } /*! * \brief Returns a reference to the object which calculates the * local residual. */ LocalResidual &localResidual() { return localResidual_; } /*! * \brief Returns the Jacobian of the equations at subcontrolvolume i * to the primary variables at subcontrolvolume j. * * \param i The local subcontrolvolume index on which * the equations are defined * \param j The local subcontrolvolume index which holds * primary variables */ const MatrixBlock &mat(const int i, const int j) const { return A_[i][j]; } /*! * \brief Returns the Jacobian of the storage term at subcontrolvolume i. * * \param i The local subcontrolvolume index */ const MatrixBlock &storageJacobian(const int i) const { return storageJacobian_[i]; } /*! * \brief Returns the residual of the equations at subcontrolvolume i. * * \param i The local subcontrolvolume index on which * the equations are defined */ const PrimaryVariables &residual(const int i) const { return residual_[i]; } /*! * \brief Returns the storage term for subcontrolvolume i. * * \param i The local subcontrolvolume index on which * the equations are defined */ const PrimaryVariables &storageTerm(const int i) const { return storageTerm_[i]; } /*! * \brief Returns the epsilon value which is added and removed * from the current solution. * * \param scvIdx The local index of the element's subcontrolvolume for * which the local derivative ought to be calculated. * \param pvIdx The index of the primary variable which gets varied */ Scalar numericEpsilon(const int scvIdx, const int pvIdx) const { // define the base epsilon as the geometric mean of 1 and the // resolution of the scalar type. E.g. for standard 64 bit // floating point values, the resolution is about 10^-16 and // the base epsilon is thus approximately 10^-8. /* static const Scalar baseEps = Dumux::geometricMean(std::numeric_limits::epsilon(), 1.0); */ static const Scalar baseEps = 1e-10; assert(std::numeric_limits::epsilon()*1e4 < baseEps); // the epsilon value used for the numeric differentiation is // now scaled by the absolute value of the primary variable... Scalar priVar = this->curVolVars_[scvIdx].priVar(pvIdx); return baseEps*(std::abs(priVar) + 1.0); } protected: Implementation &asImp_() { return *static_cast(this); } const Implementation &asImp_() const { return *static_cast(this); } /*! * \brief Returns a reference to the problem. */ const Problem &problem_() const { Valgrind::CheckDefined(problemPtr_); return *problemPtr_; } /*! * \brief Returns a reference to the grid view. */ const GridView &gridView_() const { return problem_().gridView(); } /*! * \brief Returns a reference to the element. */ const Element &element_() const { Valgrind::CheckDefined(elemPtr_); return *elemPtr_; } /*! * \brief Returns a reference to the model. */ const Model &model_() const { return problem_().model(); } /*! * \brief Returns a reference to the jacobian assembler. */ const JacobianAssembler &jacAsm_() const { return model_().jacobianAssembler(); } /*! * \brief Returns a reference to the vertex mapper. */ const VertexMapper &vertexMapper_() const { return problem_().vertexMapper(); } /*! * \brief Reset the local jacobian matrix to 0 */ void reset_() { for (unsigned int i = 0; i < A_.N(); ++ i) { storageJacobian_[i] = 0.0; for (unsigned int j = 0; j < A_.M(); ++ j) { A_[i][j] = 0.0; } } } /*! * \brief Compute the partial derivatives to a primary variable at * an degree of freedom. * * This method can be overwritten by the implementation if a * better scheme than numerical differentiation is available. * * The default implementation of this method uses numeric * differentiation, i.e. forward or backward differences (2nd * order), or central differences (3rd order). The method used is * determined by the "NumericDifferenceMethod" property: * * - if the value of this property is smaller than 0, backward * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x) - f(x - \epsilon)}{\epsilon} * \f] * * - if the value of this property is 0, central * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x + \epsilon) - f(x - \epsilon)}{2 \epsilon} * \f] * * - if the value of this property is larger than 0, forward * differences are used, i.e.: * \f[ \frac{\partial f(x)}{\partial x} \approx \frac{f(x + \epsilon) - f(x)}{\epsilon} * \f] * * Here, \f$ f \f$ is the residual function for all equations, \f$x\f$ * is the value of a sub-control volume's primary variable at the * evaluation point and \f$\epsilon\f$ is a small value larger than 0. * * \param partialDeriv The vector storing the partial derivatives of all * equations * \param storageDeriv the mass matrix contributions * \param col The block column index of the degree of freedom * for which the partial derivative is calculated. * Box: a sub-control volume index. * Cell centered: a neighbor index. * \param pvIdx The index of the primary variable * for which the partial derivative is calculated */ void evalPartialDerivative_(ElementSolutionVector &partialDeriv, PrimaryVariables &storageDeriv, const int col, const int pvIdx) { int dofIdxGlobal; FVElementGeometry neighborFVGeom; ElementPointer neighbor(element_()); if (isBox) { #if DUNE_VERSION_NEWER(DUNE_COMMON, 2, 4) dofIdxGlobal = vertexMapper_().subIndex(element_(), col, dim); #else dofIdxGlobal = vertexMapper_().map(element_(), col, dim); #endif } else { neighbor = fvElemGeom_.neighbors[col]; neighborFVGeom.updateInner(*neighbor); #if DUNE_VERSION_NEWER(DUNE_COMMON, 2, 4) dofIdxGlobal = problemPtr_->elementMapper().index(*neighbor); #else dofIdxGlobal = problemPtr_->elementMapper().map(*neighbor); #endif } PrimaryVariables priVars(model_().curSol()[dofIdxGlobal]); VolumeVariables origVolVars(curVolVars_[col]); curVolVars_[col].setEvalPoint(&origVolVars); Scalar eps = asImp_().numericEpsilon(col, pvIdx); Scalar delta = 0; if (numericDifferenceMethod_ >= 0) { // we are not using backward differences, i.e. we need to // calculate f(x + \epsilon) // deflect primary variables priVars[pvIdx] += eps; delta += eps; // calculate the residual if (isBox) curVolVars_[col].update(priVars, problem_(), element_(), fvElemGeom_, col, false); else curVolVars_[col].update(priVars, problem_(), *neighbor, neighborFVGeom, /*scvIdx=*/0, false); localResidual().eval(element_(), fvElemGeom_, prevVolVars_, curVolVars_, bcTypes_); // store the residual and the storage term partialDeriv = localResidual().residual(); if (isBox || col == 0) storageDeriv = localResidual().storageTerm()[col]; } else { // we are using backward differences, i.e. we don't need // to calculate f(x + \epsilon) and we can recycle the // (already calculated) residual f(x) partialDeriv = residual_; if (isBox || col == 0) storageDeriv = storageTerm_[col]; } if (numericDifferenceMethod_ <= 0) { // we are not using forward differences, i.e. we // need to calculate f(x - \epsilon) // deflect the primary variables priVars[pvIdx] -= delta + eps; delta += eps; // calculate residual again if (isBox) curVolVars_[col].update(priVars, problem_(), element_(), fvElemGeom_, col, false); else curVolVars_[col].update(priVars, problem_(), *neighbor, neighborFVGeom, /*scvIdx=*/0, false); localResidual().eval(element_(), fvElemGeom_, prevVolVars_, curVolVars_, bcTypes_); partialDeriv -= localResidual().residual(); if (isBox || col == 0) storageDeriv -= localResidual().storageTerm()[col]; } else { // we are using forward differences, i.e. we don't need to // calculate f(x - \epsilon) and we can recycle the // (already calculated) residual f(x) partialDeriv -= residual_; if (isBox || col == 0) storageDeriv -= storageTerm_[col]; } // divide difference in residuals by the magnitude of the // deflections between the two function evaluation partialDeriv /= delta; storageDeriv /= delta; // restore the original state of the element's volume variables curVolVars_[col] = origVolVars; #if HAVE_VALGRIND for (unsigned i = 0; i < partialDeriv.size(); ++i) Valgrind::CheckDefined(partialDeriv[i]); #endif } /*! * \brief Updates the current local Jacobian matrix with the * partial derivatives of all equations in regard to the * primary variable 'pvIdx' at dof 'col' . */ void updateLocalJacobian_(const int col, const int pvIdx, const ElementSolutionVector &partialDeriv, const PrimaryVariables &storageDeriv) { // store the derivative of the storage term if (isBox || col == 0) { for (int eqIdx = 0; eqIdx < numEq; eqIdx++) { storageJacobian_[col][eqIdx][pvIdx] = storageDeriv[eqIdx]; } } for (int i = 0; i < fvElemGeom_.numScv; i++) { // Green vertices are not to be changed! if (!isBox || jacAsm_().vertexColor(element_(), i) != 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'. this->A_[i][col][eqIdx][pvIdx] = partialDeriv[i][eqIdx]; Valgrind::CheckDefined(this->A_[i][col][eqIdx][pvIdx]); } } } } const Element *elemPtr_; FVElementGeometry fvElemGeom_; ElementBoundaryTypes bcTypes_; // The problem we would like to solve Problem *problemPtr_; // secondary variables at the previous and at the current time // levels ElementVolumeVariables prevVolVars_; ElementVolumeVariables curVolVars_; LocalResidual localResidual_; LocalBlockMatrix A_; std::vector storageJacobian_; ElementSolutionVector residual_; ElementSolutionVector storageTerm_; int numericDifferenceMethod_; }; } #endif