ampsci
High-precision calculations for one- and two-valence atomic systems
AdamsMoulton.hpp
1#pragma once
2#include <algorithm>
3#include <array>
4#include <cassert>
5#include <complex>
6#include <tuple>
7#include <type_traits>
8
9//! Contains classes and functions which use general N-step Adams Moulton method
10//! to solve systems of 2x2 ODEs, up to N=12.
11namespace AdamsMoulton {
12
13//==============================================================================
14
15//! Pure-virtual struct, holds the derivative matrix for 2x2 system of ODEs.
16//! Derive from this, and implement a(t),b(t),c(t),d(t) to define the 2x2
17//! ODE.
18/*! @details
19This struct is used by ODESolver2D, and defined the Derivative matrix, and the
20(optional) inhomogenous term.
21
22The system of ODEs is defined such that:
23
24\f[ \frac{dF(t)}{dt} = D(t) * F(t) + S(t) \f]
25
26Where F is a 2D set of functions:
27
28\f[
29 F(t) = \begin{pmatrix}
30 f(t)\\
31 g(t)
32 \end{pmatrix},
33\f]
34
35D is the 2x2 "derivative matrix":
36
37\f[
38 D(t) = \begin{pmatrix}
39 a(t) & b(t)\\
40 c(t) & d(t)
41 \end{pmatrix},
42\f]
43
44and S(t) is the (optional) 2D inhomogenous term:
45
46\f[
47 S(t) = \begin{pmatrix}
48 s_f(t)\\
49 s_g(t)
50 \end{pmatrix}.
51\f]
52
53D (and, optionally S) must be provided by the user by implementing this
54DerivativeMatrix. \n
55D is defined by four functions, a,b,c,d. These four functions
56must be overriden with definitions to define the ODE system.
57
58Template parameters, T and Y
59
60Each of the four functions is a function of t, which has type T.
61Usually, T=double or complex<double>, but it may also be an index type (e.g.,
62std::size_t) if the derivative matrix is only known numerically at certain grid
63points/stored in an array.
64
65The return type, Y, is also a template parameter.
66It is usually double, but may be any type (e.g., float or complex<double>).
67DerivativeMatrix will work with any type, though only arithmetic types will
68work in the ODE solver.
69
70See documentation of ODESolver2D for example.
71
72\par
73
74Note: in tests, deriving from a struct was significantly faster than using
75std::function, slightly faster than using function pointers, and performed
76about equally to directly implementing the DerivativeMatrix.
77*/
78template <typename T = double, typename Y = double>
80 //! a,b,c,d are derivative matrix functions; all must be user implemented
81 virtual Y a(T t) const = 0;
82 virtual Y b(T t) const = 0;
83 virtual Y c(T t) const = 0;
84 virtual Y d(T t) const = 0;
85 //! Sf and Sg are optional inhomogenous terms
86 virtual Y Sf(T) const { return Y(0); };
87 virtual Y Sg(T) const { return Y(0); };
88 virtual ~DerivativeMatrix() = default;
89};
90
91//==============================================================================
92
93// User-defined type-trait: Checks whether T is a std::complex type
94template <typename T>
95struct is_complex : std::false_type {};
96// User-defined type-trait: Checks whether T is a std::complex type
97template <typename T>
98struct is_complex<std::complex<T>> : std::true_type {};
99//! User-defined type-trait: Checks whether T is a std::complex type
100/*! @details
101 For example:
102 `static_assert(!is_complex_v<double>);`
103 `static_assert(!is_complex_v<float>);`
104 `static_assert(is_complex_v<std::complex<double>>);`
105 `static_assert(is_complex_v<std::complex<float>>);`
106 `static_assert(is_complex<std::complex<float>>::value);`
107*/
108template <typename T>
109constexpr bool is_complex_v = is_complex<T>::value;
110
111//==============================================================================
112
113//! Inner product of two std::arrays.
114/*! @details
115\f[ inner_product(a, b) = \sum_{i=0}^{N-1} a_i * b_i \f]
116Where `N = min(a.size(), b.size())`.
117The types of the arrays may be different (T and U).
118However, U must be convertable to T; the return-type is T (same as first
119array).
120*/
121template <typename T, typename U, std::size_t N, std::size_t M>
122constexpr T inner_product(const std::array<T, N> &a,
123 const std::array<U, M> &b) {
124 static_assert(std::is_convertible_v<U, T>,
125 "In inner_product, type of second array (U) must be "
126 "convertable to that of dirst (T)");
127 constexpr std::size_t Size = std::min(N, M);
128
129 if constexpr (Size == 0) {
130 return T{0};
131 } else if constexpr (!std::is_same_v<T, U> && is_complex_v<T>) {
132 // This is to avoid float conversion warning in case that U=double,
133 // T=complex<float>; want to case b to float, then to complex<float>
134 T res = a[0] * static_cast<typename T::value_type>(b[0]);
135 for (std::size_t i = 1; i < Size; ++i) {
136 res += a[i] * static_cast<typename T::value_type>(b[i]);
137 }
138 return res;
139 } else {
140 T res = a[0] * static_cast<T>(b[0]);
141 for (std::size_t i = 1; i < Size; ++i) {
142 res += a[i] * static_cast<T>(b[i]);
143 }
144 return res;
145 }
146}
147
148//==============================================================================
149
150namespace helper {
151
152// Simple struct for storing "Raw" Adams ("B") coefficients.
153/*
154Stored as integers, with 'denominator' factored out. \n
155Converted to double ("A" coefs) once, at compile time (see below). \n
156Adams coefficients, a_k, defined such that: \n
157 \f[ F_{n+K} = F_{n+K-1} + dx * \sum_{k=0}^K a_k y_{n+k} \f]
158where:
159 \f[ y = d(F)/dr \f]
160Note: the 'order' of the coefs is reversed compared to some sources.
161The final coefficient is separated, such that: \n
162 \f[ a_k = b_k / denom \f]
163for k = {0,1,...,K-1} \n
164and
165 \f[ a_K = b_K / denom \f]
166*/
167template <std::size_t K>
168struct AdamsB {
169 long denom;
170 std::array<long, K> bk;
171 long bK;
172};
173
174// List of Adams coefficients data
175/*
176Note: there is (of course) no 0-step Adams method.
177The entry at [0] is invalid, and will produce 0.
178It is included so that the index of this list matches the order of the method.
179Program will not compile (static_asser) is [0] is requested.
180Note: assumes that the kth element corresponds to k-order AM method.
181*/
182static constexpr auto ADAMS_data = std::tuple{
183 AdamsB<0>{1, {}, 0}, // invalid entry, but want index to match order
184 AdamsB<1>{2, {1}, 1},
185 AdamsB<2>{12, {-1, 8}, 5},
186 AdamsB<3>{24, {1, -5, 19}, 9},
187 AdamsB<4>{720, {-19, 106, -264, 646}, 251},
188 AdamsB<5>{1440, {27, -173, 482, -798, 1427}, 475},
189 AdamsB<6>{60480, {-863, 6312, -20211, 37504, -46461, 65112}, 19087},
190 AdamsB<7>{
191 120960, {1375, -11351, 41499, -88547, 123133, -121797, 139849}, 36799},
192 AdamsB<8>{
193 3628800,
194 {-33953, 312874, -1291214, 3146338, -5033120, 5595358, -4604594, 4467094},
195 1070017},
196 AdamsB<9>{7257600,
197 {57281, -583435, 2687864, -7394032, 13510082, -17283646, 16002320,
198 -11271304, 9449717},
199 2082753},
200 AdamsB<10>{479001600,
201 {-3250433, 36284876, -184776195, 567450984, -1170597042,
202 1710774528, -1823311566, 1446205080, -890175549, 656185652},
203 134211265},
204 AdamsB<11>{958003200,
205 {5675265, -68928781, 384709327, -1305971115, 3007739418,
206 -4963166514, 6043521486, -5519460582, 3828828885, -2092490673,
207 1374799219},
208 262747265},
209 AdamsB<12>{2615348736000,
210 {-13695779093, 179842822566, -1092096992268, 4063327863170,
211 -10344711794985, 19058185652796, -26204344465152, 27345870698436,
212 -21847538039895, 13465774256510, -6616420957428, 3917551216986},
213 703604254357}};
214
215} // namespace helper
216
217//==============================================================================
218
219//! Stores maximum K (order of AM method) for which we have coefficients
220//! implemented.
221static constexpr std::size_t K_max =
222 std::tuple_size_v<decltype(helper::ADAMS_data)> - 1;
223
224//==============================================================================
225
226//! Holds the K+1 Adams-Moulton ak coefficients for the K-step AM method. Final
227//! one, aK, is stored separately.
228/*! @details
229The Adams coefficients, a_k, are defined such that: \n
230 \f[ F_{n+K} = F_{n+K-1} + dx * \sum_{k=0}^K a_k y_{n+k} \f]
231where:
232 \f[ y = d(F)/dr \f]
233Note: the 'order' of the coefs is reversed compared to some sources. \n
234The final coefficient is separated, such that: \n
235 \f[ a_k = b_k / denom \f]
236for k = {0,1,...,K-1} \n
237and
238 \f[ a_K = b_K / denom \f]
239They are stored as doubles regardless of other template parameters.
240*/
241template <std::size_t K, typename = std::enable_if_t<(K > 0)>,
242 typename = std::enable_if_t<(K <= K_max)>>
243struct AM_Coefs {
244
245private:
246 // Forms the (double) ak coefficients, from the raw (int) bk ones
247 static constexpr std::array<double, K> make_ak() {
248 const auto &am = std::get<K>(helper::ADAMS_data);
249 static_assert(
250 am.bk.size() == K,
251 "Kth Entry in ADAMS_data must correspond to K-order AM method");
252 std::array<double, K> tak{};
253 for (std::size_t i = 0; i < K; ++i) {
254 tak.at(i) = double(am.bk.at(i)) / double(am.denom);
255 }
256 return tak;
257 }
258
259 // Forms the final (double) aK coefficient, from the raw (int) bK one
260 static constexpr double make_aK() {
261 const auto &am = std::get<K>(helper::ADAMS_data);
262 return (double(am.bK) / double(am.denom));
263 }
264
265public:
266 //! First K coefficients: ak for k={0,1,...,K-1}
267 static constexpr std::array<double, K> ak{make_ak()};
268 //! Final aK coefficients: ak for k=K
269 static constexpr double aK{make_aK()};
270};
271
272//==============================================================================
273//! Solves a 2D system of ODEs using a K-step Adams-Moulton method
274/*! @details
275The system of ODEs is defined such that:
276
277\f[ \frac{dF(t)}{dt} = D(t) * F(t) + S(t) \f]
278
279Where F is a 2D set of functions:
280
281\f[
282 F(t) = \begin{pmatrix}
283 f(t)\\
284 g(t)
285 \end{pmatrix},
286\f]
287
288D is the 2x2 "derivative matrix":
289
290\f[
291 D(t) = \begin{pmatrix}
292 a(t) & b(t)\\
293 c(t) & d(t)
294 \end{pmatrix},
295\f]
296
297and S(t) is the (optional) 2D inhomogenous term:
298
299\f[
300 S(t) = \begin{pmatrix}
301 s_f(t)\\
302 s_g(t)
303 \end{pmatrix}.
304\f]
305
306See struct `DerivativeMatrix` - which is a pure virtual struct that must be
307implmented by the user in order to define the ODE.
308
309The step-size, dt, must remain constant (since it must remain consistant
310between the K+1 and previous K points). It may be positive or negative,
311however (or complex). It's perfectly possible to have a non-uniform grid - this
312just introduces a Jacobian into the Derivative matrix; dt must still be
313constant.
314
315The template parameter, T, is the type of the argument of the Derivative
316Matrix (i.e., type of `t`). This is often `double` or `complex<double>`, but may
317also be an index type (e.g., std::size_t) if the derivative matrix is only known
318numerically at certain grid points/stored in an array.
319
320The template parameter, Y, is the type of the function value F(t), and the
321type of dt, and the return value of the Derivative Matrix. This is often
322`double`, but may also be another floating-point type, or std::complex.
323
324The first K points of the function F, and derivative dF/dt, must be known.
325You may directly access the f,g (function) and df,dg (derivative) arrays, to
326set these points.
327
328Alternatively, you may use the provided function
329 ```cpp
330 void solve_initial_K(T t0, Y f0, Y g0);
331 ```
332which automatically sets the first K values for F (and dF), given a single
333initial value for F, f0=f(t0), fg=g(t0), by using successive N-step AM
334methods, for N={1,2,...,K-1}.
335
336For now, just a 2x2 system. In theory, simple to generalise to an N*N system,
337though requires a matrix inversion.
338
339\par
340
341**Example:** Bessel's differential equation
342
343\f[
344 x^2 \frac{d^2y}{dx^2} + x \frac{dy}{dx} + (x^2-n^2)y = 0
345\f]
346
347With y(0) = 1.0, the solutions are the Bessel functions, y(x) = J_n(x)
348
349This can be re-written as a pair of coupled first-order ODEs:
350
351\f[
352 \begin{aligned}
353 \frac{dy}{dx} &= p \\
354 \frac{dp}{dx} &= \left[\left(\frac{n}{x}\right)^2 - 1\right]y - \frac{1}{x}p
355 \end{aligned}
356\f]
357
358Putting this into the notation/form required for the solver we have:
359
360\f[
361 F(x) = \begin{pmatrix}
362 y(x)\\
363 p(x)
364 \end{pmatrix}
365\f]
366
367with the "derivative matrix":
368
369\f[
370 D(x) = \begin{pmatrix}
371 0 & 1 \\
372 \left(\frac{n}{x}\right)^2 - 1 & \frac{-1}{x}
373 \end{pmatrix}
374\f]
375
376i.e.,
377
378for n=1:
379
380```cpp
381 struct BesselDerivative : AdamsMoulton::DerivativeMatrix<double, double> {
382 double a(double) const final { return 0.0; }
383 double b(double) const final { return 1.0; }
384 double c(double t) const final { return 1.0/t/t - 1.0; }
385 double d(double t) const final { return -1.0 / t; }
386 };
387```
388
389Or, more generally (for example):
390
391```cpp
392 struct BesselDerivative : AdamsMoulton::DerivativeMatrix<double, double> {
393 int n;
394 BesselDerivative(int tn) : n(tn) {}
395 double a(double) const final { return 0.0; }
396 double b(double) const final { return 1.0; }
397 double c(double t) const final { return std::pow(n/t,2) - 1.0; }
398 double d(double t) const final { return -1.0 / t; }
399 };
400```
401
402Minimal example: -- see full examples included elsewhere
403
404```
405 // Construct the Derivative matrix (BesselDerivative defined above) with n=0
406 int n = 0;
407 BesselDerivative D{n};
408
409 // Set the step size:
410 double dt = 0.01;
411
412 // Construct the Solver, using K=6-step method:
413 AdamsMoulton::ODESolver2D<6> ode{dt, &D};
414
415 // Since 1/t appears in D, we cannot start at zero. Instead, begin at small t
416 double t0 = 1.0e-6;
417
418 // Set initial points:
419 // Note: these are *approximate*, since they are technically f(0.0)
420 double f0 = 1.0;
421 double g0 = 0.0;
422
423 // Use automatic solver for first K points:
424 ode.solve_initial_K(t0, f0, g0);
425
426 // Drive forwards another 100 steps
427 for (int i = 0; i < 100; ++i) {
428 ode.drive();
429 std::cout << ode.last_t() << " " << ode.last_f() << '\n';
430 }
431```
432
433*/
434template <std::size_t K, typename T = double, typename Y = double>
436 static_assert(K > 0, "Order (K) for Adams method must be K>0");
437 static_assert(K <= K_max,
438 "Order (K) requested for Adams method too "
439 "large. Adams coefficients are implemented up to K_max-1 only");
440 static_assert(
441 is_complex_v<Y> || std::is_floating_point_v<Y>,
442 "Template parameter Y (function values and dt) must be floating point "
443 "or complex");
444 static_assert(is_complex_v<Y> || std::is_floating_point_v<Y> ||
445 std::is_integral_v<T>,
446 "Template parameter T (derivative matrix argument) must be "
447 "floating point, complex, or integral");
448
449private:
450 // Stores the AM coefficients
451 static constexpr AM_Coefs<K> am{};
452 // step size:
453 Y m_dt;
454 // previous 't' value
455 // T m_t{T(0)}; // Should be invalid (nan), but no nan for int
456 // Pointer to the derivative matrix
457 const DerivativeMatrix<T, Y> *m_D;
458
459public:
460 //! Arrays to store the previous K values of f and g.
461 /*!
462 Note: Stored in the same order, regardless of sign of dt (i.e. whether
463 driving forwards or backwards). So f[0] is the 'oldest' value, f[K-1] is
464 newest.
465 */
466 std::array<Y, K> f{}, g{};
467
468 //! Arrays to store the previous K values of derivatives, df and dg
469 std::array<Y, K> df{}, dg{};
470
471 //! Array to store the previous K values of t: f.at(i) = f(t.at(i))
472 std::array<T, K> t{};
473
474 Y S_scale{1.0};
475
476public:
477 //! Construct the solver. dt is the (constant) step size
478 //! @param dt -- (constant) step size
479 //! @param D -- Pointer to derivative matrix
480 /*! @details
481 The step-size, dt, may be positive (to drive forwards) or negative (to
482 drive backwards); it may also be complex. \n
483 Note: a pointer to the DerivativeMatrix, tD, is stored. This may not be null,
484 and must outlive the ODESolver2D.
485 */
486 ODESolver2D(Y dt, const DerivativeMatrix<T, Y> *D) : m_dt(dt), m_D(D) {
487 assert(dt != Y{0.0} && "Cannot have zero step-size in ODESolver2D");
488 assert(D != nullptr && "Cannot have null Derivative Matrix in ODESolver2D");
489 }
490
491 //! Returns the AM order (number of steps), K
492 constexpr std::size_t K_steps() const { return K; }
493
494 //! Returns most recent f value. Can also access f array directly
495 Y last_f() { return f.back(); }
496 //! Returns most recent g value. Can also access g array directly
497 Y last_g() { return g.back(); }
498 //! Returns most recent t value; last_f() := f(last_t())
499 T last_t() { return t.back(); }
500 //! Returns the step size
501 Y dt() { return m_dt; }
502
503 //! Returns derivative, df/dt(t), given f(t),g(t),t
504 Y dfdt(Y ft, Y gt, T tt) const {
505 return m_D->a(tt) * ft + m_D->b(tt) * gt + S_scale * m_D->Sf(tt);
506 }
507 //! Returns derivative, dg/dt(t), given f(t),g(t),t
508 Y dgdt(Y ft, Y gt, T tt) const {
509 return m_D->c(tt) * ft + m_D->d(tt) * gt + S_scale * m_D->Sg(tt);
510 }
511
512 //! Drives the DE system to next value, F(t), assuming system has already
513 //! been solved for the K previous values {t-K*dt, ..., t-dt}.
514 /*! @details
515 Note: t must align with previous values: i.e., t_next = last_t + dt \n
516 We re-send t to the function to avoid large build-up of small errors. \n
517 There's an overload that avoids this, but care should be taken. \n
518 For example, the 10,000th point along t grid may not exactly line up with t0
519 + 10000*dt, particularly for complicated non-linear grids. \n
520
521 The type of t_next (T) must match type required by DerivativeMatrix. \n
522 This is often T=double if analytic formula for derivative is known. \n
523 If we have a numerical approximation to the derivative stored, e.g., on a
524 grid, then we may instead have T=std::size_t (or whatever). \n
525 */
526 void drive(T t_next) {
527
528 // assert (t_next = Approx[next_t(last_t())])
529
530 // Use AM method to determine new values, given previous K values:
531 const auto sf = f.back() + m_dt * (inner_product(df, am.ak) +
532 am.aK * S_scale * m_D->Sf(t_next));
533 const auto sg = g.back() + m_dt * (inner_product(dg, am.ak) +
534 am.aK * S_scale * m_D->Sg(t_next));
535
536 const auto a = m_D->a(t_next);
537 const auto b = m_D->b(t_next);
538 const auto c = m_D->c(t_next);
539 const auto d = m_D->d(t_next);
540
541 const auto a0 = m_dt * static_cast<Y>(am.aK);
542 const auto a02 = a0 * a0;
543 const auto det_inv =
544 Y{1.0} / (Y{1.0} - (a02 * (b * c - a * d) + a0 * (a + d)));
545 const auto fi = (sf - a0 * (d * sf - b * sg)) * det_inv;
546 const auto gi = (sg - a0 * (-c * sf + a * sg)) * det_inv;
547
548 // Shift values along. nb: rotate({1,2,3,4}) = {2,3,4,1}
549 // We keep track of previous K values in order to determine next value
550 std::rotate(f.begin(), f.begin() + 1, f.end());
551 std::rotate(g.begin(), g.begin() + 1, g.end());
552 std::rotate(df.begin(), df.begin() + 1, df.end());
553 std::rotate(dg.begin(), dg.begin() + 1, dg.end());
554 std::rotate(t.begin(), t.begin() + 1, t.end());
555
556 // Sets new values:
557 t.back() = t_next;
558 f.back() = fi;
559 g.back() = gi;
560 df.back() = dfdt(fi, gi, t_next);
561 dg.back() = dgdt(fi, gi, t_next);
562 }
563
564 //! Overload of drive(T t) for 'default' case, where next t is defined as
565 //! last_t + dt (for arithmetic/complex types), or last_t++/last_t-- for
566 //! integral types (grid index).
567 void drive() { drive(next_t(last_t())); }
568
569 //! Automatically sets the first K values for F (and dF), given a single
570 //! initial value for F, f0=f(t0), fg=g(t0).
571 /*! @details
572 It does this by using successive N-step AM methods, for N={1,2,...,K-1}. \n
573 i.e., \n
574 F[0] must be given \n
575 F[1] determined using F[0] and a 1-step AM method \n
576 F[2] determined using F[0],F[1] and a 2-step AM method \n
577 ... \n
578 F[K-1] determined using F[0],F[1],...,F[K-2] and a (K-1)-step AM method \n
579 */
580 void solve_initial_K(T t0, Y f0, Y g0) {
581 t.at(0) = t0;
582 f.at(0) = f0;
583 g.at(0) = g0;
584 df.at(0) = dfdt(f0, g0, t0);
585 dg.at(0) = dgdt(f0, g0, t0);
586 first_k_i<1>(next_t(t0));
587 }
588
589private:
590 // only used in solve_initial_K(). Use this, because it works for double t,
591 // where next value is t+dt, and for integral t, where next value is t++ is
592 // driving forward (dt>0), or t-- if driving backwards (dt<0)
593 T next_t(T last_t) {
594 if constexpr (std::is_integral_v<T> && is_complex_v<Y>) {
595 return (m_dt.real() > 0.0) ? last_t + 1 : last_t - 1;
596 } else if constexpr (std::is_integral_v<T>) {
597 return (m_dt > 0.0) ? last_t + 1 : last_t - 1;
598 } else {
599 return last_t + m_dt;
600 }
601 }
602
603 // Used recursively by solve_initial_K() to find first K points
604 template <std::size_t ik>
605 void first_k_i(T t_next) {
606 if constexpr (ik >= K) {
607 (void)t_next; // suppress unused variable warning on old g++ versions
608 return;
609 } else {
610 constexpr AM_Coefs<ik> ai{};
611 // nb: ai.ak is smaller than df; inner_product still works
612 const auto sf = f.at(ik - 1) + m_dt * (inner_product(df, ai.ak) +
613 am.aK * S_scale * m_D->Sf(t_next));
614 const auto sg = g.at(ik - 1) + m_dt * (inner_product(dg, ai.ak) +
615 am.aK * S_scale * m_D->Sg(t_next));
616 const auto a0 = m_dt * static_cast<Y>(ai.aK);
617 const auto a02 = a0 * a0;
618 const auto a = m_D->a(t_next);
619 const auto b = m_D->b(t_next);
620 const auto c = m_D->c(t_next);
621 const auto d = m_D->d(t_next);
622 const auto det_inv =
623 Y{1.0} / (Y{1.0} - (a02 * (b * c - a * d) + a0 * (a + d)));
624 const auto fi = (sf - a0 * (d * sf - b * sg)) * det_inv;
625 const auto gi = (sg - a0 * (-c * sf + a * sg)) * det_inv;
626 // Sets new values:
627 t.at(ik) = t_next;
628 f.at(ik) = fi;
629 g.at(ik) = gi;
630 df.at(ik) = dfdt(fi, gi, t_next);
631 dg.at(ik) = dgdt(fi, gi, t_next);
632 // call recursively
633 first_k_i<ik + 1>(next_t(t_next));
634 }
635 }
636};
637} // namespace AdamsMoulton
Solves a 2D system of ODEs using a K-step Adams-Moulton method.
Definition AdamsMoulton.hpp:435
T last_t()
Returns most recent t value; last_f() := f(last_t())
Definition AdamsMoulton.hpp:499
Y last_g()
Returns most recent g value. Can also access g array directly.
Definition AdamsMoulton.hpp:497
std::array< T, K > t
Array to store the previous K values of t: f.at(i) = f(t.at(i))
Definition AdamsMoulton.hpp:472
constexpr std::size_t K_steps() const
Returns the AM order (number of steps), K.
Definition AdamsMoulton.hpp:492
void drive(T t_next)
Drives the DE system to next value, F(t), assuming system has already been solved for the K previous ...
Definition AdamsMoulton.hpp:526
Y last_f()
Returns most recent f value. Can also access f array directly.
Definition AdamsMoulton.hpp:495
void solve_initial_K(T t0, Y f0, Y g0)
Automatically sets the first K values for F (and dF), given a single initial value for F,...
Definition AdamsMoulton.hpp:580
Y dgdt(Y ft, Y gt, T tt) const
Returns derivative, dg/dt(t), given f(t),g(t),t.
Definition AdamsMoulton.hpp:508
Y dfdt(Y ft, Y gt, T tt) const
Returns derivative, df/dt(t), given f(t),g(t),t.
Definition AdamsMoulton.hpp:504
void drive()
Overload of drive(T t) for 'default' case, where next t is defined as last_t + dt (for arithmetic/com...
Definition AdamsMoulton.hpp:567
ODESolver2D(Y dt, const DerivativeMatrix< T, Y > *D)
Construct the solver. dt is the (constant) step size.
Definition AdamsMoulton.hpp:486
std::array< Y, K > df
Arrays to store the previous K values of derivatives, df and dg.
Definition AdamsMoulton.hpp:469
std::array< Y, K > f
Arrays to store the previous K values of f and g.
Definition AdamsMoulton.hpp:466
Y dt()
Returns the step size.
Definition AdamsMoulton.hpp:501
Contains classes and functions which use general N-step Adams Moulton method to solve systems of 2x2 ...
Definition AdamsMoulton.hpp:11
constexpr bool is_complex_v
User-defined type-trait: Checks whether T is a std::complex type.
Definition AdamsMoulton.hpp:109
constexpr T inner_product(const std::array< T, N > &a, const std::array< U, M > &b)
Inner product of two std::arrays.
Definition AdamsMoulton.hpp:122
constexpr double c
speed of light in a.u. (=1/alpha)
Definition PhysConst_constants.hpp:63
Holds the K+1 Adams-Moulton ak coefficients for the K-step AM method. Final one, aK,...
Definition AdamsMoulton.hpp:243
static constexpr std::array< double, K > ak
First K coefficients: ak for k={0,1,...,K-1}.
Definition AdamsMoulton.hpp:267
static constexpr double aK
Final aK coefficients: ak for k=K.
Definition AdamsMoulton.hpp:269
Pure-virtual struct, holds the derivative matrix for 2x2 system of ODEs. Derive from this,...
Definition AdamsMoulton.hpp:79
virtual Y a(T t) const =0
a,b,c,d are derivative matrix functions; all must be user implemented
virtual Y Sf(T) const
Sf and Sg are optional inhomogenous terms.
Definition AdamsMoulton.hpp:86