Skip to content

Commit

Permalink
Merge branch 'UnitTestsForFixedTimeStepping' into 'master'
Browse files Browse the repository at this point in the history
Unit tests for FixedTimeStepping

See merge request ogs/ogs!4998
  • Loading branch information
endJunction committed Jun 2, 2024
2 parents f547d40 + eaeba6a commit e26316e
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 29 deletions.
8 changes: 7 additions & 1 deletion NumLib/TimeStepping/Algorithms/CreateFixedTimeStepping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ std::unique_ptr<TimeStepAlgorithm> createFixedTimeStepping(
OGS_FATAL("no timesteps have been given");
}

std::vector<std::pair<std::size_t, double>> repeat_dt_pairs;
std::vector<RepeatDtPair> repeat_dt_pairs;
for (auto const pair : range)
{
repeat_dt_pairs.emplace_back(
Expand All @@ -49,6 +49,12 @@ std::unique_ptr<TimeStepAlgorithm> createFixedTimeStepping(
//! \ogs_file_param{prj__time_loop__processes__process__time_stepping__FixedTimeStepping__timesteps__pair__delta_t}
pair.getConfigParameter<double>("delta_t"));
}
if (!FixedTimeStepping::areRepeatDtPairsValid(repeat_dt_pairs))
{
OGS_FATAL(
"CreateFixedTimeStepping: invalid specification of (repeat, "
"delta_t) pairs");
}

return std::make_unique<FixedTimeStepping>(
t_initial, t_end, repeat_dt_pairs, fixed_times_for_output);
Expand Down
42 changes: 30 additions & 12 deletions NumLib/TimeStepping/Algorithms/FixedTimeStepping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,24 +141,18 @@ void incorporateFixedTimesForOutput(
}

FixedTimeStepping::FixedTimeStepping(
double t0, double tn,
std::vector<std::pair<std::size_t, double>> const& repeat_dt_pairs,
double t0, double tn, std::vector<RepeatDtPair> const& repeat_dt_pairs,
std::vector<double> const& fixed_times_for_output)
: TimeStepAlgorithm(t0, tn)
{
double t_curr = _t_initial;

if (!areRepeatDtPairsValid(repeat_dt_pairs))
{
OGS_FATAL("FixedTimeStepping: Couldn't construct object from data");
}
for (auto const& [repeat, delta_t] : repeat_dt_pairs)
{
if (repeat == 0)
{
OGS_FATAL("<repeat> is zero.");
}
if (delta_t <= 0.0)
{
OGS_FATAL("timestep <delta_t> is <= 0.0.");
}

if (t_curr <= _t_end)
{
t_curr = addTimeIncrement(_dt_vector, repeat, delta_t, t_curr);
Expand All @@ -168,7 +162,7 @@ FixedTimeStepping::FixedTimeStepping(
// append last delta_t until t_end is reached
if (t_curr <= _t_end)
{
auto const delta_t = repeat_dt_pairs.back().second;
auto const delta_t = std::get<1>(repeat_dt_pairs.back());
auto const repeat =
static_cast<std::size_t>(std::ceil((_t_end - t_curr) / delta_t));
addTimeIncrement(_dt_vector, repeat, delta_t, t_curr);
Expand Down Expand Up @@ -231,4 +225,28 @@ std::tuple<bool, double> FixedTimeStepping::next(
return std::make_tuple(true, dt);
}

bool FixedTimeStepping::areRepeatDtPairsValid(
std::vector<RepeatDtPair> const& repeat_dt_pairs)
{
if (repeat_dt_pairs.empty())
{
return false;
}

for (auto const& [repeat, delta_t] : repeat_dt_pairs)
{
if (repeat == 0)
{
ERR("FixedTimeStepping: <repeat> is zero.");
return false;
}
if (delta_t <= 0.0)
{
ERR("FixedTimeStepping: timestep <delta_t> is <= 0.0.");
return false;
}
}
return true;
}

} // namespace NumLib
12 changes: 8 additions & 4 deletions NumLib/TimeStepping/Algorithms/FixedTimeStepping.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

namespace NumLib
{
using RepeatDtPair = std::tuple<std::size_t, double>;

/**
* \brief Fixed time stepping algorithm
*
Expand Down Expand Up @@ -46,15 +48,17 @@ class FixedTimeStepping final : public TimeStepAlgorithm
* Constructor with user-specified time step sizes including additional time
* steps for output.
*/
FixedTimeStepping(
double t0, double tn,
std::vector<std::pair<std::size_t, double>> const& repeat_dt_pairs,
std::vector<double> const& fixed_times_for_output);
FixedTimeStepping(double t0, double tn,
std::vector<RepeatDtPair> const& repeat_dt_pairs,
std::vector<double> const& fixed_times_for_output);

std::tuple<bool, double> next(double solution_error, int number_iterations,
NumLib::TimeStep& ts_previous,
NumLib::TimeStep& ts_current) override;

static bool areRepeatDtPairsValid(
std::vector<RepeatDtPair> const& repeat_dt_pairs);

private:
/// a vector of time step sizes
std::vector<double> _dt_vector;
Expand Down
197 changes: 197 additions & 0 deletions Tests/NumLib/TestFixedTimeStepping.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* \file
* \copyright
* Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org)
* Distributed under a Modified BSD License.
* See accompanying file LICENSE.txt or
* http://www.opengeosys.org/project/license
*
*/

#include <gtest/gtest.h>

#include <autocheck/autocheck.hpp>
#include <numeric>

#include "NumLib/TimeStepping/Algorithms/FixedTimeStepping.h"

namespace ac = autocheck;

class NumLibFixedTimeStepping : public ::testing::Test
{
public:
NumLibFixedTimeStepping()
{
double_classifier.trivial([](const std::vector<double>& time_step_sizes)
{ return time_step_sizes.empty(); });
double_classifier.collect(
[](const std::vector<double>& time_step_sizes)
{
return time_step_sizes.size() == 1
? "of test cases with one time step size"
: "of test cases with more than one time step size "
"entry";
});

pair_classifier.trivial(
[](const std::vector<NumLib::RepeatDtPair>& pairs)
{ return pairs.size() == 1; });
pair_classifier.collect(
[](const std::vector<NumLib::RepeatDtPair>& pairs)
{
return pairs.size() == 1
? "of test cases with one pair of RepeatDtPair"
: "of test cases with more than one pair of "
"RepeatDtPair";
});
}

protected:
ac::gtest_reporter gtest_reporter;
ac::classifier<std::vector<double>> double_classifier;
ac::classifier<std::vector<NumLib::RepeatDtPair>> pair_classifier;
};

TEST_F(NumLibFixedTimeStepping, EmptyRepeatDtPairs)
{
std::vector<NumLib::RepeatDtPair> empty;
ASSERT_FALSE(NumLib::FixedTimeStepping::areRepeatDtPairsValid(empty));
}

TEST_F(NumLibFixedTimeStepping, RepeatZeroDtPairs)
{
std::vector<NumLib::RepeatDtPair> zero_repeat_dt_pair_vec{{0, 1.0}};
ASSERT_FALSE(NumLib::FixedTimeStepping::areRepeatDtPairsValid(
zero_repeat_dt_pair_vec));
}

std::vector<double> transformTimesToDts(std::vector<double> const& times)
{
std::vector<double> dts(times.size());
std::adjacent_difference(times.begin(), times.end(), dts.begin());
return dts;
}

std::vector<NumLib::RepeatDtPair> transformToRepeatDtPair(
std::vector<double> const& dts)
{
std::vector<NumLib::RepeatDtPair> repeat_dt_pairs;
std::transform(dts.begin(),
dts.end(),
std::back_inserter(repeat_dt_pairs),
[](auto const dt) { return std::tuple(1, dt); });
return repeat_dt_pairs;
}

TEST_F(NumLibFixedTimeStepping, next)
{
auto test = [](std::vector<double>& expected_time_points) -> bool
{
double const t_initial = expected_time_points.front();

expected_time_points.erase(expected_time_points.begin());

double const t_end = expected_time_points.back();
auto dts = transformTimesToDts(expected_time_points);
dts.front() -= t_initial;
auto const repeat_dt_pair = transformToRepeatDtPair(dts);
NumLib::FixedTimeStepping fixed_time_stepping{
t_initial, t_end, repeat_dt_pair, {}};

NumLib::TimeStep ts_dummy(0, 0, 0);
NumLib::TimeStep ts_current(0, t_initial, 0);
for (auto const& expected_time_point : expected_time_points)
{
auto [is_next, step_size] =
fixed_time_stepping.next(0.0 /* solution_error */,
0 /* number_of_iterations */,
ts_dummy,
ts_current);
// this only happens if the last time step was processed or the
// current time is already at the end time up to machine precision
if (!is_next && step_size != 0.0)
{
return false;
}
// if the current time plus the computed step size minus the
// expected time is larger than the minimal time step size then the
// step size should be larger
// if next is true then the step
if (is_next && std::abs((ts_current.current() + step_size) -
expected_time_point) >
NumLib::TimeStep::minimalTimeStepSize)
{
return false;
}
// if next is true then the step size should be larger than zero
if (is_next && step_size == 0.0)
{
return false;
}
ts_current += step_size;
}
return ts_current.timeStepNumber() == dts.size();
};

auto ordered_list_generator = ac::ordered_list(ac::generator<double>());
auto time_points = ac::make_arbitrary(ordered_list_generator);
// generated list must not be empty
time_points.discard_if([](std::vector<double> const& xs)
{ return xs.size() <= 1; });

ac::check<std::vector<double>>(
test, 1000, time_points, gtest_reporter, double_classifier);
}

TEST_F(NumLibFixedTimeStepping, next_StaticTest)
{
std::vector<double> expected_time_points{};
for (int i = 0; i < 101; ++i)
{
expected_time_points.push_back(i * 1e-2);
}

std::vector<double> fixed_output_times{};
for (int i = 0; i < 10; ++i)
{
fixed_output_times.push_back(i * 1e-1);
}

double const t_initial = expected_time_points.front();

expected_time_points.erase(expected_time_points.begin());

double const t_end = expected_time_points.back();
auto dts = transformTimesToDts(expected_time_points);
dts.front() -= t_initial;
auto const repeat_dt_pair = transformToRepeatDtPair(dts);
NumLib::FixedTimeStepping fixed_time_stepping{
t_initial, t_end, repeat_dt_pair, fixed_output_times};

NumLib::TimeStep ts_dummy(0, 0, 0);
NumLib::TimeStep ts_current(0, t_initial, 0);
for (auto const& expected_time_point : expected_time_points)
{
auto [is_next, step_size] =
fixed_time_stepping.next(0.0 /* solution_error */,
0 /* number_of_iterations */,
ts_dummy,
ts_current);
// this only happens if the last time step was processed or the
// current time is already at the end time up to machine precision
ASSERT_FALSE(!is_next && step_size != 0.0);

// if the current time plus the computed step size minus the
// expected time is larger than the minimal time step size then the
// step size should be larger
// if next is true then the step
ASSERT_FALSE(is_next && std::abs((ts_current.current() + step_size) -
expected_time_point) >
NumLib::TimeStep::minimalTimeStepSize);

// if next is true then the step size should be larger than zero
ASSERT_FALSE(is_next && step_size == 0.0);
ts_current += step_size;
}
ASSERT_EQ(ts_current.timeStepNumber(), dts.size());
}
7 changes: 3 additions & 4 deletions Tests/NumLib/TestTimeSteppingFixed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ TEST_F(NumLibTimeSteppingFixed_TimeSteps, HomogeneousDt)

TEST_F(NumLibTimeSteppingFixed_TimeSteps, DtVectorEqualSum)
{
std::vector<std::pair<std::size_t, double>> const repeat_dt = {{3, 10}};
std::vector<NumLib::RepeatDtPair> const repeat_dt = {{3, 10}};
NumLib::FixedTimeStepping algorithm(1, 31, repeat_dt, {});

checkExpectedTimes(algorithm, {1, 11, 21, 31});
}

TEST_F(NumLibTimeSteppingFixed_TimeSteps, DtVectorLessThanSum)
{
std::vector<std::pair<std::size_t, double>> const repeat_dt = {
std::vector<NumLib::RepeatDtPair> const repeat_dt = {
{1, 5}, {1, 10}, {1, 20}};
NumLib::FixedTimeStepping algorithm(1, 31, repeat_dt, {});

Expand All @@ -116,8 +116,7 @@ TEST_F(NumLibTimeSteppingFixed_TimeSteps, DtVectorLessThanSum)

TEST_F(NumLibTimeSteppingFixed_TimeSteps, DtVectorGreaterThanSum)
{
std::vector<std::pair<std::size_t, double>> const repeat_dt = {{1, 5},
{3, 10}};
std::vector<NumLib::RepeatDtPair> const repeat_dt = {{1, 5}, {3, 10}};
NumLib::FixedTimeStepping algorithm(1, 31, repeat_dt, {});

checkExpectedTimes(algorithm, {1, 6, 16, 26, 31});
Expand Down
17 changes: 9 additions & 8 deletions Tests/ProcessLib/TestProcessLibOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ class ProcessLibOutputDataSpecification : public ::testing::Test
TEST_F(ProcessLibOutputDataSpecification,
FixedOutputTimesAndEmptyRepeatsEachSteps)
{
auto test = [](std::vector<double>& fixed_output_times) -> bool
auto test = [](std::vector<double>& fixed_output_time_points) -> bool
{
bool const output_residuals = false;

ProcessLib::OutputDataSpecification output_data_specification{
{} /* output_variables */,
std::vector<double>{fixed_output_times},
std::vector<double>{fixed_output_time_points},
{} /* repeats_each_steps */,
output_residuals};

Expand All @@ -77,24 +77,25 @@ TEST_F(ProcessLibOutputDataSpecification,
}
}

if (fixed_output_times.empty())
if (fixed_output_time_points.empty())
{
return true;
}
// test time generated by random number generator that isn't included
// into fixed_output_times
// into fixed_output_time_points
static const auto seed = static_cast<std::mt19937::result_type>(
std::chrono::system_clock::now().time_since_epoch().count());
static std::mt19937 generator{seed};
auto const [min, max] = std::minmax_element(fixed_output_times.begin(),
fixed_output_times.end());
auto const [min, max] = std::minmax_element(
fixed_output_time_points.begin(), fixed_output_time_points.end());
static std::uniform_real_distribution<double> distribution(*min, *max);

for (int i = 0; i < 100; i++)
{
auto const random_time = distribution(generator);
if (std::find(fixed_output_times.begin(), fixed_output_times.end(),
random_time) == fixed_output_times.end())
if (std::find(fixed_output_time_points.begin(),
fixed_output_time_points.end(),
random_time) == fixed_output_time_points.end())
{
if (output_data_specification.isOutputStep(1, random_time))
{
Expand Down

0 comments on commit e26316e

Please sign in to comment.