diff --git a/test/freeflow/navierstokes/errors.hh b/test/freeflow/navierstokes/errors.hh index b77874e995a07f705f1171f6f7d2de27753313be..89e872a83a18b759e29ba2e5b90d953b4485c1ef 100644 --- a/test/freeflow/navierstokes/errors.hh +++ b/test/freeflow/navierstokes/errors.hh @@ -26,11 +26,10 @@ #include <vector> #include <cmath> -#include <fstream> #include <dune/common/indices.hh> -#include <dune/common/fvector.hh> #include <dumux/discretization/extrusion.hh> #include <dumux/io/format.hh> +#include <dumux/geometry/diameter.hh> namespace Dumux { @@ -197,7 +196,7 @@ public: // print auxiliary file with the number of dofs std::ofstream logFileDofs(name_ + "_dofs.csv", std::ios::trunc); logFileDofs << "cc dofs, face dofs, all dofs\n" - << numCCDofs << numFaceDofs << numCCDofs + numFaceDofs << "\n"; + << numCCDofs << ", " << numFaceDofs << ", " << numCCDofs + numFaceDofs << "\n"; // clear error file std::ofstream logFile(name_ + ".csv", std::ios::trunc); @@ -301,24 +300,25 @@ void convergenceTestAppendErrors(std::shared_ptr<Problem> problem, namespace NavierStokesTest { /*! - * \brief Compute errors between an analytical solution and the numerical approximation + * \brief Compute errors between an analytical solution and the numerical approximation for momentum eq. */ -template<class MomentumProblem, class MassProblem, class Scalar = double> -class Errors +template<class Problem, class Scalar = double> +class ErrorsSubProblem { + static constexpr bool isMomentumProblem = Problem::isMomentumProblem(); static constexpr int dim - = std::decay_t<decltype(std::declval<MomentumProblem>().gridGeometry())>::GridView::dimension; + = std::decay_t<decltype(std::declval<Problem>().gridGeometry())>::GridView::dimension; - using ErrorVector = Dune::FieldVector<Scalar, dim+1>; + using ErrorVector = typename std::conditional_t< isMomentumProblem, + Dune::FieldVector<Scalar, dim>, + Dune::FieldVector<Scalar, 1> >; public: template<class SolutionVector> - Errors(std::shared_ptr<const MomentumProblem> momentumProblem, - std::shared_ptr<const MassProblem> massProblem, + ErrorsSubProblem(std::shared_ptr<const Problem> problem, const SolutionVector& curSol, Scalar time = 0.0) - : momentumProblem_(momentumProblem) - , massProblem_(massProblem) + : problem_(problem) { calculateErrors_(curSol, time); } /*! @@ -343,6 +343,12 @@ public: //! Time corresponding to the error (returns 0 per default) Scalar time() const { return time_; } + //! Maximum diameter of primal grid elements + Scalar hMax() const { return hMax_; } + + //! Volume of domain + Scalar totalVolume() const { return totalVolume_; } + private: template<class SolutionVector> void calculateErrors_(const SolutionVector& curSol, Scalar time) @@ -351,7 +357,8 @@ private: time_ = time; // calculate helping variables - Scalar totalVolume = 0.0; + totalVolume_ = 0.0; + hMax_ = 0.0; ErrorVector sumReference(0.0); ErrorVector sumError(0.0); @@ -360,54 +367,66 @@ private: using namespace Dune::Indices; - // velocity errors + auto fvGeometry = localView(problem_->gridGeometry()); + for (const auto& element : elements(problem_->gridGeometry().gridView())) { - auto fvGeometry = localView(momentumProblem_->gridGeometry()); - for (const auto& element : elements(momentumProblem_->gridGeometry().gridView())) + hMax_ = std::max(hMax_, diameter(element.geometry())); + fvGeometry.bindElement(element); + for (const auto& scv : scvs(fvGeometry)) { - fvGeometry.bindElement(element); - for (const auto& scv : scvs(fvGeometry)) - { - using GridGeometry = std::decay_t<decltype(std::declval<MassProblem>().gridGeometry())>; - using Extrusion = Extrusion_t<GridGeometry>; - - // compute the pressure errors - using Indices = typename MomentumProblem::Indices; - const auto velIdx = Indices::velocity(scv.dofAxis()); - const auto analyticalSolution - = momentumProblem_->analyticalSolution(scv.dofPosition(), time)[velIdx]; - const auto numericalSolution - = curSol[_0][scv.dofIndex()][0]; - - const Scalar vError = absDiff_(analyticalSolution, numericalSolution); - const Scalar vReference = absDiff_(analyticalSolution, 0.0); + using GridGeometry = std::decay_t<decltype(std::declval<Problem>().gridGeometry())>; + using Extrusion = Extrusion_t<GridGeometry>; + totalVolume_ += Extrusion::volume(scv); - maxError[velIdx+1] = std::max(maxError[velIdx+1], vError); - maxReference[velIdx+1] = std::max(maxReference[velIdx+1], vReference); - sumError[velIdx+1] += vError * vError * Extrusion::volume(scv); - sumReference[velIdx+1] += vReference * vReference * Extrusion::volume(scv); + // velocity errors + if constexpr (isMomentumProblem) + { + if constexpr (GridGeometry::discMethod == DiscretizationMethods::fcstaggered) + { + // compute the velocity errors + using Indices = typename Problem::Indices; + const auto velIdx = Indices::velocity(scv.dofAxis()); + const auto analyticalSolution + = problem_->analyticalSolution(scv.dofPosition(), time)[velIdx]; + const auto numericalSolution + = curSol[scv.dofIndex()][0]; + + const Scalar vError = absDiff_(analyticalSolution, numericalSolution); + const Scalar vReference = absDiff_(analyticalSolution, 0.0); + + maxError[velIdx] = std::max(maxError[velIdx], vError); + maxReference[velIdx] = std::max(maxReference[velIdx], vReference); + sumError[velIdx] += vError * vError * Extrusion::volume(scv); + sumReference[velIdx] += vReference * vReference * Extrusion::volume(scv); + } + else if (GridGeometry::discMethod == DiscretizationMethods::fcdiamond) + { + for (int dirIdx = 0; dirIdx < dim; ++dirIdx) + { + const auto analyticalSolution + = problem_->analyticalSolution(scv.dofPosition(), time)[dirIdx]; + const auto numericalSolution + = curSol[scv.dofIndex()][dirIdx]; + + const Scalar vError = absDiff_(analyticalSolution, numericalSolution); + const Scalar vReference = absDiff_(analyticalSolution, 0.0); + + maxError[dirIdx] = std::max(maxError[dirIdx], vError); + maxReference[dirIdx] = std::max(maxReference[dirIdx], vReference); + sumError[dirIdx] += vError * vError * Extrusion::volume(scv); + sumReference[dirIdx] += vReference * vReference * Extrusion::volume(scv); + } + } } - } - } - - // pressure errors - { - auto fvGeometry = localView(massProblem_->gridGeometry()); - for (const auto& element : elements(massProblem_->gridGeometry().gridView())) - { - fvGeometry.bindElement(element); - for (const auto& scv : scvs(fvGeometry)) + // pressure errors + else { - using GridGeometry = std::decay_t<decltype(std::declval<MomentumProblem>().gridGeometry())>; - using Extrusion = Extrusion_t<GridGeometry>; - totalVolume += Extrusion::volume(scv); - // compute the pressure errors - using Indices = typename MassProblem::Indices; + using Indices = typename Problem::Indices; const auto analyticalSolution - = massProblem_->analyticalSolution(scv.dofPosition(), time)[Indices::pressureIdx]; + = problem_->analyticalSolution(scv.dofPosition(), time)[Indices::pressureIdx]; const auto numericalSolution - = curSol[_1][scv.dofIndex()][Indices::pressureIdx]; + = curSol[scv.dofIndex()][Indices::pressureIdx]; const Scalar pError = absDiff_(analyticalSolution, numericalSolution); const Scalar pReference = absDiff_(analyticalSolution, 0.0); @@ -423,7 +442,7 @@ private: // calculate errors for (int i = 0; i < ErrorVector::size(); ++i) { - l2Absolute_[i] = std::sqrt(sumError[i] / totalVolume); + l2Absolute_[i] = std::sqrt(sumError[i] / totalVolume_); l2Relative_[i] = std::sqrt(sumError[i] / sumReference[i]); lInfAbsolute_[i] = maxError[i]; lInfRelative_[i] = maxError[i] / maxReference[i]; @@ -434,16 +453,116 @@ private: T absDiff_(const T& a, const T& b) const { using std::abs; return abs(a-b); } - std::shared_ptr<const MomentumProblem> momentumProblem_; - std::shared_ptr<const MassProblem> massProblem_; + std::shared_ptr<const Problem> problem_; + + ErrorVector l2Absolute_; + ErrorVector l2Relative_; + ErrorVector lInfAbsolute_; + ErrorVector lInfRelative_; + Scalar time_; + Scalar hMax_; + Scalar totalVolume_; +}; + +/*! + * \brief Compute errors between an analytical solution and the numerical approximation + */ +template<class MomentumProblem, class MassProblem, class Scalar = double> +class Errors +{ + static constexpr int dim + = std::decay_t<decltype(std::declval<MomentumProblem>().gridGeometry())>::GridView::dimension; + + using ErrorVector = Dune::FieldVector<Scalar, dim+1>; + +public: + template<class SolutionVector> + Errors(std::shared_ptr<const MomentumProblem> momentumProblem, + std::shared_ptr<const MassProblem> massProblem, + const SolutionVector& curSol, + Scalar time = 0.0) + : momentumErrors_(momentumProblem, curSol[Dune::Indices::_0], time) + , massErrors_(massProblem, curSol[Dune::Indices::_1], time) + { update(curSol, time); } + + /*! + * \brief Computes errors between an analytical solution and the numerical approximation + * + * \param curSol The current solution vector + * \param time The current time + */ + template<class SolutionVector> + void update(const SolutionVector& curSol, Scalar time = 0.0) + { + momentumErrors_.update(curSol[Dune::Indices::_0], time); + massErrors_.update(curSol[Dune::Indices::_1], time); + + time_ = time; + + const auto& l2AbsoluteMomentum = momentumErrors_.l2Absolute(); + const auto& l2RelativeMomentum = momentumErrors_.l2Relative(); + const auto& lInfAbsoluteMomentum = momentumErrors_.lInfAbsolute(); + const auto& lInfRelativeMomentum = momentumErrors_.lInfRelative(); + + const auto& l2AbsoluteMass = massErrors_.l2Absolute(); + const auto& l2RelativeMass = massErrors_.l2Relative(); + const auto& lInfAbsoluteMass = massErrors_.lInfAbsolute(); + const auto& lInfRelativeMass = massErrors_.lInfRelative(); + + l2Absolute_[0] = l2AbsoluteMass[0]; + l2Relative_[0] = l2RelativeMass[0]; + lInfAbsolute_[0] = lInfAbsoluteMass[0]; + lInfRelative_[0] = lInfRelativeMass[0]; + + std::copy( l2AbsoluteMomentum.begin(), l2AbsoluteMomentum.end(), l2Absolute_.begin() + 1 ); + std::copy( l2RelativeMomentum.begin(), l2RelativeMomentum.end(), l2Relative_.begin() + 1 ); + std::copy( lInfAbsoluteMomentum.begin(), lInfAbsoluteMomentum.end(), lInfAbsolute_.begin() + 1 ); + std::copy( lInfRelativeMomentum.begin(), lInfRelativeMomentum.end(), lInfRelative_.begin() + 1 ); + + hMax_ = massErrors_.hMax(); + totalVolume_ = massErrors_.totalVolume(); + } + + //! The (absolute) discrete l2 error + const ErrorVector& l2Absolute() const { return l2Absolute_; } + //! The relative discrete l2 error (relative to the discrete l2 norm of the reference solution) + const ErrorVector& l2Relative() const { return l2Relative_; } + //! The (absolute) discrete l-infinity error + const ErrorVector& lInfAbsolute() const { return lInfAbsolute_; } + //! The relative discrete l-infinity error (relative to the discrete loo norm of the reference solution) + const ErrorVector& lInfRelative() const { return lInfRelative_; } + + //! Time corresponding to the error (returns 0 per default) + Scalar time() const { return time_; } + + //! Maximum diameter of primal grid elements + Scalar hMax() const { return hMax_; } + + //! Volume of domain + Scalar totalVolume() const { return totalVolume_; } + +private: + ErrorsSubProblem<MomentumProblem, Scalar> momentumErrors_; + ErrorsSubProblem<MassProblem, Scalar> massErrors_; ErrorVector l2Absolute_; ErrorVector l2Relative_; ErrorVector lInfAbsolute_; ErrorVector lInfRelative_; + Scalar time_; + Scalar hMax_; + Scalar totalVolume_; }; +template<class Problem, class SolutionVector> +ErrorsSubProblem(std::shared_ptr<Problem>, SolutionVector&&) +-> ErrorsSubProblem<Problem>; + +template<class Problem, class SolutionVector, class Scalar> +ErrorsSubProblem(std::shared_ptr<Problem>, SolutionVector&&, Scalar) +-> ErrorsSubProblem<Problem, Scalar>; + template<class MomentumProblem, class MassProblem, class SolutionVector> Errors(std::shared_ptr<MomentumProblem>, std::shared_ptr<MassProblem>, SolutionVector&&) -> Errors<MomentumProblem, MassProblem>; @@ -474,7 +593,7 @@ public: // print auxiliary file with the number of dofs std::ofstream logFileDofs(name_ + "_dofs.csv", std::ios::trunc); logFileDofs << "cc dofs, face dofs, all dofs\n" - << numCCDofs << numFaceDofs << numCCDofs + numFaceDofs << "\n"; + << numCCDofs << ", " << numFaceDofs << ", " << numCCDofs + numFaceDofs << "\n"; // clear error file std::ofstream logFile(name_ + ".csv", std::ios::trunc); @@ -509,7 +628,7 @@ private: using MomIndices = typename MomentumProblem::Indices; logFile << Fmt::format(", " + format, error[MassIndices::pressureIdx]); for (int dirIdx = 0; dirIdx < dim; ++dirIdx) - logFile << Fmt::format(", " + format, error[MomIndices::velocity(dirIdx)]); + logFile << Fmt::format(", " + format, error[MomIndices::velocity(dirIdx)+1]); } std::string name_; @@ -572,6 +691,50 @@ void convergenceTestAppendErrors(std::shared_ptr<MomentumProblem> momentumProble convergenceTestAppendErrors(logFile, momentumProblem, massProblem, errors); } +/*! + * \brief Append errors to the log file during a convergence test + */ +template<class MomentumProblem> +void convergenceTestAppendErrorsMomentum(std::ofstream& logFile, + std::shared_ptr<MomentumProblem> problem, + const ErrorsSubProblem<MomentumProblem>& errors) +{ + const auto numFaceDofs = problem->gridGeometry().numDofs(); + + logFile << Fmt::format( + "[ConvergenceTest] numFaceDofs = {}", + numFaceDofs + ); + + const auto print = [&](const auto& e, const std::string& name){ + using MomIndices = typename MomentumProblem::Indices; + logFile << Fmt::format( + " {0}(vx) = {1} {0}(vy) = {2}", + name, e[MomIndices::velocityXIdx], e[MomIndices::velocityYIdx] + ); + }; + + print(errors.l2Absolute(), "L2Abs"); + print(errors.l2Relative(), "L2Rel"); + print(errors.lInfAbsolute(), "LinfAbs"); + print(errors.lInfRelative(), "LinfRel"); + + logFile << "\n"; +} + + +/*! + * \brief Append errors to the log file during a convergence test + */ +template<class MomentumProblem> +void convergenceTestAppendErrorsMomentum(std::shared_ptr<MomentumProblem> problem, + const ErrorsSubProblem<MomentumProblem>& errors) +{ + const auto logFileName = problem->name() + ".log"; + std::ofstream logFile(logFileName, std::ios::app); + convergenceTestAppendErrorsMomentum(logFile, problem, errors); +} + } // end namespace NavierStokesTest } // end namespace Dumux