From cd375a872ef59a17cfd8c95bdc360031c680bab1 Mon Sep 17 00:00:00 2001 From: Mathis Kelm <mathis.kelm@iws.uni-stuttgart.de> Date: Wed, 13 Dec 2023 14:26:52 +0100 Subject: [PATCH] [boxdfm] remove corner storage in scv/scvf --- dumux/experimental/ode/odesolver.hh | 226 ++++++++++++++++++ .../boxdfm/fvelementgeometry.hh | 64 +++-- .../boxdfm/subcontrolvolume.hh | 46 +--- .../boxdfm/subcontrolvolumeface.hh | 60 ++--- 4 files changed, 307 insertions(+), 89 deletions(-) create mode 100644 dumux/experimental/ode/odesolver.hh diff --git a/dumux/experimental/ode/odesolver.hh b/dumux/experimental/ode/odesolver.hh new file mode 100644 index 0000000000..bd1fc227c1 --- /dev/null +++ b/dumux/experimental/ode/odesolver.hh @@ -0,0 +1,226 @@ +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +#include <cmath> +#include <cassert> +#include <iostream> +#include <algorithm> +#include <utility> +#include <memory> +#include <array> + +#include <dune/common/float_cmp.hh> +#include <dune/common/exceptions.hh> + +#include <dumux/io/format.hh> +#include <dumux/common/initialize.hh> +#include <dumux/experimental/common/variables.hh> +#include <dumux/nonlinear/newtonsolver.hh> + +#include <dumux/experimental/timestepping/timelevel.hh> +#include <dumux/experimental/timestepping/multistagemethods.hh> +#include <dumux/experimental/timestepping/multistagetimestepper.hh> + +namespace Dumux::Experimental { + +class ScalarAssembler +{ + +public: + using Scalar = double; + using SolutionVector = Scalar; + using ResidualType = Scalar; + using JacobianMatrix = Scalar; + using Variables = Experimental::Variables<SolutionVector>; + using StageParams = Experimental::MultiStageParams<Scalar>; + + void setLinearSystem() {} + JacobianMatrix& jacobian() { return jac_; } + ResidualType& residual() { return res_; } + + void assembleResidual(const Variables& vars) + { + if (stageParams_->size() != spatialResiduals_.size()) + DUNE_THROW(Dune::InvalidStateException, "Wrong number of residuals"); + + // assemble residual components depending on current solution + assembleResiduals_(vars, + temporalResiduals_.back(), + spatialResiduals_.back() + ); + + // assemble residual for current stage solver + res_ = 0.0; + for (std::size_t k = 0; k < stageParams_->size(); ++k) + { + if (!stageParams_->skipTemporal(k)) + res_ += stageParams_->temporalWeight(k)*temporalResiduals_[k]; + if (!stageParams_->skipSpatial(k)) + res_ += stageParams_->spatialWeight(k)*spatialResiduals_[k]; + } + } + + void assembleJacobianAndResidual(const Variables& vars) + { + assembleResidual(vars); + jac_ = 1.0; + } + + void prepareStage(Variables& variables, + std::shared_ptr<const StageParams> params) + { + const auto curStage = params->size() - 1; + std::cout << "Assembler is preparing stage " << curStage << std::endl; + const auto prevStage = stageParams_ ? stageParams_->size() - 1 : 0; + if (curStage != prevStage+1) + DUNE_THROW(Dune::InvalidStateException, + "Can only prepare stages consecutively (current stage: " << curStage + << ", previous stage: " << prevStage << ")"); + + stageParams_ = params; + + // in the first stage, also assemble the old residual + if (curStage == 1) + { + // we update the time level of the given grid variables + const auto t = params->timeAtStage(0); + const auto prevT = params->timeAtStage(0); + const auto dtFraction = params->timeStepFraction(0); + variables.updateTime(Experimental::TimeLevel{t, prevT, dtFraction}); + + // allocate memory for residuals of stage 0 + spatialResiduals_.emplace_back(0.0); + temporalResiduals_.emplace_back(0.0); + + // assemble stage 0 residuals + assembleResiduals_(variables, + temporalResiduals_.back(), + spatialResiduals_.back() + ); + } + + // we update the time level of the given grid variables + const auto t = params->timeAtStage(curStage); + const auto prevT = params->timeAtStage(0); + const auto dtFraction = params->timeStepFraction(curStage); + variables.updateTime(Experimental::TimeLevel{t, prevT, dtFraction}); + + // allocate memory for residuals of the upcoming stage + spatialResiduals_.emplace_back(0.0); + temporalResiduals_.emplace_back(0.0); + } + + void clearStages() + { + std::cout << "Assembler is clearing stage data " << std::endl; + spatialResiduals_.clear(); + temporalResiduals_.clear(); + stageParams_.reset(); + } + +private: + void assembleResiduals_(const Variables& variables, + ResidualType& temporal, + ResidualType& spatial) const + { + using std::exp; + temporal = variables.dofs(); + spatial = -exp(variables.timeLevel().current()); + } + + ResidualType res_; + JacobianMatrix jac_; + std::vector<ResidualType> spatialResiduals_; + std::vector<ResidualType> temporalResiduals_; + std::shared_ptr<const StageParams> stageParams_; +}; + +class ScalarLinearSolver +{ +public: + void setResidualReduction(double residualReduction) {} + + bool solve(const double& A, double& x, const double& b) const + { x = b/A; return true; } + + double norm(const double residual) const + { + using std::abs; + return abs(residual); + } +}; + +} // end namespace Dumux + +int main(int argc, char* argv[]) +{ + using namespace Dumux; + + // maybe initialize MPI and/or multithreading backend + Dumux::initialize(argc, argv); + + using Assembler = ScalarAssembler; + using LinearSolver = ScalarLinearSolver; + using NewtonSolver = NewtonSolver<Assembler, LinearSolver, DefaultPartialReassembler>; + + auto assembler = std::make_shared<Assembler>(); + auto linearSolver = std::make_shared<LinearSolver>(); + auto newtonSolver = std::make_shared<NewtonSolver>(assembler, linearSolver); + + // initial solution and variables + using Scalar = typename Assembler::Scalar; + using Variables = typename Assembler::Variables; + using SolutionVector = typename Variables::SolutionVector; + + const auto exact = [] (const Scalar t) + { + using std::exp; + return exp(t) - 1.0; + }; + + const auto computeError = [&] (const Variables& vars) + { + using std::abs; + const auto time = vars.timeLevel().current(); + const auto exactSol = exact(time); + const auto absErr = abs(vars.dofs()-exactSol); + const auto relErr = absErr/exactSol; + return std::make_pair(absErr, relErr); + }; + + const auto testIntegration = [&] (auto method, Scalar expectedRelError) + { + std::cout << "\n-- Integration with " << method->name() << ":\n\n"; + SolutionVector x = 0.0; + Variables vars(x); + + using TimeStepper = Experimental::MultiStageTimeStepper<NewtonSolver>; + TimeStepper timeStepper(newtonSolver, method); + + const Scalar dt = 0.01; + timeStepper.step(vars, /*time*/0.0, dt); + + const auto [abs, rel] = computeError(vars); + std::cout << "\n" + << "-- Summary\n" + << "-- ===========================\n" + << "-- Exact solution: " << Fmt::format("{:.4e}\n", exact(dt)) + << "-- Discrete solution: " << Fmt::format("{:.4e}\n", vars.dofs()) + << "-- Absolute error: " << Fmt::format("{:.4e}\n", abs) + << "-- Relative error: " << Fmt::format("{:.4e}", rel) << std::endl; + + if (Dune::FloatCmp::ne(rel, expectedRelError, 0.01)) + DUNE_THROW(Dune::InvalidStateException, + "Detected deviation from reference > 1% for " << method->name()); + }; + + using namespace Experimental::MultiStage; + testIntegration(std::make_shared<ExplicitEuler<Scalar>>(), 4.9917e-03); + testIntegration(std::make_shared<ImplicitEuler<Scalar>>(), 5.0083e-03); + testIntegration(std::make_shared<Theta<Scalar>>(0.5), 8.3333e-06); + testIntegration(std::make_shared<DIRKThirdOrderAlexander<Scalar>>(), 7.9214e-09); + testIntegration(std::make_shared<RungeKuttaExplicitFourthOrder<Scalar>>(), 3.4829e-12); + + return 0; +} diff --git a/dumux/porousmediumflow/boxdfm/fvelementgeometry.hh b/dumux/porousmediumflow/boxdfm/fvelementgeometry.hh index e564d16526..39bac01b7b 100644 --- a/dumux/porousmediumflow/boxdfm/fvelementgeometry.hh +++ b/dumux/porousmediumflow/boxdfm/fvelementgeometry.hh @@ -171,20 +171,34 @@ public: const Element& element() const { return *element_; } - // suppress warnings due to current implementation - // these interfaces should be used! - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - //! Create the geometry of a given sub control volume typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const - { return scv.geometry(); } + { + using ScvGeometry = typename SubControlVolume::Traits::Geometry; + if (scv.isOnFracture()) + DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " + "because the number of known corners is insufficient. " + "You can do this manually by extract the corners from this scv " + "and extrude them by the corresponding aperture. "); + + const typename GG::GeometryHelper geometryHelper(element().geometry()); + const auto corners = geometryHelper.getScvCorners(scv.index()); + return ScvGeometry(Dune::GeometryTypes::cube(ScvGeometry::mydimension), corners); + } //! Create the geometry of a given sub control volume face typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const - { return scvf.geometry(); } - - #pragma GCC diagnostic pop + { + using ScvfGeometry = typename SubControlVolumeFace::Traits::Geometry; + if (scvf.isOnFracture()) + DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " + "because the number of known corners is insufficient. " + "You can do this manually by extracting the corners from this scv " + "and extruding them by the corresponding aperture. "); + const typename GG::GeometryHelper geometryHelper(element().geometry()); + const auto corners = geometryHelper.getScvfCorners(scvf.indexInElement()); + return ScvfGeometry(Dune::GeometryTypes::cube(ScvfGeometry::mydimension), corners); + } private: const GridGeometry* gridGeometryPtr_; @@ -327,20 +341,34 @@ public: const Element& element() const { return *element_; } - // suppress warnings due to current implementation - // these interfaces should be used! - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - //! Create the geometry of a given sub control volume typename SubControlVolume::Traits::Geometry geometry(const SubControlVolume& scv) const - { return scv.geometry(); } + { + using ScvGeometry = typename SubControlVolume::Traits::Geometry; + if (scv.isOnFracture()) + DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " + "because the number of known corners is insufficient. " + "You can do this manually by extract the corners from this scv " + "and extrude them by the corresponding aperture. "); + + const GeometryHelper geometryHelper(element().geometry()); + const auto corners = geometryHelper.getScvCorners(scv.index()); + return ScvGeometry(Dune::GeometryTypes::cube(ScvGeometry::mydimension), corners); + } //! Create the geometry of a given sub control volume face typename SubControlVolumeFace::Traits::Geometry geometry(const SubControlVolumeFace& scvf) const - { return scvf.geometry(); } - - #pragma GCC diagnostic pop + { + using ScvfGeometry = typename SubControlVolumeFace::Traits::Geometry; + if (scvf.isOnFracture()) + DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " + "because the number of known corners is insufficient. " + "You can do this manually by extracting the corners from this scv " + "and extruding them by the corresponding aperture. "); + const GeometryHelper geometryHelper(element().geometry()); + const auto corners = geometryHelper.getScvfCorners(scvf.indexInElement()); + return ScvfGeometry(Dune::GeometryTypes::cube(ScvfGeometry::mydimension), corners); + } private: diff --git a/dumux/porousmediumflow/boxdfm/subcontrolvolume.hh b/dumux/porousmediumflow/boxdfm/subcontrolvolume.hh index cf8504ea5a..38a7c2d5d0 100644 --- a/dumux/porousmediumflow/boxdfm/subcontrolvolume.hh +++ b/dumux/porousmediumflow/boxdfm/subcontrolvolume.hh @@ -69,7 +69,6 @@ class BoxDfmSubControlVolume using LocalIndexType = typename T::LocalIndexType; using Scalar = typename T::Scalar; using GlobalPosition = typename T::GlobalPosition; - using CornerStorage = typename T::CornerStorage; enum { dim = Geometry::mydimension }; static_assert(dim == 2 || dim == 3, "Box-Dfm sub-control volume only implemented in 2d or 3d"); @@ -88,22 +87,22 @@ public: GridIndexType elementIndex, GridIndexType dofIndex) : isFractureScv_(false) - , corners_(geometryHelper.getScvCorners(scvIdx)) , center_(0.0) - , volume_(Dumux::convexPolytopeVolume<T::dim>( - Dune::GeometryTypes::cube(T::dim), - [&](unsigned int i){ return corners_[i]; }) - ) , elementIndex_(elementIndex) , vIdxLocal_(scvIdx) , elemLocalScvIdx_(scvIdx) , dofIndex_(dofIndex) , facetIdx_(0) { + auto corners = geometryHelper.getScvCorners(scvIdx); + dofPosition_ = corners[0]; + volume_ = Dumux::convexPolytopeVolume<T::dim>( + Dune::GeometryTypes::cube(T::dim), + [&](unsigned int i){ return corners[i]; }); // compute center point - for (const auto& corner : corners_) + for (const auto& corner : corners) center_ += corner; - center_ /= corners_.size(); + center_ /= corners.size(); } /*! @@ -126,7 +125,6 @@ public: GridIndexType elementIndex, GridIndexType dofIndex) : isFractureScv_(true) - , corners_() , center_(0.0) , volume_(0.0) , elementIndex_(elementIndex) @@ -135,21 +133,17 @@ public: , dofIndex_(dofIndex) , facetIdx_(elemLocalFacetIdx) { - // copy corners auto corners = geometryHelper.getBoundaryScvfCorners(intersection.indexInInside(), indexInIntersection); - const auto numCorners = corners.size(); - corners_.resize(numCorners); - for (unsigned int i = 0; i < numCorners; ++i) - corners_[i] = corners[i]; + dofPosition_ = corners[0]; // compute volume and scv center volume_ = Dumux::convexPolytopeVolume<T::dim-1>( Dune::GeometryTypes::cube(T::dim-1), - [&](unsigned int i){ return corners_[i]; } + [&](unsigned int i){ return corners[i]; } ); - for (const auto& corner : corners_) + for (const auto& corner : corners) center_ += corner; - center_ /= corners_.size(); + center_ /= corners.size(); } //! The center of the sub control volume @@ -178,7 +172,7 @@ public: // The position of the dof this scv is embedded in (list is defined such that first entry is vertex itself) const GlobalPosition& dofPosition() const - { return corners_[0]; } + { return dofPosition_; } //! The global index of the element this scv is embedded in GridIndexType elementIndex() const @@ -188,23 +182,9 @@ public: bool isOnFracture() const { return isFractureScv_; } - //! The geometry of the sub control volume - // e.g. for integration - [[deprecated("Will be removed after 3.7. Use fvGeometry.geometry(scv).")]] - Geometry geometry() const - { - if (isFractureScv_) - DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " - "because the number of known corners is insufficient. " - "You can do this manually by extract the corners from this scv " - "and extrude them by the corresponding aperture. "); - - return Geometry(Dune::GeometryTypes::cube(dim), corners_); - } - private: bool isFractureScv_; - CornerStorage corners_; + GlobalPosition dofPosition_; GlobalPosition center_; Scalar volume_; GridIndexType elementIndex_; diff --git a/dumux/porousmediumflow/boxdfm/subcontrolvolumeface.hh b/dumux/porousmediumflow/boxdfm/subcontrolvolumeface.hh index 85fb5af05d..4fecd088bc 100644 --- a/dumux/porousmediumflow/boxdfm/subcontrolvolumeface.hh +++ b/dumux/porousmediumflow/boxdfm/subcontrolvolumeface.hh @@ -69,7 +69,6 @@ class BoxDfmSubControlVolumeFace using LocalIndexType = typename T::LocalIndexType; using Scalar = typename T::Scalar; using GlobalPosition = typename T::GlobalPosition; - using CornerStorage = typename T::CornerStorage; using Geometry = typename T::Geometry; using BoundaryFlag = typename T::BoundaryFlag; @@ -89,13 +88,7 @@ public: const typename Element::Geometry& elemGeometry, GridIndexType scvfIndex, std::vector<LocalIndexType>&& scvIndices) - : corners_(geometryHelper.getScvfCorners(scvfIndex)) - , center_(0.0) - , unitOuterNormal_(geometryHelper.normal(corners_, scvIndices)) - , area_(Dumux::convexPolytopeVolume<T::dim-1>( - Dune::GeometryTypes::cube(T::dim-1), - [&](unsigned int i){ return corners_[i]; }) - ) + : center_(0.0) , scvfIndex_(scvfIndex) , scvIndices_(std::move(scvIndices)) , boundary_(false) @@ -103,9 +96,15 @@ public: , boundaryFlag_{} , facetIdx_(0) { - for (const auto& corner : corners_) + auto corners = geometryHelper.getScvfCorners(scvfIndex); + unitOuterNormal_ = geometryHelper.normal(corners, scvIndices_); + area_ = Dumux::convexPolytopeVolume<T::dim-1>( + Dune::GeometryTypes::cube(T::dim-1), + [&](unsigned int i){ return corners[i]; }); + + for (const auto& corner : corners) center_ += corner; - center_ /= corners_.size(); + center_ /= corners.size(); } //! Constructor for boundary scvfs @@ -116,13 +115,8 @@ public: LocalIndexType indexInIntersection, GridIndexType scvfIndex, std::vector<LocalIndexType>&& scvIndices) - : corners_(geometryHelper.getBoundaryScvfCorners(intersection.indexInInside(), indexInIntersection)) - , center_(0.0) + : center_(0.0) , unitOuterNormal_(intersection.centerUnitOuterNormal()) - , area_(Dumux::convexPolytopeVolume<T::dim-1>( - Dune::GeometryTypes::cube(T::dim-1), - [&](unsigned int i){ return corners_[i]; }) - ) , scvfIndex_(scvfIndex) , scvIndices_(std::move(scvIndices)) , boundary_(true) @@ -130,9 +124,13 @@ public: , boundaryFlag_{intersection} , facetIdx_(0) { - for (const auto& corner : corners_) + auto corners = geometryHelper.getBoundaryScvfCorners(intersection.indexInInside(), indexInIntersection); + area_ = Dumux::convexPolytopeVolume<T::dim-1>( + Dune::GeometryTypes::cube(T::dim-1), + [&](unsigned int i){ return corners[i]; }); + for (const auto& corner : corners) center_ += corner; - center_ /= corners_.size(); + center_ /= corners.size(); } //! Constructor for inner fracture scvfs @@ -144,8 +142,7 @@ public: GridIndexType scvfIndex, std::vector<LocalIndexType>&& scvIndices, bool boundary) - : corners_(geometryHelper.getFractureScvfCorners(intersection.indexInInside(), indexInIntersection)) - , center_(0.0) + : center_(0.0) , scvfIndex_(scvfIndex) , scvIndices_(std::move(scvIndices)) , boundary_(boundary) @@ -153,21 +150,22 @@ public: , boundaryFlag_{intersection} , facetIdx_(intersection.indexInInside()) { + auto corners = geometryHelper.getFractureScvfCorners(intersection.indexInInside(), indexInIntersection); // The area here is given in meters. In order to // get the right dimensions, the user has to provide // the appropriate aperture in the problem (via an extrusion factor) if (T::dim == 3) - area_ = (corners_[1]-corners_[0]).two_norm(); + area_ = (corners[1]-corners[0]).two_norm(); else if (T::dim == 2) area_ = 1.0; // obtain the unit normal vector - unitOuterNormal_ = geometryHelper.fractureNormal(corners_, intersection, indexInIntersection); + unitOuterNormal_ = geometryHelper.fractureNormal(corners, intersection, indexInIntersection); // compute the scvf center - for (const auto& corner : corners_) + for (const auto& corner : corners) center_ += corner; - center_ /= corners_.size(); + center_ /= corners.size(); } //! The center of the sub control volume face @@ -224,21 +222,7 @@ public: return static_cast<std::size_t>(!boundary()); } - //! The geometry of the sub control volume face - [[deprecated("Will be removed after 3.7. Use fvGeometry.geometry(scvf).")]] - Geometry geometry() const - { - if (isFractureScvf_) - DUNE_THROW(Dune::InvalidStateException, "The geometry object cannot be defined for fracture scvs " - "because the number of known corners is insufficient. " - "You can do this manually by extract the corners from this scv " - "and extrude them by the corresponding aperture. "); - - return Geometry(Dune::GeometryTypes::cube(Geometry::mydimension), corners_); - } - private: - CornerStorage corners_; GlobalPosition center_; GlobalPosition unitOuterNormal_; Scalar area_; -- GitLab