Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <boost/container/static_vector.hpp>
7 : #include <cstddef>
8 : #include <optional>
9 : #include <pup.h>
10 : #include <type_traits>
11 : #include <utility>
12 :
13 : #include "DataStructures/DataBox/PrefixHelpers.hpp"
14 : #include "DataStructures/DataBox/Prefixes.hpp"
15 : #include "DataStructures/MathWrapper.hpp"
16 : #include "DataStructures/StaticDeque.hpp"
17 : #include "Time/EvolutionOrdering.hpp"
18 : #include "Time/TimeStepId.hpp"
19 : #include "Utilities/ContainsAllocations.hpp"
20 : #include "Utilities/ErrorHandling/Assert.hpp"
21 : #include "Utilities/ErrorHandling/Error.hpp"
22 : #include "Utilities/Gsl.hpp"
23 : #include "Utilities/Serialization/PupBoost.hpp"
24 : #include "Utilities/Serialization/PupStlCpp17.hpp"
25 : #include "Utilities/StdHelpers.hpp"
26 : #include "Utilities/StlBoilerplate.hpp"
27 : #include "Utilities/TMPL.hpp"
28 :
29 : namespace TimeSteppers {
30 :
31 : /// Largest number of past steps supported by the time stepper
32 : /// `History`. Corresponds to the `number_of_past_steps()` method of
33 : /// `TimeStepper`. AdamsBashforth with order 8 has the largest
34 : /// requirement.
35 1 : constexpr size_t history_max_past_steps = 7;
36 : /// Largest number of substeps supported by the time stepper
37 : /// `History`. Corresponds to the `number_of_substeps()` and
38 : /// `number_of_substeps_for_error()` methods of `TimeStepper`.
39 : /// DormandPrince5 with an error estimate and Rk5Owren have the
40 : /// largest requirement.
41 1 : constexpr size_t history_max_substeps = 6;
42 :
43 : /// \ingroup TimeSteppersGroup
44 : /// Entry in the time-stepper history, in type-erased form.
45 : ///
46 : /// The history access classes do not provide mutable references to
47 : /// these structs, so they cannot be used to modify history data.
48 : ///
49 : /// This struct mirrors the typed `StepRecord` struct. See that for
50 : /// details.
51 : ///
52 : /// \tparam T One of the types in \ref MATH_WRAPPER_TYPES
53 : template <typename T>
54 1 : struct UntypedStepRecord {
55 0 : TimeStepId time_step_id;
56 0 : std::optional<T> value;
57 0 : T derivative;
58 :
59 : static_assert(tmpl::list_contains_v<tmpl::list<MATH_WRAPPER_TYPES>, T>);
60 : };
61 :
62 : template <typename T>
63 0 : bool operator==(const UntypedStepRecord<T>& a, const UntypedStepRecord<T>& b);
64 :
65 : template <typename T>
66 0 : bool operator!=(const UntypedStepRecord<T>& a, const UntypedStepRecord<T>& b);
67 :
68 : #if defined(__GNUC__) and not defined(__clang__)
69 : #pragma GCC diagnostic push
70 : #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
71 : #endif // defined(__GNUC__) and not defined(__clang__)
72 : /// \ingroup TimeSteppersGroup
73 : /// Access to the history data used by a TimeStepper in type-erased
74 : /// form. Obtain an instance with `History::untyped()`.
75 : ///
76 : /// The methods mirror similar ones in `History`. See that class for
77 : /// details.
78 : ///
79 : /// \tparam T One of the types in \ref MATH_WRAPPER_TYPES
80 : template <typename T>
81 1 : class ConstUntypedHistory
82 : : public stl_boilerplate::RandomAccessSequence<
83 : ConstUntypedHistory<T>, const UntypedStepRecord<T>, false> {
84 : static_assert(tmpl::list_contains_v<tmpl::list<MATH_WRAPPER_TYPES>, T>);
85 :
86 : protected:
87 0 : ConstUntypedHistory() = default;
88 0 : ConstUntypedHistory(const ConstUntypedHistory&) = default;
89 0 : ConstUntypedHistory(ConstUntypedHistory&&) = default;
90 0 : ConstUntypedHistory& operator=(const ConstUntypedHistory&) = default;
91 0 : ConstUntypedHistory& operator=(ConstUntypedHistory&&) = default;
92 0 : ~ConstUntypedHistory() = default;
93 :
94 : public:
95 0 : using WrapperType = T;
96 :
97 : /// \cond
98 : class UntypedSubsteps
99 : : public stl_boilerplate::RandomAccessSequence<
100 : UntypedSubsteps, const UntypedStepRecord<T>, true> {
101 : public:
102 : UntypedSubsteps() = delete;
103 : UntypedSubsteps(const UntypedSubsteps&) = delete;
104 : UntypedSubsteps(UntypedSubsteps&&) = default;
105 : UntypedSubsteps& operator=(const UntypedSubsteps&) = delete;
106 : UntypedSubsteps& operator=(UntypedSubsteps&&) = default;
107 : ~UntypedSubsteps() = default;
108 :
109 : using WrapperType = T;
110 :
111 : size_t size() const;
112 : size_t max_size() const;
113 :
114 : const UntypedStepRecord<T>& operator[](const size_t index) const;
115 :
116 : private:
117 : friend ConstUntypedHistory;
118 : explicit UntypedSubsteps(const ConstUntypedHistory& history);
119 :
120 : gsl::not_null<const ConstUntypedHistory*> history_;
121 : };
122 : /// \endcond
123 :
124 0 : virtual size_t integration_order() const = 0;
125 :
126 0 : virtual size_t size() const = 0;
127 0 : virtual size_t max_size() const = 0;
128 :
129 0 : virtual const UntypedStepRecord<T>& operator[](size_t index) const = 0;
130 :
131 0 : virtual const UntypedStepRecord<T>& operator[](
132 : const TimeStepId& id) const = 0;
133 :
134 0 : virtual bool at_step_start() const = 0;
135 :
136 0 : UntypedSubsteps substeps() const;
137 :
138 : private:
139 0 : friend UntypedSubsteps;
140 : virtual const boost::container::static_vector<UntypedStepRecord<WrapperType>,
141 : history_max_substeps>&
142 0 : substep_values() const = 0;
143 : };
144 :
145 : /// \ingroup TimeSteppersGroup
146 : /// Mutable access to the history data used by a TimeStepper in
147 : /// type-erased form. Obtain an instance with `History::untyped()`.
148 : ///
149 : /// Data cannot be inserted or modified through the type-erased
150 : /// interface. The only mutability exposed is the ability to delete
151 : /// data.
152 : ///
153 : /// The methods mirror similar ones in `History`. See that class for
154 : /// details.
155 : ///
156 : /// \tparam T One of the types in \ref MATH_WRAPPER_TYPES
157 : template <typename T>
158 1 : class MutableUntypedHistory : public ConstUntypedHistory<T> {
159 : static_assert(tmpl::list_contains_v<tmpl::list<MATH_WRAPPER_TYPES>, T>);
160 :
161 : protected:
162 0 : MutableUntypedHistory() = default;
163 0 : MutableUntypedHistory(const MutableUntypedHistory&) = default;
164 0 : MutableUntypedHistory(MutableUntypedHistory&&) = default;
165 0 : MutableUntypedHistory& operator=(const MutableUntypedHistory&) = default;
166 0 : MutableUntypedHistory& operator=(MutableUntypedHistory&&) = default;
167 0 : ~MutableUntypedHistory() = default;
168 :
169 : public:
170 0 : virtual void discard_value(const TimeStepId& id_to_discard) const = 0;
171 :
172 0 : virtual void pop_front() const = 0;
173 :
174 0 : virtual void clear_substeps() const = 0;
175 :
176 0 : virtual void clear() const = 0;
177 : };
178 : #if defined(__GNUC__) and not defined(__clang__)
179 : #pragma GCC diagnostic pop
180 : #endif // defined(__GNUC__) and not defined(__clang__)
181 :
182 : /// \ingroup TimeSteppersGroup
183 : /// Data in an entry of the time-stepper history.
184 : ///
185 : /// The `value` field may be empty if the value has been discarded to
186 : /// save memory. See `History::discard_value` and
187 : /// `ConstUntypedHistory::discard_value`.
188 : template <typename Vars>
189 1 : struct StepRecord {
190 0 : using DerivVars = db::prefix_variables<::Tags::dt, Vars>;
191 :
192 0 : TimeStepId time_step_id;
193 0 : std::optional<Vars> value;
194 0 : DerivVars derivative;
195 :
196 : // NOLINTNEXTLINE(google-runtime-references)
197 0 : void pup(PUP::er& p);
198 :
199 0 : std::ostream& print(std::ostream& os) const;
200 : };
201 :
202 : template <typename Vars>
203 : void StepRecord<Vars>::pup(PUP::er& p) {
204 : p | time_step_id;
205 : p | value;
206 : p | derivative;
207 : }
208 :
209 : template <typename Vars>
210 : std::ostream& StepRecord<Vars>::print(std::ostream& os) const {
211 : using ::operator<<;
212 : os << "TimeStepId: " << time_step_id << "\n";
213 : os << "Value: " << value << "\n";
214 : os << "Derivative: " << derivative << "\n";
215 : return os;
216 : }
217 :
218 : template <typename Vars>
219 0 : bool operator==(const StepRecord<Vars>& a, const StepRecord<Vars>& b) {
220 : return a.time_step_id == b.time_step_id and a.value == b.value and
221 : a.derivative == b.derivative;
222 : }
223 :
224 : template <typename Vars>
225 0 : bool operator!=(const StepRecord<Vars>& a, const StepRecord<Vars>& b) {
226 : return not(a == b);
227 : }
228 :
229 : /// \cond
230 : template <typename Vars>
231 : class History;
232 : /// \endcond
233 :
234 : namespace History_detail {
235 : // Find a record from either the steps or substeps. This takes the
236 : // arguments it does so it can be used to find both const and
237 : // non-const values from both typed and untyped histories.
238 : template <typename History>
239 : decltype(auto) find_record(History&& history, const TimeStepId& id) {
240 : const size_t substep = id.substep();
241 : if (substep == 0) {
242 : for (size_t i = 0; i < history.size(); ++i) {
243 : auto& record = history[i];
244 : if (record.time_step_id == id) {
245 : return record;
246 : }
247 : }
248 : ERROR(id << " not present");
249 : } else {
250 : ASSERT(substep - 1 < history.substeps().size(), id << " not present");
251 : auto& record = history.substeps()[substep - 1];
252 : ASSERT(record.time_step_id == id, id << " not present");
253 : return record;
254 : }
255 : }
256 :
257 : #if defined(__GNUC__) and not defined(__clang__)
258 : #pragma GCC diagnostic push
259 : #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
260 : #endif // defined(__GNUC__) and not defined(__clang__)
261 : template <typename UntypedBase>
262 : class UntypedAccessCommon : public UntypedBase {
263 : protected:
264 : UntypedAccessCommon() = delete;
265 : UntypedAccessCommon(const UntypedAccessCommon&) = delete;
266 : UntypedAccessCommon(UntypedAccessCommon&&) = default;
267 : UntypedAccessCommon& operator=(const UntypedAccessCommon&) = delete;
268 : // Can't move-assign non-owning DataVectors.
269 : UntypedAccessCommon& operator=(UntypedAccessCommon&&) = delete;
270 : ~UntypedAccessCommon() = default;
271 :
272 : public:
273 : using WrapperType = typename UntypedBase::WrapperType;
274 :
275 : size_t integration_order() const override;
276 :
277 : size_t size() const override;
278 : size_t max_size() const override;
279 :
280 : const UntypedStepRecord<WrapperType>& operator[](
281 : const size_t index) const override;
282 : const UntypedStepRecord<WrapperType>& operator[](
283 : const TimeStepId& id) const override;
284 :
285 : bool at_step_start() const override;
286 :
287 : protected:
288 : template <typename Vars>
289 : UntypedAccessCommon(const History<Vars>& history) {
290 : reinitialize(history);
291 : }
292 :
293 : template <typename Vars>
294 : void reinitialize(const History<Vars>& history) const {
295 : // This class is presenting a view. Regenerating its internal
296 : // representation after a change to the parent structure is an
297 : // implementation detail.
298 : auto* const mutable_this = const_cast<UntypedAccessCommon*>(this);
299 : mutable_this->integration_order_ = history.integration_order();
300 : mutable_this->step_values_.clear();
301 : for (const StepRecord<Vars>& record : history) {
302 : mutable_this->step_values_.push_back(make_untyped(record));
303 : }
304 : mutable_this->substep_values_.clear();
305 : for (const StepRecord<Vars>& record : history.substeps()) {
306 : mutable_this->substep_values_.push_back(make_untyped(record));
307 : }
308 : }
309 :
310 : private:
311 : const boost::container::static_vector<UntypedStepRecord<WrapperType>,
312 : history_max_substeps>&
313 : substep_values() const override;
314 :
315 : template <typename Vars>
316 : static UntypedStepRecord<WrapperType> make_untyped(
317 : const StepRecord<Vars>& record) {
318 : // This class only exposes these records as const references, so
319 : // it is OK if we break non-allocating references to the original
320 : // data by moving out of the MathWrappers since you can't modify
321 : // through them.
322 : return {record.time_step_id,
323 : record.value.has_value() ? std::optional{const_cast<WrapperType&&>(
324 : *make_math_wrapper(*record.value))}
325 : : std::nullopt,
326 : const_cast<WrapperType&&>(*make_math_wrapper(record.derivative))};
327 : }
328 :
329 : size_t integration_order_{};
330 : // static_vector never reallocates, so storing non-owning
331 : // DataVectors is safe.
332 : boost::container::static_vector<UntypedStepRecord<WrapperType>,
333 : history_max_past_steps + 2>
334 : step_values_{};
335 : boost::container::static_vector<UntypedStepRecord<WrapperType>,
336 : history_max_substeps>
337 : substep_values_{};
338 : };
339 : #if defined(__GNUC__) and not defined(__clang__)
340 : #pragma GCC diagnostic pop
341 : #endif // defined(__GNUC__) and not defined(__clang__)
342 : } // namespace History_detail
343 :
344 : /// \ingroup TimeSteppersGroup
345 : /// The past-time data used by TimeStepper classes to update the
346 : /// evolved variables.
347 : ///
348 : /// This class exposes an STL-like container interface for accessing
349 : /// the step (not substep) `StepRecord` data. The records can be
350 : /// freely modified through this interface, although modifying the
351 : /// `time_step_id` field is generally inadvisable.
352 : ///
353 : /// This class is designed to minimize the number of memory
354 : /// allocations performed during a step, and so caches discarded
355 : /// entries for reuse if they contain dynamic allocations. During
356 : /// steady-state operation, this class will perform no heap
357 : /// allocations. If `Vars` and the associated `DerivVars` do not
358 : /// allocate internally, then this class will perform no heap
359 : /// allocations under any circumstances.
360 : template <typename Vars>
361 1 : class History
362 : : public stl_boilerplate::RandomAccessSequence<History<Vars>,
363 : StepRecord<Vars>, false> {
364 : public:
365 0 : using DerivVars = db::prefix_variables<::Tags::dt, Vars>;
366 :
367 0 : History() = default;
368 0 : explicit History(const size_t integration_order)
369 : : integration_order_(integration_order) {}
370 0 : History(const History& other);
371 0 : History(History&&) = default;
372 0 : History& operator=(const History& other);
373 0 : History& operator=(History&&) = default;
374 :
375 : /// The wrapped type presented by the type-erased history. One of
376 : /// the types in \ref MATH_WRAPPER_TYPES.
377 1 : using UntypedVars = math_wrapper_type<Vars>;
378 :
379 : /// \cond
380 : // This wrapper around UntypedAccessCommon exists because we want
381 : // the special members of that to be protected, and we want this to
382 : // be final.
383 : class ConstUntypedAccess final : public History_detail::UntypedAccessCommon<
384 : ConstUntypedHistory<UntypedVars>> {
385 : public:
386 : ConstUntypedAccess() = delete;
387 : ConstUntypedAccess(const ConstUntypedAccess&) = delete;
388 : ConstUntypedAccess(ConstUntypedAccess&&) = default;
389 : ConstUntypedAccess& operator=(const ConstUntypedAccess&) = delete;
390 : ConstUntypedAccess& operator=(ConstUntypedAccess&&) = default;
391 : ~ConstUntypedAccess() = default;
392 :
393 : private:
394 : friend History;
395 :
396 : explicit ConstUntypedAccess(const History& history)
397 : : History_detail::UntypedAccessCommon<ConstUntypedHistory<UntypedVars>>(
398 : history) {}
399 : };
400 :
401 : class MutableUntypedAccess final : public History_detail::UntypedAccessCommon<
402 : MutableUntypedHistory<UntypedVars>> {
403 : public:
404 : MutableUntypedAccess() = delete;
405 : MutableUntypedAccess(const MutableUntypedAccess&) = delete;
406 : MutableUntypedAccess(MutableUntypedAccess&&) = default;
407 : MutableUntypedAccess& operator=(const MutableUntypedAccess&) = delete;
408 : MutableUntypedAccess& operator=(MutableUntypedAccess&&) = default;
409 : ~MutableUntypedAccess() = default;
410 :
411 : void discard_value(const TimeStepId& id_to_discard) const override {
412 : history_->discard_value(id_to_discard);
413 : this->reinitialize(*history_);
414 : }
415 :
416 : void pop_front() const override {
417 : history_->pop_front();
418 : this->reinitialize(*history_);
419 : }
420 :
421 : void clear_substeps() const override {
422 : history_->clear_substeps();
423 : this->reinitialize(*history_);
424 : }
425 :
426 : void clear() const override {
427 : history_->clear();
428 : this->reinitialize(*history_);
429 : }
430 :
431 : private:
432 : friend History;
433 :
434 : explicit MutableUntypedAccess(const gsl::not_null<History*> history)
435 : : History_detail::UntypedAccessCommon<
436 : MutableUntypedHistory<UntypedVars>>(*history),
437 : history_(history) {}
438 :
439 : private:
440 : gsl::not_null<History*> history_;
441 : };
442 :
443 : class ConstSubsteps : public stl_boilerplate::RandomAccessSequence<
444 : ConstSubsteps, const StepRecord<Vars>, true> {
445 : public:
446 : ConstSubsteps() = delete;
447 : ConstSubsteps(const ConstSubsteps&) = delete;
448 : ConstSubsteps(ConstSubsteps&&) = default;
449 : ConstSubsteps& operator=(const ConstSubsteps&) = delete;
450 : ConstSubsteps& operator=(ConstSubsteps&&) = default;
451 : ~ConstSubsteps() = default;
452 :
453 : size_t size() const { return history_->substep_values_.size(); }
454 : static constexpr size_t max_size() { return history_max_substeps; }
455 :
456 : const StepRecord<Vars>& operator[](const size_t index) const {
457 : ASSERT(index < size(),
458 : "Requested substep " << index << " but only have " << size());
459 : return history_->substep_values_[index];
460 : }
461 :
462 : private:
463 : friend History;
464 : explicit ConstSubsteps(const History& history) : history_(&history) {}
465 :
466 : gsl::not_null<const History*> history_;
467 : };
468 :
469 : class MutableSubsteps
470 : : public stl_boilerplate::RandomAccessSequence<MutableSubsteps,
471 : StepRecord<Vars>, true> {
472 : public:
473 : MutableSubsteps() = delete;
474 : MutableSubsteps(const MutableSubsteps&) = delete;
475 : MutableSubsteps(MutableSubsteps&&) = default;
476 : MutableSubsteps& operator=(const MutableSubsteps&) = delete;
477 : MutableSubsteps& operator=(MutableSubsteps&&) = default;
478 : ~MutableSubsteps() = default;
479 :
480 : size_t size() const { return history_->substep_values_.size(); }
481 : static constexpr size_t max_size() { return history_max_substeps; }
482 :
483 : StepRecord<Vars>& operator[](const size_t index) const {
484 : ASSERT(index < size(),
485 : "Requested substep " << index << " but only have " << size());
486 : return history_->substep_values_[index];
487 : }
488 :
489 : private:
490 : friend History;
491 : explicit MutableSubsteps(const gsl::not_null<History*> history)
492 : : history_(history) {}
493 :
494 : gsl::not_null<History*> history_;
495 : };
496 : /// \endcond
497 :
498 : /// Immutable, type-erased access to the history. This method
499 : /// returns a class derived from `ConstUntypedHistory<UntypedVars>`.
500 : /// Any modifications to the History class invalidate the object
501 : /// returned by this function.
502 1 : ConstUntypedAccess untyped() const { return ConstUntypedAccess(*this); }
503 :
504 : /// Mutable, type-erased access to the history. This method returns
505 : /// a class derived from `MutableUntypedHistory<UntypedVars>`. Any
506 : /// modifications to the History class invalidate the object
507 : /// returned by this function, except for modifications performed
508 : /// through the object itself.
509 1 : MutableUntypedAccess untyped() { return MutableUntypedAccess(this); }
510 :
511 : /// Get or set the order the time stepper is running at. Many time
512 : /// steppers expect this to have a particular value. This has no
513 : /// effect on the storage of past data.
514 : /// @{
515 1 : size_t integration_order() const { return integration_order_; }
516 1 : void integration_order(const size_t new_integration_order) {
517 : integration_order_ = new_integration_order;
518 : }
519 : /// @}
520 :
521 : /// Type and value used to indicate that a record is to be created
522 : /// without the `value` field set.
523 : /// @{
524 1 : struct NoValue {};
525 0 : static constexpr NoValue no_value{};
526 : /// @}
527 :
528 : /// Insert a new entry into the history. It will be inserted as a
529 : /// step or substep as appropriate.
530 : ///
531 : /// The supplied `time_step_id` must be later than the current
532 : /// latest entry, and if the substep of `time_step_id` is nonzero,
533 : /// the id must be consistent with the existing data for the current
534 : /// step.
535 : ///
536 : /// If the constant `History::no_value` is passed, the created
537 : /// record will not have its `value` field set. This is useful for
538 : /// histories other than the history of the primary evolution
539 : /// integration, such as the implicit portion of an IMEX evolution.
540 : /// These other uses adjust the result of the main history, and so
541 : /// do not require values (which are only used for the zeroth-order
542 : /// terms).
543 : ///
544 : /// The `value` and `derivative` data will be copied into cached
545 : /// allocations, if any are available. That is, only a copy, not a
546 : /// memory allocation, will be performed when possible.
547 : /// @{
548 1 : void insert(const TimeStepId& time_step_id, const Vars& value,
549 : const DerivVars& derivative);
550 1 : void insert(const TimeStepId& time_step_id, NoValue /*unused*/,
551 : const DerivVars& derivative);
552 : /// @}
553 :
554 : /// Insert a new entry into the history. It will be inserted as a
555 : /// step or substep as appropriate.
556 : ///
557 : /// The supplied record's `time_step_id` must be later than the
558 : /// current latest entry, and if the substep of `time_step_id` is
559 : /// nonzero, the id must be consistent with the existing data for
560 : /// the current step.
561 1 : void insert(StepRecord<Vars> record);
562 :
563 : /// Insert a new entry in the history by modifying the fields of the
564 : /// record directly, instead of passing a value to copy.
565 : ///
566 : /// The (optional) \p value_inserter must be a functor callable with
567 : /// single argument of type `gsl::not_null<Vars*>` and the \p
568 : /// derivative_inserter must be callable with a single argument of
569 : /// type `gsl::not_null<DerivVars*>`. The passed objects will be
570 : /// created from a cached memory allocation if one is available, and
571 : /// will otherwise be default-constructed.
572 : ///
573 : /// All the restrictions on valid values \p time_step_id for the
574 : /// `insert` method apply here as well.
575 : /// @{
576 : template <typename ValueFunc, typename DerivativeFunc>
577 1 : void insert_in_place(const TimeStepId& time_step_id,
578 : ValueFunc&& value_inserter,
579 : DerivativeFunc&& derivative_inserter);
580 : template <typename DerivativeFunc>
581 1 : void insert_in_place(const TimeStepId& time_step_id, NoValue /*unused*/,
582 : DerivativeFunc&& derivative_inserter);
583 : /// @}
584 :
585 : /// Insert data at the start of the history, similar to `push_front`
586 : /// on some STL containers. This can be useful when initializing a
587 : /// multistep integrator.
588 : ///
589 : /// This should only be used for initialization, and cannot be used
590 : /// for substep data. The supplied `time_step_id` must be earlier
591 : /// than the current first entry.
592 : /// @{
593 1 : void insert_initial(TimeStepId time_step_id, Vars value,
594 : DerivVars derivative);
595 1 : void insert_initial(TimeStepId time_step_id, NoValue /*unused*/,
596 : DerivVars derivative);
597 : /// @}
598 :
599 : /// The number of stored step (not substep) entries.
600 1 : size_t size() const { return step_values_.size(); }
601 : /// The maximum number of step (not substep) entries that can be
602 : /// stored in the history. This number is not very large, but will
603 : /// be sufficient for running a time stepper requiring
604 : /// `history_max_past_steps` past step values.
605 1 : static constexpr size_t max_size() {
606 : // In addition to the past steps, we must store the start and end
607 : // of the current step.
608 : return history_max_past_steps + 2;
609 : }
610 :
611 : /// Access the `StepRecord` for a step (not substep).
612 : /// @{
613 1 : const StepRecord<Vars>& operator[](const size_t index) const {
614 : return step_values_[index];
615 : }
616 1 : StepRecord<Vars>& operator[](const size_t index) {
617 : return step_values_[index];
618 : }
619 : /// @}
620 :
621 : /// Access the `StepRecord` for a step or substep with a given
622 : /// TimeStepId. It is an error if there is no entry with that id.
623 : /// @{
624 1 : const StepRecord<Vars>& operator[](const TimeStepId& id) const {
625 : return History_detail::find_record(*this, id);
626 : }
627 1 : StepRecord<Vars>& operator[](const TimeStepId& id) {
628 : return History_detail::find_record(*this, id);
629 : }
630 : /// @}
631 :
632 : /// Get the value at the latest step or substep in the history, even
633 : /// if the value in that record has been discarded. This is not
634 : /// available if `undo_latest` has been called since the last
635 : /// insertion.
636 : ///
637 : /// This function exists to allow access to the previous value after
638 : /// a potentially bad step has been taken without restricting how
639 : /// the time steppers can manage their history.
640 1 : const Vars& latest_value() const;
641 :
642 : /// Get the record for the start of the step containing the passed
643 : /// time. If the time matches a step time exactly, the record for
644 : /// that step is returned.
645 1 : const StepRecord<Vars>& step_start(double time) const;
646 :
647 : /// Check whether we are at the start of a step, i.e, the most
648 : /// recent entry in the history is not a substep.
649 1 : bool at_step_start() const;
650 :
651 : /// Container view of the `StepRecord`s for the history substeps.
652 : /// These methods return classes providing an STL-like container
653 : /// interface to the substep data. These containers are
654 : /// zero-indexed, so `substeps()[0]` will have a substep value of 1.
655 : ///
656 : /// These containers do not have methods to modify their sizes. Use
657 : /// the methods on the `History` class for those operations.
658 : /// @{
659 1 : ConstSubsteps substeps() const { return ConstSubsteps(*this); }
660 1 : MutableSubsteps substeps() { return MutableSubsteps(this); }
661 : /// @}
662 :
663 : /// Clear the `value` in the indicated record. It is an error if
664 : /// there is no record with the passed TimeStepId. Any memory
665 : /// allocations will be cached for future reuse.
666 1 : void discard_value(const TimeStepId& id_to_discard);
667 :
668 : /// Drop the oldest step (not substep) entry in the history. Any
669 : /// memory allocations will be cached for future reuse.
670 1 : void pop_front();
671 :
672 : /// Drop the newest step or substep entry in the history. Any
673 : /// memory allocations will be cached for future reuse.
674 1 : void undo_latest();
675 :
676 : /// Remove all substep entries from the history. Any memory
677 : /// allocations will be cached for future reuse.
678 1 : void clear_substeps();
679 :
680 : /// Remove all (step and substep) entries from the history. Any
681 : /// memory allocations will be cached for future reuse.
682 1 : void clear();
683 :
684 : /// Release any cached memory allocations.
685 1 : void shrink_to_fit();
686 :
687 : /// Apply \p func to `make_not_null(&e)` for `e` every `derivative`
688 : /// and valid `*value` in records held by the history.
689 : template <typename F>
690 1 : void map_entries(F&& func);
691 :
692 : // NOLINTNEXTLINE(google-runtime-references)
693 0 : void pup(PUP::er& p);
694 :
695 0 : std::ostream& print(std::ostream& os) const;
696 :
697 : private:
698 0 : void discard_value(gsl::not_null<std::optional<Vars>*> value);
699 0 : void cache_allocations(gsl::not_null<StepRecord<Vars>*> record);
700 :
701 : template <typename ValueFunc, typename DerivativeFunc>
702 0 : void insert_impl(const TimeStepId& time_step_id, ValueFunc&& value_inserter,
703 : DerivativeFunc&& derivative_inserter);
704 :
705 : template <typename InsertedVars>
706 0 : void insert_initial_impl(TimeStepId time_step_id, InsertedVars value,
707 : DerivVars derivative);
708 :
709 0 : size_t integration_order_{0};
710 :
711 0 : StaticDeque<StepRecord<Vars>, max_size()> step_values_{};
712 : boost::container::static_vector<StepRecord<Vars>, history_max_substeps>
713 0 : substep_values_{};
714 :
715 : // If the algorithm undoes a substep, it may want to reset the
716 : // variables to the previous value. We don't want the time stepper
717 : // to have to keep track of this when discarding values, so we hang
718 : // onto the last value if it gets discarded.
719 0 : std::optional<Vars> latest_value_if_discarded_{};
720 :
721 : // Memory allocations available for reuse.
722 : boost::container::static_vector<Vars, max_size() + history_max_substeps>
723 0 : vars_allocation_cache_{};
724 : boost::container::static_vector<DerivVars, max_size() + history_max_substeps>
725 0 : deriv_vars_allocation_cache_{};
726 : };
727 :
728 : // Don't copy the allocation caches.
729 : template <typename Vars>
730 : History<Vars>::History(const History& other)
731 : : integration_order_(other.integration_order_),
732 : step_values_(other.step_values_),
733 : substep_values_(other.substep_values_),
734 : latest_value_if_discarded_(other.latest_value_if_discarded_) {}
735 :
736 : // Don't copy the allocation caches.
737 : template <typename Vars>
738 : History<Vars>& History<Vars>::operator=(const History& other) {
739 : integration_order_ = other.integration_order_;
740 : step_values_ = other.step_values_;
741 : substep_values_ = other.substep_values_;
742 : latest_value_if_discarded_ = other.latest_value_if_discarded_;
743 : return *this;
744 : }
745 :
746 : template <typename Vars>
747 : void History<Vars>::insert(const TimeStepId& time_step_id, const Vars& value,
748 : const DerivVars& derivative) {
749 : insert_impl(
750 : time_step_id,
751 : [&](const gsl::not_null<Vars*> record_value) { *record_value = value; },
752 : [&](const gsl::not_null<DerivVars*> record_derivative) {
753 : *record_derivative = derivative;
754 : });
755 : }
756 :
757 : template <typename Vars>
758 : void History<Vars>::insert(const TimeStepId& time_step_id, NoValue /*unused*/,
759 : const DerivVars& derivative) {
760 : insert_impl(time_step_id, NoValue{},
761 : [&](const gsl::not_null<DerivVars*> record_derivative) {
762 : *record_derivative = derivative;
763 : });
764 : }
765 :
766 : template <typename Vars>
767 : void History<Vars>::insert(StepRecord<Vars> record) {
768 : ASSERT(this->empty() or record.time_step_id > this->back().time_step_id,
769 : "New entry at " << record.time_step_id
770 : << " must be later than previous entry at "
771 : << this->back().time_step_id);
772 : const size_t substep = record.time_step_id.substep();
773 : if (substep == 0) {
774 : step_values_.push_back(std::move(record));
775 : } else {
776 : ASSERT(not this->empty(), "Cannot insert substep into empty history.");
777 : ASSERT(record.time_step_id.step_time() ==
778 : this->back().time_step_id.step_time(),
779 : "Cannot insert substep " << record.time_step_id
780 : << " of different step " << this->back().time_step_id);
781 : ASSERT(substep == substeps().size() + 1,
782 : "Cannot insert substep " << substep << " following "
783 : << substeps().size());
784 : ASSERT(substep_values_.size() < substep_values_.max_size(),
785 : "Cannot insert new substep because the History is full.");
786 : substep_values_.push_back(std::move(record));
787 : }
788 : }
789 :
790 : template <typename Vars>
791 : template <typename ValueFunc, typename DerivativeFunc>
792 : void History<Vars>::insert_in_place(const TimeStepId& time_step_id,
793 : ValueFunc&& value_inserter,
794 : DerivativeFunc&& derivative_inserter) {
795 : insert_impl(time_step_id, std::forward<ValueFunc>(value_inserter),
796 : std::forward<DerivativeFunc>(derivative_inserter));
797 : }
798 :
799 : template <typename Vars>
800 : template <typename DerivativeFunc>
801 : void History<Vars>::insert_in_place(const TimeStepId& time_step_id,
802 : NoValue /*unused*/,
803 : DerivativeFunc&& derivative_inserter) {
804 : insert_impl(time_step_id, NoValue{},
805 : std::forward<DerivativeFunc>(derivative_inserter));
806 : }
807 :
808 : template <typename Vars>
809 : void History<Vars>::insert_initial(TimeStepId time_step_id, Vars value,
810 : DerivVars derivative) {
811 : insert_initial_impl(std::move(time_step_id), std::move(value),
812 : std::move(derivative));
813 : }
814 :
815 : template <typename Vars>
816 : void History<Vars>::insert_initial(TimeStepId time_step_id, NoValue /*unused*/,
817 : DerivVars derivative) {
818 : insert_initial_impl(std::move(time_step_id), NoValue{},
819 : std::move(derivative));
820 : }
821 :
822 : template <typename Vars>
823 : const Vars& History<Vars>::latest_value() const {
824 : const auto& latest_record =
825 : at_step_start() ? this->back() : substeps().back();
826 : if (latest_record.value.has_value()) {
827 : return *latest_record.value;
828 : } else {
829 : ASSERT(latest_value_if_discarded_.has_value(),
830 : "Latest value unavailable. The latest insertion was undone.");
831 : return *latest_value_if_discarded_;
832 : }
833 : }
834 :
835 : template <typename Vars>
836 : const StepRecord<Vars>& History<Vars>::step_start(const double time) const {
837 : // Search starting at the end to handle self-start correctly (and
838 : // because the result under usual use is one of the last two
839 : // entries).
840 : const auto first_step = this->begin();
841 : auto step = this->end();
842 : ASSERT(step != first_step, "History is empty");
843 : --step;
844 : const evolution_less<double> before{step->time_step_id.time_runs_forward()};
845 : while (before(time, step->time_step_id.step_time().value())) {
846 : ASSERT(step != first_step,
847 : "Start of step at time " << time << " is before start of history "
848 : << first_step->time_step_id);
849 : --step;
850 : }
851 : return *step;
852 : }
853 :
854 : template <typename Vars>
855 : bool History<Vars>::at_step_start() const {
856 : ASSERT(not this->empty(), "History is empty");
857 : return substep_values_.empty() or
858 : substep_values_.back().time_step_id < this->back().time_step_id;
859 : }
860 :
861 : template <typename Vars>
862 : void History<Vars>::discard_value(const TimeStepId& id_to_discard) {
863 : auto& latest_record = at_step_start() ? this->back() : substeps().back();
864 : if (latest_record.time_step_id == id_to_discard) {
865 : discard_value(&latest_value_if_discarded_);
866 : latest_value_if_discarded_ = std::move(latest_record.value);
867 : latest_record.value.reset();
868 : } else {
869 : discard_value(&History_detail::find_record(*this, id_to_discard).value);
870 : }
871 : }
872 :
873 : template <typename Vars>
874 : void History<Vars>::pop_front() {
875 : ASSERT(not this->empty(), "History is empty");
876 : ASSERT(substeps().empty() or substeps().front().time_step_id.step_time() !=
877 : this->front().time_step_id.step_time(),
878 : "Cannot remove a step with substeps. Call clear_substeps() first.");
879 : cache_allocations(&step_values_.front());
880 : step_values_.pop_front();
881 : }
882 :
883 : template <typename Vars>
884 : void History<Vars>::undo_latest() {
885 : if (at_step_start()) {
886 : cache_allocations(&step_values_.back());
887 : step_values_.pop_back();
888 : } else {
889 : cache_allocations(&substep_values_.back());
890 : substep_values_.pop_back();
891 : }
892 : discard_value(&latest_value_if_discarded_);
893 : }
894 :
895 : template <typename Vars>
896 : void History<Vars>::clear_substeps() {
897 : for (auto& record : substep_values_) {
898 : cache_allocations(&record);
899 : }
900 : substep_values_.clear();
901 : }
902 :
903 : template <typename Vars>
904 : void History<Vars>::clear() {
905 : clear_substeps();
906 : while (not this->empty()) {
907 : pop_front();
908 : }
909 : discard_value(&latest_value_if_discarded_);
910 : }
911 :
912 : template <typename Vars>
913 : void History<Vars>::shrink_to_fit() {
914 : vars_allocation_cache_.clear();
915 : vars_allocation_cache_.shrink_to_fit();
916 : deriv_vars_allocation_cache_.clear();
917 : deriv_vars_allocation_cache_.shrink_to_fit();
918 : }
919 :
920 : template <typename Vars>
921 : template <typename F>
922 : void History<Vars>::map_entries(F&& func) {
923 : for (auto& record : *this) {
924 : func(make_not_null(&record.derivative));
925 : if (record.value.has_value()) {
926 : func(make_not_null(&*record.value));
927 : }
928 : }
929 : for (auto& record : this->substeps()) {
930 : func(make_not_null(&record.derivative));
931 : if (record.value.has_value()) {
932 : func(make_not_null(&*record.value));
933 : }
934 : }
935 : }
936 :
937 : template <typename Vars>
938 : void History<Vars>::pup(PUP::er& p) {
939 : p | integration_order_;
940 : p | step_values_;
941 : p | substep_values_;
942 : p | latest_value_if_discarded_;
943 :
944 : // Don't serialize the allocation cache.
945 : }
946 :
947 : template <typename Vars>
948 : std::ostream& History<Vars>::print(std::ostream& os) const {
949 : using ::operator<<;
950 : os << "Integration order: " << integration_order_ << "\n";
951 : os << "Step values:\n";
952 : for (auto& record : *this) {
953 : record.print(os);
954 : }
955 : os << "Substep values:\n";
956 : for (auto& record : substep_values_) {
957 : record.print(os);
958 : }
959 : os << "Latest value if discarded: " << latest_value_if_discarded_ << "\n";
960 : return os;
961 : }
962 :
963 : // Doxygen is confused by this function for some reason.
964 : /// \cond
965 : template <typename Vars>
966 : void History<Vars>::discard_value(
967 : const gsl::not_null<std::optional<Vars>*> value) {
968 : if (not value->has_value()) {
969 : return;
970 : }
971 : // If caching doesn't save anything, don't allocate memory for the cache.
972 : if (contains_allocations(**value)) {
973 : vars_allocation_cache_.emplace_back(std::move(**value));
974 : }
975 : value->reset();
976 : }
977 : /// \endcond
978 :
979 : template <typename Vars>
980 : void History<Vars>::cache_allocations(
981 : const gsl::not_null<StepRecord<Vars>*> record) {
982 : discard_value(&record->value);
983 : // If caching doesn't save anything, don't allocate memory for the cache.
984 : if (contains_allocations(record->derivative)) {
985 : deriv_vars_allocation_cache_.emplace_back(std::move(record->derivative));
986 : }
987 : }
988 :
989 : template <typename Vars>
990 : template <typename ValueFunc, typename DerivativeFunc>
991 : void History<Vars>::insert_impl(const TimeStepId& time_step_id,
992 : ValueFunc&& value_inserter,
993 : DerivativeFunc&& derivative_inserter) {
994 : discard_value(&latest_value_if_discarded_);
995 : StepRecord<Vars> record{};
996 : record.time_step_id = time_step_id;
997 : if constexpr (not std::is_same_v<ValueFunc, NoValue>) {
998 : if (vars_allocation_cache_.empty()) {
999 : record.value.emplace();
1000 : } else {
1001 : record.value.emplace(std::move(vars_allocation_cache_.back()));
1002 : vars_allocation_cache_.pop_back();
1003 : }
1004 : std::forward<ValueFunc>(value_inserter)(make_not_null(&*record.value));
1005 : }
1006 : if (not deriv_vars_allocation_cache_.empty()) {
1007 : record.derivative = std::move(deriv_vars_allocation_cache_.back());
1008 : deriv_vars_allocation_cache_.pop_back();
1009 : }
1010 : std::forward<DerivativeFunc>(derivative_inserter)(
1011 : make_not_null(&record.derivative));
1012 :
1013 : insert(std::move(record));
1014 : }
1015 :
1016 : template <typename Vars>
1017 : template <typename InsertedVars>
1018 : void History<Vars>::insert_initial_impl(TimeStepId time_step_id,
1019 : InsertedVars value,
1020 : DerivVars derivative) {
1021 : ASSERT(vars_allocation_cache_.empty(),
1022 : "insert_initial should only be used for initialization");
1023 : ASSERT(deriv_vars_allocation_cache_.empty(),
1024 : "insert_initial should only be used for initialization");
1025 : ASSERT(time_step_id.substep() == 0, "Cannot use insert_initial for substeps");
1026 : ASSERT(this->empty() or time_step_id < this->front().time_step_id,
1027 : "New initial entry at " << time_step_id
1028 : << " must be earlier than previous entry at "
1029 : << this->front().time_step_id);
1030 :
1031 : if constexpr (std::is_same_v<InsertedVars, Vars>) {
1032 : step_values_.push_front(StepRecord<Vars>{
1033 : std::move(time_step_id), std::move(value), std::move(derivative)});
1034 : } else {
1035 : static_assert(std::is_same_v<InsertedVars, NoValue>);
1036 : (void)value;
1037 : step_values_.push_front(StepRecord<Vars>{
1038 : std::move(time_step_id), std::nullopt, std::move(derivative)});
1039 : }
1040 : }
1041 :
1042 : template <typename Vars>
1043 0 : bool operator==(const History<Vars>& a, const History<Vars>& b) {
1044 : return a.integration_order() == b.integration_order() and
1045 : a.size() == b.size() and std::equal(a.begin(), a.end(), b.begin()) and
1046 : a.substeps() == b.substeps();
1047 : }
1048 :
1049 : template <typename Vars>
1050 0 : bool operator!=(const History<Vars>& a, const History<Vars>& b) {
1051 : return not(a == b);
1052 : }
1053 :
1054 : template <typename Vars>
1055 0 : std::ostream& operator<<(std::ostream& os, const History<Vars>& history) {
1056 : return history.print(os);
1057 : }
1058 :
1059 : /// \ingroup TimeSteppersGroup
1060 : /// Initialize a History object based on the contents of another,
1061 : /// applying a transformation to each value and derivative.
1062 : ///
1063 : /// The transformation functions can either take a value from the
1064 : /// source history and return a value for the destination history or
1065 : /// take a `gsl::not_null` value from the destination history and a
1066 : /// value from the source history to initialize it with. For the sake
1067 : /// of implementation simplicity, either both transformers must mutate
1068 : /// or both must produce values.
1069 : ///
1070 : /// An overload applying the same transformation to the values and
1071 : /// derivatives is provided for convenience.
1072 : ///
1073 : /// \see transform_mutate
1074 : /// @{
1075 : template <typename DestVars, typename SourceVars, typename ValueTransformer,
1076 : typename DerivativeTransformer>
1077 1 : void transform(const gsl::not_null<History<DestVars>*> dest,
1078 : const History<SourceVars>& source,
1079 : ValueTransformer&& value_transformer,
1080 : DerivativeTransformer&& derivative_transformer) {
1081 : dest->clear_substeps();
1082 : dest->clear();
1083 : dest->integration_order(source.integration_order());
1084 : if (source.empty()) {
1085 : return;
1086 : }
1087 : auto pre_substep_end = source.end();
1088 : if (not source.substeps().empty() and
1089 : source.back().time_step_id > source.substeps().back().time_step_id) {
1090 : --pre_substep_end;
1091 : }
1092 :
1093 : const auto transform_record =
1094 : [&derivative_transformer, &dest, &value_transformer](
1095 : const typename History<SourceVars>::value_type& record) {
1096 : if constexpr (std::is_invocable_v<ValueTransformer,
1097 : gsl::not_null<DestVars*>,
1098 : const SourceVars&>) {
1099 : if (record.value.has_value()) {
1100 : dest->insert_in_place(
1101 : record.time_step_id,
1102 : [&](const auto result) {
1103 : value_transformer(result, *record.value);
1104 : },
1105 : [&](const auto result) {
1106 : derivative_transformer(result, record.derivative);
1107 : });
1108 : } else {
1109 : dest->insert_in_place(
1110 : record.time_step_id, History<DestVars>::no_value,
1111 : [&](const auto result) {
1112 : derivative_transformer(result, record.derivative);
1113 : });
1114 : }
1115 : } else {
1116 : static_assert(
1117 : std::is_invocable_v<ValueTransformer, const SourceVars&>,
1118 : "Transform function must either be callable to mutate entries "
1119 : "or return the transformed state by value.");
1120 : if (record.value.has_value()) {
1121 : dest->insert(record.time_step_id, value_transformer(*record.value),
1122 : derivative_transformer(record.derivative));
1123 : } else {
1124 : dest->insert(record.time_step_id, History<DestVars>::no_value,
1125 : derivative_transformer(record.derivative));
1126 : }
1127 : }
1128 : };
1129 :
1130 : auto copying_step = source.begin();
1131 : for (; copying_step != pre_substep_end; ++copying_step) {
1132 : transform_record(*copying_step);
1133 : }
1134 : for (const auto& record : source.substeps()) {
1135 : transform_record(record);
1136 : }
1137 : if (pre_substep_end != source.end()) {
1138 : transform_record(*pre_substep_end);
1139 : }
1140 : }
1141 :
1142 : template <typename DestVars, typename SourceVars, typename Transformer>
1143 1 : void transform(const gsl::not_null<History<DestVars>*> dest,
1144 : const History<SourceVars>& source, Transformer&& transformer) {
1145 : transform(dest, source, transformer, transformer);
1146 : }
1147 : /// @}
1148 :
1149 : /// \ingroup TimeSteppersGroup
1150 : /// Combine two History objects by applying a transformation to each
1151 : /// value and derivative.
1152 : ///
1153 : /// The transformers must accept a `gsl::not_null` value from the \p
1154 : /// dest history and a const reference to value from the \p source
1155 : /// history. They will be applied to each corresponding (non-nullopt)
1156 : /// pair of values from the two histories. It is an error if the
1157 : /// entries in the two histories do not match.
1158 : ///
1159 : /// An overload applying the same transformation to the values and
1160 : /// derivatives is provided for convenience.
1161 : ///
1162 : /// \see transform
1163 : /// @{
1164 : template <typename DestVars, typename SourceVars, typename ValueTransformer,
1165 : typename DerivativeTransformer>
1166 1 : void transform_mutate(const gsl::not_null<History<DestVars>*> dest,
1167 : const History<SourceVars>& source,
1168 : ValueTransformer&& value_transformer,
1169 : DerivativeTransformer&& derivative_transformer) {
1170 : ASSERT(dest->integration_order() == source.integration_order(),
1171 : "Attempting to combine histories with integration orders "
1172 : << dest->integration_order() << " and "
1173 : << source.integration_order());
1174 :
1175 : const auto transform_record =
1176 : [&derivative_transformer, &value_transformer](
1177 : typename History<DestVars>::value_type& dest_record,
1178 : const typename History<SourceVars>::value_type& source_record) {
1179 : ASSERT(dest_record.time_step_id == source_record.time_step_id,
1180 : "Entries to combine do not match: "
1181 : << dest_record.time_step_id << " "
1182 : << source_record.time_step_id);
1183 : ASSERT(dest_record.value.has_value() == source_record.value.has_value(),
1184 : "Only one of the entries to combine has a value at "
1185 : << dest_record.time_step_id);
1186 : if (dest_record.value.has_value()) {
1187 : value_transformer(make_not_null(&*dest_record.value),
1188 : *source_record.value);
1189 : }
1190 : derivative_transformer(make_not_null(&dest_record.derivative),
1191 : source_record.derivative);
1192 : };
1193 :
1194 : ASSERT(dest->size() == source.size(),
1195 : "Attempting to combine histories with sizes "
1196 : << dest->size() << " and " << source.size());
1197 : {
1198 : auto dest_entry = dest->begin();
1199 : auto source_entry = source.begin();
1200 : while (dest_entry != dest->end()) {
1201 : transform_record(*dest_entry, *source_entry);
1202 : ++dest_entry;
1203 : ++source_entry;
1204 : }
1205 : }
1206 :
1207 : {
1208 : auto dest_substeps = dest->substeps();
1209 : const auto source_substeps = source.substeps();
1210 : ASSERT(dest_substeps.size() == source_substeps.size(),
1211 : "Attempting to combine histories with substep sizes "
1212 : << dest_substeps.size() << " and " << source_substeps.size());
1213 : auto dest_entry = dest_substeps.begin();
1214 : auto source_entry = source_substeps.begin();
1215 : while (dest_entry != dest_substeps.end()) {
1216 : transform_record(*dest_entry, *source_entry);
1217 : ++dest_entry;
1218 : ++source_entry;
1219 : }
1220 : }
1221 : }
1222 :
1223 : template <typename DestVars, typename SourceVars, typename Transformer>
1224 1 : void transform_mutate(const gsl::not_null<History<DestVars>*> dest,
1225 : const History<SourceVars>& source,
1226 : Transformer&& transformer) {
1227 : transform_mutate(dest, source, transformer, transformer);
1228 : }
1229 : /// @}
1230 : } // namespace TimeSteppers
|