diff --git a/CHANGELOG.md b/CHANGELOG.md index cefbe74c9a3bb2c383ee6d138ab78b3e6131af36..3aa4e623a626a72f7cb6f4ed7388628cbccbf0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Differences Between DuMu<sup>x</sup> 3.8 and DuMu<sup>x</sup> 3.7 from the parameter input file. The constructor takes the expression and an array of variable names to be replaced by function arguments. The function can then be evaluated with the function values provided in the same order as the names where specified. An arbitrary number of arguments is supported. ExprTK support very complex expressions, see https://www.partow.net/programming/exprtk/. - __Time loop__: Fixed a bug when the time is chosen to be negative and/or the end time is set to zero. (Before we assume time is always positive and endtime never zero.). Moreover, time loops can now be constructed from and time step sizes set with `std::chrono::duration`. Finally, corner cases that may occur when using both periodic and manual check points in the `CheckPointTimeLoop` are now properly handled. +- __Chrono__: Added the conversion function `Dumux::Chrono::toSeconds` to parse strings representing numbers with trailing unit into a `std::chrono::duration`. This allows convenient construction of durations, for instance: `const auto tEnd = Dumux::Chrono::toSeconds(getParam("TimeLoop.TEnd"));`, where `TEnd = 12ms` would be valid content in the input file. - __Hyperelastic__: Added a hyperelastic model (large deformations) and a test (in geomechanics) - __Property system__: Added a function `inheritsFrom` that allows to query whether a type tag is present in the inheritance hierarchy of another type tag - __PDESolver__: The LinearPDESolver/NewtonSolver now has an `apply` method that returns a bool (true if converged) instead of throwing an exception diff --git a/dumux/io/chrono.hh b/dumux/io/chrono.hh new file mode 100644 index 0000000000000000000000000000000000000000..7b771007accb0e37541fe7304d24b8821b83ed59 --- /dev/null +++ b/dumux/io/chrono.hh @@ -0,0 +1,72 @@ +// -*- 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 InputOutput + * \brief Helper functions for working with std::chrono. + */ +#ifndef DUMUX_IO_CHRONO_HH +#define DUMUX_IO_CHRONO_HH + +#include <array> +#include <cctype> +#include <chrono> +#include <string> +#include <string_view> +#include <algorithm> + +#include <dune/common/exceptions.hh> + +namespace Dumux::Chrono { + +//! Try to construct a std::chrono::duration from a string +template<typename Rep, typename Period> +void toDuration(std::chrono::duration<Rep, Period>& duration, const std::string& s) +{ + using std::chrono::duration_cast; + using namespace std::chrono_literals; + using S = std::chrono::duration<Rep>; + constexpr std::array<std::pair<std::string_view, S>, 9> unitMap{{ + {"", S(1)}, // assume seconds if no unit is given + {"s", S(1)}, + {"ns", duration_cast<S>(std::chrono::nanoseconds(1))}, + {"us", duration_cast<S>(std::chrono::microseconds(1))}, + {"ms", duration_cast<S>(std::chrono::milliseconds(1))}, + {"min", duration_cast<S>(std::chrono::minutes(1))}, + {"h", duration_cast<S>(std::chrono::hours(1))}, + // After requiring cpp20, we can use the aliases in std::chrono + {"d", duration_cast<S>(std::chrono::hours(24))}, + {"y", duration_cast<S>(std::chrono::seconds(31556952))} // to match cpp20, see https://en.cppreference.com/w/cpp/chrono/duration + }}; + + const auto unitIt = std::find_if(s.rbegin(), s.rend(), [] (const auto& c) { return !std::isalpha(c); }); + const auto unitPos = s.size() - std::distance(s.rbegin(), unitIt); + const auto number = std::stod(s.substr(0, unitPos)); + const auto unit = s.substr(unitPos); + const auto conversion = [&] () { + const auto it = std::find_if(unitMap.begin(), unitMap.end(), [u=std::string_view{unit}] (const auto& p) { + return p.first == u; + }); + if (it == unitMap.end()) + DUNE_THROW(Dune::InvalidStateException, "Unsupported unit " << unit << "."); + return it->second; + } (); + duration = duration_cast<std::chrono::duration<Rep, Period>>(number*conversion); +} + +//! Try to construct an instance of std::chrono::seconds from a string including a unit suffix +template<typename Rep = double> +std::chrono::duration<Rep> toSeconds(const std::string& s) +{ + std::chrono::duration<Rep> result; + toDuration(result, s); + return result; +} + +} // end namespace Dumux::Chrono + +#endif diff --git a/examples/cahn_hilliard/doc/main.md b/examples/cahn_hilliard/doc/main.md index 1d42334b80f602504e740832fbe363eae8c8b316..1f30c44ce2288cb66f9508d9c2c98e7ddbd8bba8 100644 --- a/examples/cahn_hilliard/doc/main.md +++ b/examples/cahn_hilliard/doc/main.md @@ -44,6 +44,7 @@ We start in `main.cc` with the necessary header includes: #include <dumux/assembly/fvassembler.hh> #include <dune/grid/yaspgrid.hh> +#include <dumux/io/chrono.hh> #include <dumux/io/vtkoutputmodule.hh> #include <dumux/io/grid/gridmanager_yasp.hh> @@ -329,16 +330,16 @@ the time step size from the parameter tree (`params.input`) ```cpp auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>( - getParam<Scalar>("TimeLoop.TStart", 0.0), - getParam<Scalar>("TimeLoop.InitialTimeStepSize"), - getParam<Scalar>("TimeLoop.TEnd") + Chrono::toSeconds(getParam("TimeLoop.TStart", "0")), + Chrono::toSeconds(getParam("TimeLoop.InitialTimeStepSize")), + Chrono::toSeconds(getParam("TimeLoop.TEnd")) ); ``` We set the maximum time step size allowed in the adaptive time stepping scheme. ```cpp - timeLoop->setMaxTimeStepSize(getParam<Scalar>("TimeLoop.MaxTimeStepSize")); + timeLoop->setMaxTimeStepSize(Chrono::toSeconds(getParam("TimeLoop.MaxTimeStepSize"))); ``` Next, we choose the type of assembler, linear solver and PDE solver diff --git a/examples/cahn_hilliard/main.cc b/examples/cahn_hilliard/main.cc index 6ddde40f8b2aaaf10460f5fa2c18c267a815a2fc..ba61d24284320f0e97a4bd921bce21b3ed128e8b 100644 --- a/examples/cahn_hilliard/main.cc +++ b/examples/cahn_hilliard/main.cc @@ -40,6 +40,7 @@ #include <dumux/assembly/fvassembler.hh> #include <dune/grid/yaspgrid.hh> +#include <dumux/io/chrono.hh> #include <dumux/io/vtkoutputmodule.hh> #include <dumux/io/grid/gridmanager_yasp.hh> @@ -266,13 +267,13 @@ int main(int argc, char** argv) // We instantiate time loop using start and end time as well as // the time step size from the parameter tree (`params.input`) auto timeLoop = std::make_shared<CheckPointTimeLoop<Scalar>>( - getParam<Scalar>("TimeLoop.TStart", 0.0), - getParam<Scalar>("TimeLoop.InitialTimeStepSize"), - getParam<Scalar>("TimeLoop.TEnd") + Chrono::toSeconds(getParam("TimeLoop.TStart", "0")), + Chrono::toSeconds(getParam("TimeLoop.InitialTimeStepSize")), + Chrono::toSeconds(getParam("TimeLoop.TEnd")) ); // We set the maximum time step size allowed in the adaptive time stepping scheme. - timeLoop->setMaxTimeStepSize(getParam<Scalar>("TimeLoop.MaxTimeStepSize")); + timeLoop->setMaxTimeStepSize(Chrono::toSeconds(getParam("TimeLoop.MaxTimeStepSize"))); // Next, we choose the type of assembler, linear solver and PDE solver // and construct instances of these classes. diff --git a/examples/cahn_hilliard/params.input b/examples/cahn_hilliard/params.input index f9d41a960d4dd43dd177449a3ffdba6ea82425c4..05a95c190d0281650808b46488a10cc7c1fb1467 100644 --- a/examples/cahn_hilliard/params.input +++ b/examples/cahn_hilliard/params.input @@ -1,7 +1,7 @@ [TimeLoop] -TEnd = 1.0 -InitialTimeStepSize = 0.0025 -MaxTimeStepSize = 0.01 +TEnd = 1.0s +InitialTimeStepSize = 2.5ms +MaxTimeStepSize = 10ms [Grid] LowerLeft = 0 0 diff --git a/test/io/CMakeLists.txt b/test/io/CMakeLists.txt index 859b7c724d88874583ddd64b426cf24659828096..394a45a794f68f1318aef2572e468e8ff5418d7b 100644 --- a/test/io/CMakeLists.txt +++ b/test/io/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder # SPDX-License-Identifier: GPL-3.0-or-later +add_subdirectory(chrono) add_subdirectory(container) add_subdirectory(format) add_subdirectory(gnuplotinterface) diff --git a/test/io/chrono/CMakeLists.txt b/test/io/chrono/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..cac1ca0197c8d27e89946c8050a9033c1be32189 --- /dev/null +++ b/test/io/chrono/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +# SPDX-License-Identifier: GPL-3.0-or-later + +dumux_add_test(SOURCES test_chrono.cc + LABELS io unit) diff --git a/test/io/chrono/test_chrono.cc b/test/io/chrono/test_chrono.cc new file mode 100644 index 0000000000000000000000000000000000000000..b1cb2593aac50238f00bcda402b75a8403316fe6 --- /dev/null +++ b/test/io/chrono/test_chrono.cc @@ -0,0 +1,39 @@ +// +// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder +// SPDX-License-Identifier: GPL-3.0-or-later +// +#include <iostream> + +#include <dune/common/exceptions.hh> +#include <dumux/io/chrono.hh> + +int main(int argc, char* argv[]) +{ + using namespace Dumux; + using namespace std::chrono; + using dseconds = std::chrono::duration<double>; + using dmilliseconds = std::chrono::duration<double, std::milli>; + + // seconds (test scientific notation and real values) + if (Chrono::toSeconds("1") != seconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1s") != seconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1.0s") != seconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("42.42s") != dseconds(42.42)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1e3s") != dseconds(1.0e3)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("42.42e3s") != dseconds(42.42e3)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1e-3s") != milliseconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1.5e-3s") != dmilliseconds(1.5)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + + if (Chrono::toSeconds("1ms") != milliseconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1us") != microseconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1ns") != nanoseconds(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + + if (Chrono::toSeconds("1min") != minutes(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1h") != hours(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); + if (Chrono::toSeconds("1d") != hours(24)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); +#if __cplusplus >= 202002L + if (Chrono::toSeconds("1y") != years(1)) DUNE_THROW(Dune::InvalidStateException, "Wrong parse result"); +#endif + + return 0; +}