OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
FvClosureKernels.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
57
58#ifndef OPENSWMM_ENGINE_FV_CLOSURE_KERNELS_HPP
59#define OPENSWMM_ENGINE_FV_CLOSURE_KERNELS_HPP
60
61#include <cmath>
62#include <cstdint>
63#include <type_traits>
64
65// Same portable-kernel marker as FvKernels.hpp / InertialKernels.hpp.
66#ifndef OPENSWMM_KERNEL_FN
67#define OPENSWMM_KERNEL_FN inline
68#endif
69
70namespace openswmm::fv {
71
73inline constexpr int kClosurePanels = 128;
74
76enum : uint8_t { kClosureTabulated = 0, kClosurePolynomial = 1 };
77
89struct FvClosure {
90 double y_full = 0.0;
91 double dh = 0.0;
92 double inv_dh = 0.0;
93 double a_crown = 0.0;
94 double t_slot = 0.0;
95 double i1_crown = 0.0;
96 double r_full = 0.0;
97 double inv_a_crown_n = 0.0;
98
99 // kClosurePolynomial: A = c2·h² + c1·h, P = p0 + p1·h on [0, y_full].
100 double c2 = 0.0, c1 = 0.0, p0 = 0.0, p1 = 0.0;
101
103
104 double A[kClosurePanels + 1] = {};
105 double M[kClosurePanels + 1] = {};
106 double I1[kClosurePanels + 1] = {};
107 double R[kClosurePanels + 1] = {};
108 int16_t j_of_a[kClosurePanels + 1] = {};
109};
110
111static_assert(std::is_trivially_copyable_v<FvClosure>,
112 "FvClosure must be a plain buffer a device backend can copy");
113static_assert(std::is_standard_layout_v<FvClosure>, "FvClosure must be standard-layout");
114
115namespace kernels {
116
119 double a = 0.0;
120 double t = 0.0;
121 double i1 = 0.0;
122};
123
137OPENSWMM_KERNEL_FN void closureLocate(const FvClosure& c, double h, int& i, double& t) noexcept {
138 double x = h * c.inv_dh;
139 i = static_cast<int>(x);
140 if (i < 0)
141 i = 0;
142 if (i > kClosurePanels - 1)
143 i = kClosurePanels - 1;
144 t = x - static_cast<double>(i);
145 if (t < 0.0)
146 t = 0.0;
147 if (t > 1.0)
148 t = 1.0;
149}
150
166OPENSWMM_KERNEL_FN void closureHermite(const FvClosure& c, int i, double t, double& a, double& dadh,
167 double& int_dh) noexcept {
168 const double a0 = c.A[i], a1 = c.A[i + 1];
169 const double m0 = c.M[i] * c.dh, m1 = c.M[i + 1] * c.dh; // slopes in t
170 const double t2 = t * t, t3 = t2 * t;
171 const double h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
172 const double h10 = t3 - 2.0 * t2 + t;
173 const double h01 = -2.0 * t3 + 3.0 * t2;
174 const double h11 = t3 - t2;
175 a = h00 * a0 + h10 * m0 + h01 * a1 + h11 * m1;
176 const double d00 = 6.0 * t2 - 6.0 * t;
177 const double d10 = 3.0 * t2 - 4.0 * t + 1.0;
178 const double d01 = -6.0 * t2 + 6.0 * t;
179 const double d11 = 3.0 * t2 - 2.0 * t;
180 dadh = (d00 * a0 + d10 * m0 + d01 * a1 + d11 * m1) * c.inv_dh;
181 const double t4 = t2 * t2;
182 const double i00 = 0.5 * t4 - t3 + t;
183 const double i10 = 0.25 * t4 - (2.0 / 3.0) * t3 + 0.5 * t2;
184 const double i01 = -0.5 * t4 + t3;
185 const double i11 = 0.25 * t4 - t3 / 3.0;
186 int_dh = (i00 * a0 + i10 * m0 + i01 * a1 + i11 * m1) * c.dh;
187}
188
204 ClosureEval e;
205 if (h <= 0.0)
206 return e;
207 if (h >= c.y_full) {
208 const double d = h - c.y_full;
209 e.a = c.a_crown + c.t_slot * d;
210 e.t = c.t_slot;
211 e.i1 = c.i1_crown + c.a_crown * d + 0.5 * c.t_slot * d * d;
212 return e;
213 }
214 if (c.kind == kClosurePolynomial) {
215 e.a = (c.c2 * h + c.c1) * h;
216 e.t = 2.0 * c.c2 * h + c.c1;
217 e.i1 = (c.c2 * h / 3.0 + 0.5 * c.c1) * h * h;
218 return e;
219 }
220 int i;
221 double t;
222 closureLocate(c, h, i, t);
223 double a, dadh, idh;
224 closureHermite(c, i, t, a, dadh, idh);
225 e.a = a;
226 e.t = (dadh > 0.0) ? dadh : 0.0;
227 e.i1 = c.I1[i] + idh;
228 return e;
229}
230
243OPENSWMM_KERNEL_FN ClosureEval closureEval(const FvClosure& c, double h, double& radius) noexcept {
244 ClosureEval e;
245 radius = 0.0;
246 if (h <= 0.0)
247 return e;
248 if (h >= c.y_full) {
249 radius = c.r_full;
250 const double d = h - c.y_full;
251 e.a = c.a_crown + c.t_slot * d;
252 e.t = c.t_slot;
253 e.i1 = c.i1_crown + c.a_crown * d + 0.5 * c.t_slot * d * d;
254 return e;
255 }
256 if (c.kind == kClosurePolynomial) {
257 e.a = (c.c2 * h + c.c1) * h;
258 const double p = c.p0 + c.p1 * h;
259 radius = (p > 0.0) ? (c.c2 * h + c.c1) * h / p : 0.0;
260 e.t = 2.0 * c.c2 * h + c.c1;
261 e.i1 = (c.c2 * h / 3.0 + 0.5 * c.c1) * h * h;
262 return e;
263 }
264 int i;
265 double t;
266 closureLocate(c, h, i, t);
267 radius = c.R[i] + t * (c.R[i + 1] - c.R[i]);
268 double a, dadh, idh;
269 closureHermite(c, i, t, a, dadh, idh);
270 e.a = a;
271 e.t = (dadh > 0.0) ? dadh : 0.0;
272 e.i1 = c.I1[i] + idh;
273 return e;
274}
275
289OPENSWMM_KERNEL_FN double closureArea(const FvClosure& c, double h) noexcept {
290 if (h <= 0.0)
291 return 0.0;
292 if (h >= c.y_full)
293 return c.a_crown + c.t_slot * (h - c.y_full);
294 if (c.kind == kClosurePolynomial)
295 return (c.c2 * h + c.c1) * h;
296 int i;
297 double t;
298 closureLocate(c, h, i, t);
299 double a, dadh, idh;
300 closureHermite(c, i, t, a, dadh, idh);
301 return a;
302}
303
317OPENSWMM_KERNEL_FN double closureWidth(const FvClosure& c, double h) noexcept {
318 if (h <= 0.0)
319 return 0.0;
320 if (h >= c.y_full)
321 return c.t_slot;
322 if (c.kind == kClosurePolynomial)
323 return 2.0 * c.c2 * h + c.c1;
324 int i;
325 double t;
326 closureLocate(c, h, i, t);
327 double a, dadh, idh;
328 closureHermite(c, i, t, a, dadh, idh);
329 return (dadh > 0.0) ? dadh : 0.0;
330}
331
345OPENSWMM_KERNEL_FN double closureI1(const FvClosure& c, double h) noexcept {
346 return closureEval(c, h).i1;
347}
348
363OPENSWMM_KERNEL_FN double closureHydRad(const FvClosure& c, double h) noexcept {
364 if (h <= 0.0)
365 return 0.0;
366 if (h >= c.y_full)
367 return c.r_full;
368 if (c.kind == kClosurePolynomial) {
369 const double p = c.p0 + c.p1 * h;
370 return (p > 0.0) ? (c.c2 * h + c.c1) * h / p : 0.0;
371 }
372 int i;
373 double t;
374 closureLocate(c, h, i, t);
375 return c.R[i] + t * (c.R[i + 1] - c.R[i]);
376}
377
394OPENSWMM_KERNEL_FN double closureDepthOfArea(const FvClosure& c, double a) noexcept {
395 if (a <= 0.0)
396 return 0.0;
397 if (a >= c.a_crown)
398 return c.y_full + (a - c.a_crown) / c.t_slot;
399 if (c.kind == kClosurePolynomial) {
400 if (c.c2 <= 0.0)
401 return (c.c1 > 0.0) ? a / c.c1 : 0.0;
402 return 2.0 * a / (c.c1 + std::sqrt(c.c1 * c.c1 + 4.0 * c.c2 * a));
403 }
404
405 int k = static_cast<int>(a * c.inv_a_crown_n);
406 if (k < 0)
407 k = 0;
408 if (k > kClosurePanels)
409 k = kClosurePanels;
410 int i = c.j_of_a[k];
411 while (i > 0 && c.A[i] > a)
412 --i;
413 while (i < kClosurePanels - 1 && c.A[i + 1] < a)
414 ++i;
415
416 const double a0 = c.A[i], a1 = c.A[i + 1];
417 if (a <= a0)
418 return static_cast<double>(i) * c.dh;
419 if (a >= a1)
420 return static_cast<double>(i + 1) * c.dh;
421
422 const double tol = 1.0e-15 * c.y_full * c.inv_dh; // in units of t
423 double lo = 0.0, hi = 1.0;
424 const double frac = (a - a0) / (a1 - a0);
425 // Seed: the secant, except in a panel whose lower node has zero width
426 // (the invert of a round section, A ∝ t² there): sqrt overestimates by
427 // ≤ √1.5 and Newton on the convex panel then descends monotonically.
428 double t = (c.M[i] > 0.0) ? frac : std::sqrt(frac);
429 for (int it = 0; it < 12; ++it) {
430 double f, dadh, idh;
431 closureHermite(c, i, t, f, dadh, idh);
432 f -= a;
433 if (f == 0.0)
434 break;
435 if (f > 0.0)
436 hi = t;
437 else
438 lo = t;
439 // Newton in the panel coordinate: dA/dt = dA/dh · dh. A step at
440 // round-off is convergence, not an excursion (it lands ON the
441 // bracket edge just set, which the bracket test would reject).
442 const double dadt = dadh * c.dh;
443 double step = (dadt > 0.0) ? -f / dadt : 0.0;
444 if (dadt > 0.0 && std::fabs(step) <= tol)
445 break;
446 double tn = t + step;
447 if (!(dadt > 0.0) || !(tn > lo && tn < hi))
448 tn = 0.5 * (lo + hi);
449 step = tn - t;
450 t = tn;
451 if (std::fabs(step) <= tol)
452 break;
453 }
454 return static_cast<double>(i) * c.dh + t * c.dh;
455}
456
457} // namespace kernels
458} // namespace openswmm::fv
459
460#endif // OPENSWMM_ENGINE_FV_CLOSURE_KERNELS_HPP
#define OPENSWMM_KERNEL_FN
Definition ExplicitKokkosSurfaceSolver.cpp:22
Definition FvClosureKernels.hpp:115
OPENSWMM_KERNEL_FN double closureArea(const FvClosure &c, double h) noexcept
Evaluate the closure flow area.
Definition FvClosureKernels.hpp:289
OPENSWMM_KERNEL_FN double closureWidth(const FvClosure &c, double h) noexcept
Evaluate the closure area derivative used as top width.
Definition FvClosureKernels.hpp:317
OPENSWMM_KERNEL_FN double closureI1(const FvClosure &c, double h) noexcept
Evaluate the closure hydrostatic first moment.
Definition FvClosureKernels.hpp:345
OPENSWMM_KERNEL_FN double closureDepthOfArea(const FvClosure &c, double a) noexcept
Invert aggregate closure area using the same forward geometry.
Definition FvClosureKernels.hpp:394
OPENSWMM_KERNEL_FN void closureHermite(const FvClosure &c, int i, double t, double &a, double &dadh, double &int_dh) noexcept
Evaluate one cubic area panel, its derivative and its partial integral.
Definition FvClosureKernels.hpp:166
OPENSWMM_KERNEL_FN void closureLocate(const FvClosure &c, double h, int &i, double &t) noexcept
Locate a uniform depth panel and its clamped local coordinate.
Definition FvClosureKernels.hpp:137
OPENSWMM_KERNEL_FN ClosureEval closureEval(const FvClosure &c, double h) noexcept
Evaluate area, top width and cumulative hydrostatic integral together.
Definition FvClosureKernels.hpp:203
OPENSWMM_KERNEL_FN double closureHydRad(const FvClosure &c, double h) noexcept
Evaluate the friction hydraulic radius.
Definition FvClosureKernels.hpp:363
Definition AcceptedWaterVolumes.hpp:8
@ kClosureTabulated
Definition FvClosureKernels.hpp:76
@ kClosurePolynomial
Definition FvClosureKernels.hpp:76
constexpr int kClosurePanels
Panels of the closure table, uniform in depth on [0, y_full].
Definition FvClosureKernels.hpp:73
One section's closure. Fixed-size POD: inline arrays, no pointers.
Definition FvClosureKernels.hpp:89
double inv_a_crown_n
kClosurePanels / a_crown
Definition FvClosureKernels.hpp:97
double r_full
R above the crown.
Definition FvClosureKernels.hpp:96
double t_slot
slot top width above the crown
Definition FvClosureKernels.hpp:94
uint8_t kind
Definition FvClosureKernels.hpp:102
int16_t j_of_a[kClosurePanels+1]
Definition FvClosureKernels.hpp:108
double a_crown
A(y_full) incl. the tapered slot = A[N].
Definition FvClosureKernels.hpp:93
double A[kClosurePanels+1]
Definition FvClosureKernels.hpp:104
double inv_dh
Definition FvClosureKernels.hpp:92
double y_full
Definition FvClosureKernels.hpp:90
double i1_crown
I₁(y_full) = I1[N].
Definition FvClosureKernels.hpp:95
double dh
Definition FvClosureKernels.hpp:91
double M[kClosurePanels+1]
Definition FvClosureKernels.hpp:105
double c1
Definition FvClosureKernels.hpp:100
double p1
Definition FvClosureKernels.hpp:100
double I1[kClosurePanels+1]
Definition FvClosureKernels.hpp:106
double R[kClosurePanels+1]
Definition FvClosureKernels.hpp:107
double c2
Definition FvClosureKernels.hpp:100
double p0
Definition FvClosureKernels.hpp:100
A, T and I₁ at one depth, from one panel locate.
Definition FvClosureKernels.hpp:118
double i1
Definition FvClosureKernels.hpp:121
double t
Definition FvClosureKernels.hpp:120
double a
Definition FvClosureKernels.hpp:119