OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
OdeSolverDetail.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2026 Caleb Buahin
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
24#pragma once
25#include "OdeSolver.hpp"
26#include <cmath>
27#include <utility>
28
29namespace openswmm::ode {
30namespace detail {
31// Cash-Karp RK45 coefficients
32static constexpr double a2 = 0.2, a3 = 0.3, a4 = 0.6, a5 = 1.0, a6 = 0.875;
33
34static constexpr double b21 = 0.2;
35static constexpr double b31 = 3.0 / 40.0, b32 = 9.0 / 40.0;
36static constexpr double b41 = 0.3, b42 = -0.9, b43 = 1.2;
37static constexpr double b51 = -11.0 / 54.0, b52 = 2.5, b53 = -70.0 / 27.0, b54 = 35.0 / 27.0;
38static constexpr double b61 = 1631.0 / 55296.0, b62 = 175.0 / 512.0, b63 = 575.0 / 13824.0,
39 b64 = 44275.0 / 110592.0, b65 = 253.0 / 4096.0;
40
41static constexpr double c1 = 37.0 / 378.0, c3 = 250.0 / 621.0, c4 = 125.0 / 594.0,
42 c6 = 512.0 / 1771.0;
43static constexpr double dc1 = c1 - 2825.0 / 27648.0;
44static constexpr double dc3 = c3 - 18575.0 / 48384.0;
45static constexpr double dc4 = c4 - 13525.0 / 55296.0;
46static constexpr double dc5 = -277.0 / 14336.0;
47static constexpr double dc6 = c6 - 0.25;
48
50 int nmax = 0;
51 double* y = nullptr; // internal state buffer (matches legacy y[])
52 double* yscal = nullptr;
53 double* dydx = nullptr;
54 double* yerr = nullptr;
55 double* ytemp = nullptr;
56 double* ak = nullptr; // flat n*5 (ak2..ak6) matching legacy
57
64};
66
67// ============================================================================
68// rkck — One RK45 Cash-Karp step (matching legacy rkck exactly)
69// ============================================================================
70
82template <typename F> void rkck(OdeWorkspace& ws_, double x, int n, double h, F&& derivs) {
83 double* ak2 = ws_.ak;
84 double* ak3 = ws_.ak + n;
85 double* ak4 = ws_.ak + n * 2;
86 double* ak5 = ws_.ak + n * 3;
87 double* ak6 = ws_.ak + n * 4;
88
89 for (int i = 0; i < n; i++)
90 ws_.ytemp[i] = ws_.y[i] + b21 * h * ws_.dydx[i];
91 derivs(x + a2 * h, ws_.ytemp, ak2);
92
93 for (int i = 0; i < n; i++)
94 ws_.ytemp[i] = ws_.y[i] + h * (b31 * ws_.dydx[i] + b32 * ak2[i]);
95 derivs(x + a3 * h, ws_.ytemp, ak3);
96
97 for (int i = 0; i < n; i++)
98 ws_.ytemp[i] = ws_.y[i] + h * (b41 * ws_.dydx[i] + b42 * ak2[i] + b43 * ak3[i]);
99 derivs(x + a4 * h, ws_.ytemp, ak4);
100
101 for (int i = 0; i < n; i++)
102 ws_.ytemp[i] =
103 ws_.y[i] + h * (b51 * ws_.dydx[i] + b52 * ak2[i] + b53 * ak3[i] + b54 * ak4[i]);
104 derivs(x + a5 * h, ws_.ytemp, ak5);
105
106 for (int i = 0; i < n; i++)
107 ws_.ytemp[i] = ws_.y[i] + h * (b61 * ws_.dydx[i] + b62 * ak2[i] + b63 * ak3[i] +
108 b64 * ak4[i] + b65 * ak5[i]);
109 derivs(x + a6 * h, ws_.ytemp, ak6);
110
111 for (int i = 0; i < n; i++)
112 ws_.ytemp[i] = ws_.y[i] + h * (c1 * ws_.dydx[i] + c3 * ak3[i] + c4 * ak4[i] + c6 * ak6[i]);
113
114 for (int i = 0; i < n; i++)
115 ws_.yerr[i] =
116 h * (dc1 * ws_.dydx[i] + dc3 * ak3[i] + dc4 * ak4[i] + dc5 * ak5[i] + dc6 * ak6[i]);
117}
118
119// ============================================================================
120// rkqs — Quality-controlled RK step (matching legacy rkqs exactly)
121// ============================================================================
122
137template <typename F>
138int rkqs(OdeWorkspace& ws_, double* x, int n, double htry, double eps, double* hdid, double* hnext,
139 F&& derivs) {
140 double err, errmax, h, htemp, xnew, xold = *x;
141
142 h = htry;
143 for (;;) {
144 rkck(ws_, xold, n, h, derivs);
145
146 errmax = 0.0;
147 for (int i = 0; i < n; i++) {
148 err = std::fabs(ws_.yerr[i] / ws_.yscal[i]);
149 if (err > errmax)
150 errmax = err;
151 }
152 errmax /= eps;
153
154 if (errmax > 1.0) {
155 htemp = SAFETY * h * std::pow(errmax, PSHRNK);
156 if (h >= 0) {
157 if (htemp > 0.1 * h)
158 h = htemp;
159 else
160 h = 0.1 * h;
161 } else {
162 if (htemp < 0.1 * h)
163 h = htemp;
164 else
165 h = 0.1 * h;
166 }
167 xnew = xold + h;
168 if (xnew == xold)
169 return ODE_UNDERFLOW;
170 continue;
171 } else {
172 if (errmax > ERRCON)
173 *hnext = SAFETY * h * std::pow(errmax, PGROW);
174 else
175 *hnext = 5.0 * h;
176 *x += (*hdid = h);
177 for (int i = 0; i < n; i++)
178 ws_.y[i] = ws_.ytemp[i];
179 return ODE_OK;
180 }
181 }
182}
183
184template <typename F>
185int integrate(double* ystart, int n, double x1, double x2, double eps, double h1,
186 F&& derivs) {
187 auto& ws_ = workspace(n);
188 if (ws_.nmax < n)
189 return ODE_TOO_MANY;
190
191 double hdid, hnext;
192 double x = x1;
193 double h = h1;
194
195 for (int i = 0; i < n; i++)
196 ws_.y[i] = ystart[i];
197
198 for (int nstp = 1; nstp <= MAXSTP; nstp++) {
199 derivs(x, ws_.y, ws_.dydx);
200 for (int i = 0; i < n; i++)
201 ws_.yscal[i] = std::fabs(ws_.y[i]) + std::fabs(ws_.dydx[i] * h) + TINY;
202 if ((x + h - x2) * (x + h - x1) > 0.0)
203 h = x2 - x;
204
205 int errcode = rkqs(ws_, &x, n, h, eps, &hdid, &hnext, derivs);
206 if (errcode)
207 break;
208
209 if ((x - x2) * (x2 - x1) >= 0.0) {
210 for (int i = 0; i < n; i++)
211 ystart[i] = ws_.y[i];
212 return ODE_OK;
213 }
214 if (std::fabs(hnext) <= 0.0)
215 return ODE_UNDERFLOW;
216 h = hnext;
217 }
218 return ODE_MAX_STEPS;
219}
220} // namespace detail
221
222template <typename F>
223int integrateCallable(double* y, int n, double x1, double x2, double eps, double h1, F&& derivs) {
224 return detail::integrate(y, n, x1, x2, eps, h1, std::forward<F>(derivs));
225}
226} // namespace openswmm::ode
Runge-Kutta-Cash-Karp (RK45) ODE integrator with adaptive step control.
Definition OdeSolver.cpp:37
OdeWorkspace & workspace(int n)
Grow this thread's reusable solver arrays for n equations.
Definition OdeSolver.cpp:84
int rkqs(OdeWorkspace &ws_, double *x, int n, double htry, double eps, double *hdid, double *hnext, F &&derivs)
Reject and shrink trial RK45 steps until local error is acceptable.
Definition OdeSolverDetail.hpp:138
void rkck(OdeWorkspace &ws_, double x, int n, double h, F &&derivs)
Evaluate one Cash-Karp trial step in the thread-local workspace.
Definition OdeSolverDetail.hpp:82
int integrate(double *ystart, int n, double x1, double x2, double eps, double h1, F &&derivs)
Definition OdeSolverDetail.hpp:185
Definition OdeSolver.cpp:35
constexpr int MAXSTP
Maximum integration steps.
Definition OdeSolver.hpp:55
constexpr double PSHRNK
Exponent for step decrease.
Definition OdeSolver.hpp:59
int integrateCallable(double *y, int n, double x1, double x2, double eps, double h1, F &&derivs)
Integrate with a concrete synchronous derivative callable.
Definition OdeSolverDetail.hpp:223
constexpr double SAFETY
Step adjustment safety factor.
Definition OdeSolver.hpp:57
constexpr double PGROW
Exponent for step increase.
Definition OdeSolver.hpp:58
constexpr int ODE_UNDERFLOW
Step size underflowed.
Definition OdeSolver.hpp:68
constexpr int ODE_OK
Success.
Definition OdeSolver.hpp:66
constexpr int ODE_MAX_STEPS
Exceeded MAXSTP.
Definition OdeSolver.hpp:69
constexpr double TINY
Underflow protection.
Definition OdeSolver.hpp:56
constexpr double ERRCON
= (5/SAFETY)^(1/PGROW)
Definition OdeSolver.hpp:60
constexpr int ODE_TOO_MANY
n > allocated max
Definition OdeSolver.hpp:67
double * y
Definition odesolve.c:37
Definition OdeSolverDetail.hpp:49
double * yerr
Definition OdeSolverDetail.hpp:54
double * ytemp
Definition OdeSolverDetail.hpp:55
double * y
Definition OdeSolverDetail.hpp:51
~OdeWorkspace()
Release this thread's six solver buffers and reset their pointers.
Definition OdeSolver.cpp:53
double * yscal
Definition OdeSolverDetail.hpp:52
int nmax
Definition OdeSolverDetail.hpp:50
double * dydx
Definition OdeSolverDetail.hpp:53
double * ak
Definition OdeSolverDetail.hpp:56