diff --git a/dumux/experimental/timestepping/newmarkbeta.hh b/dumux/experimental/timestepping/newmarkbeta.hh new file mode 100644 index 0000000000000000000000000000000000000000..507963d442eb54945326a6ec628c796f4a9c9f82 --- /dev/null +++ b/dumux/experimental/timestepping/newmarkbeta.hh @@ -0,0 +1,131 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +/*! + * \file + * \ingroup Experimental + * \brief Newmark-beta time integration scheme + */ + +#ifndef DUMUX_TIMESTEPPING_NEWMARK_BETA_HH +#define DUMUX_TIMESTEPPING_NEWMARK_BETA_HH + +#include <dumux/common/parameters.hh> + +namespace Dumux::Experimental { + +/*! + * \brief Newmark-beta time integration scheme + * \tparam Scalar The scalar type used for the solution + * \tparam Solution The solution type used for the variables + * \note Newmark (1959) "A method of computation for structural dynamics" + * Journal of the Engineering Mechanics Division, + * https://doi.org/10.1061/JMCEA3.0000098 + * \note This is typically used for PDEs of the form M*a + C*v + K*x = f + * with M the mass matrix, C the damping matrix, K the stiffness matrix, + * x the displacement, v the velocity and a the acceleration. + * An example is dynamic mechanics. + */ +template<class Scalar, class Solution> +class NewmarkBeta +{ + using Block = typename Solution::block_type; +public: + //! Construct the Newmark-beta time integration scheme + //! with the given β and γ parameters + NewmarkBeta(const Scalar beta, const Scalar gamma) + : beta_(beta) + , gamma_(gamma) + {} + + //! Construct the Newmark-beta time integration scheme + //! where parameters are read from the parameter file + NewmarkBeta() + : NewmarkBeta( + getParam<Scalar>("NewmarkBeta.Beta", 0.25), + getParam<Scalar>("NewmarkBeta.Gamma", 0.5) + ) {} + + //! Initialize the time integration scheme with the current solution + //! while setting the velocity and acceleration to zero + void initialize(const Solution& x) + { + x_ = x; + + // make sure the size is correct, but the values are zero + v_ = x; v_ = 0.0; + a_ = x; a_ = 0.0; + } + + //! Initialize the time integration scheme with the current solution + void initialize(const Solution& x, const Solution& v, const Solution& a) + { + x_ = x; + v_ = v; + a_ = a; + } + + //! Update with new position x + void update(const Scalar dt, const Solution& x) + { + for (std::size_t i = 0; i < x_.size(); ++i) + { + const auto xNew = x[i]; + const auto aNew = acceleration(i, dt, xNew); + const auto vNew = velocity(i, dt, xNew, aNew); + + x_[i] = xNew; + v_[i] = vNew; + a_[i] = aNew; + } + } + + //! new a in terms of the old x, v, a, the new x and the time step size + Block acceleration(const std::size_t index, const Scalar dt, const Block& x) const + { + const auto& x0 = x_[index]; + const auto& v0 = v_[index]; + const auto& a0 = a_[index]; + + return (x - x0 - dt*v0)/(beta_*dt*dt) + (beta_ - 0.5)/beta_*a0; + } + + //! new v in terms of the old v, a, the new x, a and the time step size + Block velocity(const std::size_t index, const Scalar dt, const Block& x, const Block& a) const + { + const auto& v0 = v_[index]; + const auto& a0 = a_[index]; + + return v0 + dt*((1.0-gamma_)*a0 + gamma_*a); + } + + //! new v in terms of the old v, a, the new x and the time step size + Block velocity(const std::size_t index, const Scalar dt, const Block& x) const + { + const auto a = acceleration(index, dt, x); + return velocity(index, dt, x, a); + } + + //! current position + Scalar position(const std::size_t index) const + { return x_[index]; } + + //! current velocity + Scalar velocity(const std::size_t index) const + { return v_[index]; } + + //! current acceleration + Scalar acceleration(const std::size_t index) const + { return a_[index]; } + +private: + Scalar beta_, gamma_; //!< Newmark-beta parameters + Solution x_, v_, a_; //!< current position, velocity and acceleration +}; + +} // end namespace Dumux::Experimental + +#endif diff --git a/test/experimental/timestepping/CMakeLists.txt b/test/experimental/timestepping/CMakeLists.txt index ea918317f3cfd30b56d49fd6b1907223d04722b9..d4da32cf5e4f17cdfbca62f86f9c5ac40b798cd2 100644 --- a/test/experimental/timestepping/CMakeLists.txt +++ b/test/experimental/timestepping/CMakeLists.txt @@ -2,3 +2,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later dumux_add_test(SOURCES test_timestepmethods.cc LABELS unit timestepping experimental) +dumux_add_test(SOURCES test_newmarkbeta.cc LABELS unit timestepping experimental) diff --git a/test/experimental/timestepping/test_newmarkbeta.cc b/test/experimental/timestepping/test_newmarkbeta.cc new file mode 100644 index 0000000000000000000000000000000000000000..ebbf49d71d418594c671d03bb9ac667cf41eb81f --- /dev/null +++ b/test/experimental/timestepping/test_newmarkbeta.cc @@ -0,0 +1,86 @@ +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +#include <config.h> + +#include <iostream> +#include <vector> + +#include <dune/istl/bvector.hh> +#include <dune/common/exceptions.hh> + +#include <dumux/common/parameters.hh> +#include <dumux/common/initialize.hh> +#include <dumux/nonlinear/findscalarroot.hh> +#include <dumux/io/gnuplotinterface.hh> + +#include <dumux/experimental/timestepping/newmarkbeta.hh> + +int main(int argc, char* argv[]) +{ + using namespace Dumux; + + // maybe initialize MPI and/or multithreading backend + Dumux::initialize(argc, argv); + + // integrate d^2x/dt^2 = -x with Newmark-beta scheme + Experimental::NewmarkBeta<double, Dune::BlockVector<double>> newmark; + Dune::BlockVector<double> x(1); x = -1.0; + Dune::BlockVector<double> v(1); v = 0.0; + Dune::BlockVector<double> a(1); a = 1.0; + newmark.initialize(x, v, a); + + const double dt = 0.1; + const double tEnd = 16*M_PI; + const int nSteps = tEnd/dt; + + const bool showPlot = getParam<bool>("Plot", false); + Dumux::GnuplotInterface<double> plotter(showPlot); + std::vector<double> tValues, xValues, xExact, vValues, vExact, aValues, aExact; + tValues.reserve(nSteps); + xValues.reserve(nSteps); xExact.reserve(nSteps); + vValues.reserve(nSteps); vExact.reserve(nSteps); + aValues.reserve(nSteps); aExact.reserve(nSteps); + + for (int i = 0; i < nSteps; ++i) + { + const double t = dt*i; + + // mimic a nonlinear solver by finding the root of the residual (d^2x/dt^2 + x = 0) + const auto residual = [&](const auto x){ return newmark.acceleration(0, dt, x) + x; }; + const auto xNew = findScalarRootNewton(x[0], residual); + + x[0] = xNew; + // update with the new position + // after the update we have the current position, velocity and acceleration + newmark.update(dt, x); + + tValues.push_back(t); + xValues.push_back(newmark.position(0)); + xExact.push_back(-std::cos(t)); + vValues.push_back(newmark.velocity(0)); + vExact.push_back(std::sin(t)); + aValues.push_back(newmark.acceleration(0)); + aExact.push_back(std::cos(t)); + + if (i % 50 == 0) + { + plotter.resetPlot(); + plotter.addDataSetToPlot(tValues, xValues, "x.dat", "w l"); + plotter.addDataSetToPlot(tValues, xExact, "xe.dat", "w l"); + plotter.addDataSetToPlot(tValues, vValues, "v.dat", "w l"); + plotter.addDataSetToPlot(tValues, vExact, "ve.dat", "w l"); + plotter.addDataSetToPlot(tValues, aValues, "a.dat", "w l"); + plotter.addDataSetToPlot(tValues, aExact, "ae.dat", "w l"); + plotter.plot("result"); + } + } + + // compare the final position with the exact solution + const double error = std::abs(x[0] + std::cos(tEnd)); + if (error > 0.006) + DUNE_THROW(Dune::Exception, "Integration error too large: " << error); + + return 0; +}