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