OGS
FixedTimeStepping.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "FixedTimeStepping.h"
5
6#include <spdlog/fmt/ranges.h>
7
8#include <algorithm>
9#include <cassert>
10#include <limits>
11#include <numeric>
12
14
15namespace
16{
18[[maybe_unused]] NumLib::Time addTimeIncrement(std::vector<double>& delta_ts,
19 std::size_t const repeat,
20 double const delta_t,
21 NumLib::Time const t_curr)
22{
23 auto const new_size = delta_ts.size() + repeat;
24 try
25 {
26 delta_ts.resize(new_size, delta_t);
27 }
28 catch (std::exception const& e)
29 {
31 "Resize of the time steps vector failed for the requested new size "
32 "{:d}. Probably there is not enough memory ({:g} GiB "
33 "requested).\nThrown exception: {:s}",
34 new_size,
35 new_size * sizeof(double) / 1024. / 1024. / 1024.,
36 e.what());
37 }
38
39 // Multiplying dt * repeat is not the same as in the current
40 // implementation of the time loop, where the dt's are added.
41 // Therefore the sum of all dt is taken here.
42 return std::accumulate(end(delta_ts) - repeat, end(delta_ts), t_curr);
43}
44} // namespace
45
46namespace NumLib
47{
48std::size_t findDeltatInterval(Time const& t_initial,
49 std::vector<double> const& delta_ts,
50 Time const& fixed_output_time)
51{
52 if (fixed_output_time < t_initial)
53 {
54 return std::numeric_limits<std::size_t>::max();
55 }
56
57 auto timestepper_time = t_initial;
58 for (std::size_t k = 0; k < delta_ts.size(); ++k)
59 {
60 if (timestepper_time <= fixed_output_time &&
61 fixed_output_time < timestepper_time + delta_ts[k])
62 {
63 return k;
64 }
65 timestepper_time += delta_ts[k];
66 }
67
68 return std::numeric_limits<std::size_t>::max();
69}
70
72 NumLib::Time const t_initial, NumLib::Time const t_end,
73 std::vector<double>& delta_ts,
74 std::vector<double> const& fixed_times_for_output)
75{
76 if (fixed_times_for_output.empty())
77 {
78 return;
79 }
80
81 if (auto lower_bound = std::lower_bound(
82 begin(fixed_times_for_output), end(fixed_times_for_output),
83 t_initial, [](auto const time, NumLib::Time const& initial_time)
84 { return NumLib::Time(time) < initial_time; });
85 lower_bound != begin(fixed_times_for_output))
86 {
87 WARN(
88 "Request for output at times {}, but the simulation's start time "
89 "is {}. Output will be skipped.",
90 fmt::join(begin(fixed_times_for_output), lower_bound, ", "),
91 t_initial());
92 }
93
94 if (auto upper_bound =
95 std::upper_bound(begin(fixed_times_for_output),
96 end(fixed_times_for_output), t_end());
97 upper_bound != end(fixed_times_for_output))
98 {
99 WARN(
100 "Request for output at times {}, but simulation's end time is {}. "
101 "Output will be skipped.",
102 fmt::join(upper_bound, end(fixed_times_for_output), ", "),
103 t_end());
104 }
105
106 if (delta_ts.empty())
107 {
108 WARN("No timesteps specified.");
109 return;
110 }
111
112 // incorporate fixed output times into dts vector
113 for (auto const fixed_time_for_output : fixed_times_for_output)
114 {
115 auto const interval_number = findDeltatInterval(
116 t_initial, delta_ts, Time(fixed_time_for_output));
117 if (interval_number == std::numeric_limits<std::size_t>::max())
118 {
119 WARN("Did not find interval for fixed output time {}",
120 fixed_time_for_output);
121 continue;
122 }
123
124 auto const lower_bound = std::accumulate(
125 begin(delta_ts), begin(delta_ts) + interval_number, t_initial);
126 auto const upper_bound = lower_bound + delta_ts[interval_number];
127 // in order to use the comparison from struct Time
128 if (NumLib::Time(fixed_time_for_output) == lower_bound)
129 {
130 continue;
131 }
132 if (upper_bound == NumLib::Time(fixed_time_for_output))
133 {
134 continue;
135 }
136 delta_ts[interval_number] = fixed_time_for_output - lower_bound();
137
138 delta_ts.insert(delta_ts.begin() + interval_number + 1,
139 upper_bound() - fixed_time_for_output);
140 }
141}
142
144 double t0, double tn, std::vector<RepeatDtPair> const& repeat_dt_pairs,
145 std::vector<double> const& fixed_times_for_output)
146 : TimeStepAlgorithm(t0, tn)
147{
148 Time t_curr = _t_initial;
149
150 if (Time(tn) <= Time(t0))
151 {
152 OGS_FATAL(
153 "FixedTimeStepping: No time steps can be generated for the time "
154 "interval [{}, {}]. The end time must be larger than the start "
155 "time.",
156 t0, tn);
157 }
158
159 if (!areRepeatDtPairsValid(repeat_dt_pairs))
160 {
161 OGS_FATAL("FixedTimeStepping: Couldn't construct object from data");
162 }
163
164 for (auto const& [repeat, delta_t] : repeat_dt_pairs)
165 {
166 if (t_curr <= _t_end)
167 {
168 t_curr = addTimeIncrement(dt_vector_, repeat, delta_t, t_curr);
169 last_prescribed_dt_ = delta_t;
170 }
171 }
172
173 // append last delta_t until t_end is reached
174 if (t_curr <= _t_end)
175 {
176 auto const repeat = static_cast<std::size_t>(
177 std::ceil((_t_end() - t_curr()) / last_prescribed_dt_));
178 addTimeIncrement(dt_vector_, repeat, last_prescribed_dt_, t_curr);
179 }
180
181 // The non-empty interval and areRepeatDtPairsValid(), which guarantees at
182 // least one pair with <repeat> >= 1 and <delta_t> > 0, imply that the loop
183 // above has appended at least one step size and has set
184 // last_prescribed_dt_.
185 if (dt_vector_.empty())
186 {
187 OGS_FATAL(
188 "FixedTimeStepping: No time steps were generated for the time "
189 "interval [{}, {}].",
190 t0, tn);
191 }
192 if (last_prescribed_dt_ <= 0.0)
193 {
194 OGS_FATAL(
195 "FixedTimeStepping: The last prescribed time step size is {:g}, "
196 "but must be positive.",
198 }
199
201 fixed_times_for_output);
202}
203
204FixedTimeStepping::FixedTimeStepping(double t0, double t_end, double dt)
205 : TimeStepAlgorithm(t0, t_end), last_prescribed_dt_(dt)
206{
207 // Checked before the cast below, which is undefined for a negative value.
208 if (Time(t_end) <= Time(t0) || dt <= 0.0)
209 {
210 OGS_FATAL(
211 "FixedTimeStepping: No time steps can be generated for the time "
212 "interval [{}, {}] with the time step size {:g}. The end time must "
213 "be larger than the start time and the time step size must be "
214 "positive.",
215 t0, t_end, dt);
216 }
217
218 auto const new_size =
219 static_cast<std::size_t>(std::ceil((t_end - t0) / dt));
220 try
221 {
222 dt_vector_ = std::vector<double>(new_size, dt);
223 }
224 catch (std::length_error const& e)
225 {
226 OGS_FATAL(
227 "Resize of the time steps vector failed for the requested new "
228 "size {}. Probably there is not enough memory ({:g} GiB "
229 "requested).\n"
230 "Thrown exception: {}",
231 new_size, new_size * sizeof(double) / 1024. / 1024. / 1024.,
232 e.what());
233 }
234 catch (std::bad_alloc const& e)
235 {
236 OGS_FATAL(
237 "Allocation of the time steps vector failed for the requested "
238 "size {}. Probably there is not enough memory ({:g} GiB "
239 "requested).\n"
240 "Thrown exception: {}",
241 new_size,
242 new_size * sizeof(double) / 1024. / 1024. / 1024.,
243 e.what());
244 }
245}
246
247double FixedTimeStepping::next(double const /*solution_error*/,
248 int const /*number_iterations*/,
249 NumLib::TimeStep& /*ts_previous*/,
250 NumLib::TimeStep& ts_current)
251{
252 // check if last time step
253 if (ts_current.current() >= end())
254 {
255 return 0.0;
256 }
257
258 // The prescribed step sizes can be used up before t_end is reached if the
259 // steps actually taken are smaller than the prescribed ones. This is the
260 // case in the staggered coupling scheme, where all processes advance with
261 // the minimum of the step sizes of all processes, but each process consumes
262 // one of its own prescribed step sizes per time step. The last prescribed
263 // step size is repeated then, such that this process does not restrict the
264 // step size of the other processes any more.
265 if (ts_current.timeStepNumber() >= dt_vector_.size())
266 {
268 {
270 WARN(
271 "FixedTimeStepping: All {:d} time step sizes of the fixed time "
272 "stepping scheme are used up at time {} before the end time "
273 "{} is reached. The last prescribed time step size of {:g} "
274 "will be repeated until the end time. Please check whether the "
275 "prescribed number of time steps is large enough for the "
276 "number of time steps that are actually taken.",
277 dt_vector_.size(), ts_current.current()(), end()(),
279 }
280 return std::min(last_prescribed_dt_, end()() - ts_current.current()());
281 }
282
283 double dt = dt_vector_[ts_current.timeStepNumber()];
284 if (ts_current.current() + dt > end())
285 { // upper bound by t_end
286 dt = end()() - ts_current.current()();
287 }
288
289 return dt;
290}
291
293 std::vector<RepeatDtPair> const& repeat_dt_pairs)
294{
295 if (repeat_dt_pairs.empty())
296 {
297 return false;
298 }
299
300 for (auto const& [repeat, delta_t] : repeat_dt_pairs)
301 {
302 if (repeat == 0)
303 {
304 ERR("FixedTimeStepping: <repeat> is zero.");
305 return false;
306 }
307 if (delta_t <= 0.0)
308 {
309 ERR("FixedTimeStepping: timestep <delta_t> is <= 0.0.");
310 return false;
311 }
312 }
313 return true;
314}
315
316} // namespace NumLib
#define OGS_FATAL(...)
Definition Error.h:10
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
static bool areRepeatDtPairsValid(std::vector< RepeatDtPair > const &repeat_dt_pairs)
std::vector< double > dt_vector_
a vector of time step sizes
FixedTimeStepping(double t0, double t_end, double dt)
double next(double solution_error, int number_iterations, NumLib::TimeStep &ts_previous, NumLib::TimeStep &ts_current) override
const Time _t_initial
initial time
Time end() const
return the end of time steps
TimeStepAlgorithm(const double t0, const double t_end)
Time step object.
Definition TimeStep.h:24
Time current() const
return current time step
Definition TimeStep.h:71
std::size_t timeStepNumber() const
the time step number
Definition TimeStep.h:75
void incorporateFixedTimesForOutput(NumLib::Time const t_initial, NumLib::Time const t_end, std::vector< double > &delta_ts, std::vector< double > const &fixed_times_for_output)
std::size_t findDeltatInterval(Time const &t_initial, std::vector< double > const &delta_ts, Time const &fixed_output_time)
NumLib::Time addTimeIncrement(std::vector< double > &delta_ts, std::size_t const repeat, double const delta_t, NumLib::Time const t_curr)
Returns sum of the newly added time increments.