CoolProp 8.0.0
An open-source fluid property and humid air property database
ODEIntegrators.cpp
Go to the documentation of this file.
1
3#include "Eigen/Core"
6#include <algorithm>
7#include <cmath>
8#include <iostream>
9
10bool ODEIntegrators::AdaptiveRK54(AbstractODEIntegrator& ode, double tmin, double tmax, double hmin, double hmax, double eps_allowed,
11 double step_relax) {
12 // Get the starting array of variables of integration
13 std::vector<double> xold = ode.get_initial_array();
14 const long N = static_cast<long>(xold.size());
15
16 // Start at an index of 0
17 int Itheta = 0;
18 double t0 = tmin;
19 double h = hmin;
20
21 // Figure out if t is increasing or decreasing in the integration and set a flag
22 bool forwards_integration = ((tmax - tmin) > 0);
23 // If backwards integration, flip the sign of the step
24 if (!forwards_integration) {
25 h *= -1;
26 }
27
28 double max_error = NAN;
29
30 std::vector<double> xnew1(N), xnew2(N), xnew3(N), xnew4(N), xnew5(N), f1(N), f2(N), f3(N), f4(N), f5(N), f6(N), error(N), xnew(N);
31
32 // t is the independent variable here, where t takes on values in the bounded range [tmin,tmax]
33 do {
34
35 // Check for termination
36 bool abort = ode.premature_termination();
37 if (abort) {
38 return abort;
39 }
40
41 bool stepAccepted = false, disableAdaptive = false;
42
43 while (!stepAccepted) {
44
45 // reset the flag
46 disableAdaptive = false;
47
48 // If the step would go beyond the end of the region of integration,
49 // just take a step to the end of the region of integration
50 if (forwards_integration && (t0 + h > tmax)) {
51 disableAdaptive = true;
52 h = tmax - t0;
53 }
54 if (!forwards_integration && (t0 + h < tmax)) {
55 disableAdaptive = true;
56 h = tmax - t0;
57 }
58
60
61 // We check stepAccepted again because if the derived class
62 // sets the variable stepAccepted, we should not actually do the evaluation.
63 // cppcheck-suppress identicalInnerCondition
64 if (!stepAccepted) {
65
66 Eigen::Map<Eigen::VectorXd> xold_w(&(xold[0]), N);
67
68 if (std::abs(h) < hmin && !disableAdaptive) {
69 // Step is too small, just use the minimum step size
70 h = (forwards_integration) ? hmin : -hmin;
71 disableAdaptive = true;
72 }
73
74 // Step 1: derivatives evaluated at old values
75 ode.derivs(t0, xold, f1);
76
77 // Call post derivative callback after the first derivative evaluation (which might cache values)
79
80 Eigen::Map<Eigen::VectorXd> xnew1_w(&(xnew1[0]), N), f1_w(&(f1[0]), N);
81 xnew1_w = xold_w + h * (1.0 / 5.0) * f1_w;
82
83 ode.derivs(t0 + 1.0 / 5.0 * h, xnew1, f2);
84 Eigen::Map<Eigen::VectorXd> xnew2_w(&(xnew2[0]), N), f2_w(&(f2[0]), N);
85 xnew2_w = xold_w + h * (+3.0 / 40.0 * f1_w + 9.0 / 40.0 * f2_w);
86
87 ode.derivs(t0 + 3.0 / 10.0 * h, xnew2, f3);
88 Eigen::Map<Eigen::VectorXd> xnew3_w(&(xnew3[0]), N), f3_w(&(f3[0]), N);
89 xnew3_w = xold_w + h * (3.0 / 10.0 * f1_w - 9.0 / 10.0 * f2_w + 6.0 / 5.0 * f3_w);
90
91 ode.derivs(t0 + 3.0 / 5.0 * h, xnew3, f4);
92 Eigen::Map<Eigen::VectorXd> xnew4_w(&(xnew4[0]), N), f4_w(&(f4[0]), N);
93 xnew4_w = xold_w + h * (-11.0 / 54.0 * f1_w + 5.0 / 2.0 * f2_w - 70.0 / 27.0 * f3_w + 35.0 / 27.0 * f4_w);
94
95 ode.derivs(t0 + h, xnew4, f5);
96 Eigen::Map<Eigen::VectorXd> xnew5_w(&(xnew5[0]), N), f5_w(&(f5[0]), N);
97 xnew5_w =
98 xold_w
99 + h * (1631.0 / 55296 * f1_w + 175.0 / 512.0 * f2_w + 575.0 / 13824.0 * f3_w + 44275.0 / 110592.0 * f4_w + 253.0 / 4096.0 * f5_w);
100
101 // Updated values at the next step using 5-th order
102 ode.derivs(t0 + 7.0 / 8.0 * h, xnew5, f6);
103 Eigen::Map<Eigen::VectorXd> xnew_w(&(xnew[0]), N), f6_w(&(f6[0]), N);
104 xnew_w = xold_w + h * (37.0 / 378.0 * f1_w + 250.0 / 621.0 * f3_w + 125.0 / 594.0 * f4_w + 512.0 / 1771.0 * f6_w);
105
106 Eigen::Map<Eigen::VectorXd> error_w(&(error[0]), N);
107 error_w =
108 h
109 * (-277.0 / 64512.0 * f1_w + 6925.0 / 370944.0 * f3_w - 6925.0 / 202752.0 * f4_w - 277.0 / 14336.0 * f5_w + 277.0 / 7084.0 * f6_w);
110
111 max_error = error_w.norm();
112
113 // If the error is too large, make the step size smaller and try
114 // the step again
115 if (disableAdaptive) {
116 // Accept the step regardless of whether the error
117 // is too large or not
118 stepAccepted = true;
119 } else {
120 if (max_error > eps_allowed) {
121 // Take a smaller step next time, try again on this step
122 // But only if adaptive mode is on
123 // If eps_allowed == max_error (approximately), force the step to change to avoid infinite loop
124 h *= std::min(step_relax * pow(eps_allowed / max_error, 0.3), 0.999);
125 stepAccepted = false;
126 } else {
127 stepAccepted = true;
128 }
129 }
130
131 } else {
132 std::cout << format("accepted");
133 }
134 }
135
136 // Step has been accepted, update variables
137 t0 += h;
138 Itheta += 1;
139 xold = xnew;
140
141 ode.post_step_callback(t0, h, xnew);
142
143 // The error is already below the threshold
144 if (max_error < eps_allowed && disableAdaptive == false && max_error > 0) {
145 // Take a bigger step next time, since eps_allowed>max_error, but don't
146 // let the steps get much larger too quickly
147 h *= step_relax * pow(eps_allowed / max_error, 0.2);
148 }
149
150 // Constrain the step to not be too large
151 if (forwards_integration) {
152 h = std::min(h, hmax);
153 } else {
154 h = -std::min(std::abs(h), hmax);
155 }
156
157 // Overshot the end, oops... That's an error
158 if (forwards_integration && (t0 - tmax > +1e-3)) {
159 throw CoolProp::ValueError(format("t0 - tmax [%g] > 1e-3", t0 - tmax));
160 }
161 if (!forwards_integration && (t0 - tmax < -1e-3)) {
162 throw CoolProp::ValueError(format("t0 - tmax [%g] < -1e-3", t0 - tmax));
163 }
164 } while (((forwards_integration) && t0 < tmax - 1e-10) || ((!forwards_integration) && t0 > tmax + 1e-10));
165
166 // No termination was requested
167 return false;
168}
169
170#if defined(ENABLE_CATCH)
171# include <catch2/catch_all.hpp>
172
173TEST_CASE("Integrate y'=y", "[ODEIntegrator]") {
174 class SimpleODEIntegrator : public ODEIntegrators::AbstractODEIntegrator
175 {
176 public:
177 std::vector<double> t, h, y;
178
179 std::vector<double> get_initial_array() const override {
180 return std::vector<double>(1, 1);
181 }
182
183 void pre_step_callback() override {};
184
185 void post_deriv_callback() override {};
186
187 void post_step_callback(double t, double h, std::vector<double>& y) override {
188 this->t.push_back(t);
189 this->h.push_back(h);
190 this->y.push_back(y[0]);
191 };
192
193 bool premature_termination() override {
194 return false;
195 };
196
197 void derivs(double t, std::vector<double>& y, std::vector<double>& yprime) override {
198 yprime[0] = y[0];
199 };
200 };
201
202 SimpleODEIntegrator simple;
203 ODEIntegrators::AdaptiveRK54(simple, 0, 4, 1e-4, 0.5, 1e-7, 0.9);
204 double yfinal_integration = simple.y[simple.y.size() - 1];
205 double tfinal_integration = simple.t[simple.t.size() - 1];
206
207 double yfinal_analytic = exp(4.0);
208 double error = yfinal_integration / yfinal_analytic - 1;
209
210 CAPTURE(yfinal_analytic);
211 CAPTURE(yfinal_integration);
212 CAPTURE(tfinal_integration);
213 CHECK(std::abs(error) < 1e-6);
214 CHECK(std::abs(tfinal_integration - 4) < 1e-10);
215 int rr = 0;
216}
217#endif