OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
VfrClosure.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_2D_VFR_CLOSURE_HPP
59#define OPENSWMM_ENGINE_2D_VFR_CLOSURE_HPP
60
61#include <cmath>
62
63// Portable kernel-function marker (P5 Kokkos port) — see InertialKernels.hpp.
64// Host builds: plain inline. The GPU plugin defines OPENSWMM_KERNEL_FN to
65// KOKKOS_INLINE_FUNCTION before including so the identical closure bodies are
66// device-callable.
67#ifndef OPENSWMM_KERNEL_FN
68#define OPENSWMM_KERNEL_FN inline
69#endif
70
71namespace openswmm::twoD {
72
75inline constexpr double kVfrFlatRelief = 1.0e-9;
76
85OPENSWMM_KERNEL_FN void vfrSort3(double& z1, double& z2, double& z3) noexcept {
86 if (z1 > z2) {
87 const double t = z1;
88 z1 = z2;
89 z2 = t;
90 }
91 if (z2 > z3) {
92 const double t = z2;
93 z2 = z3;
94 z3 = t;
95 }
96 if (z1 > z2) {
97 const double t = z1;
98 z1 = z2;
99 z2 = t;
100 }
101}
102
117OPENSWMM_KERNEL_FN double vfrWetFraction(double z1, double z2, double z3, double eta) noexcept {
118 const double relief = z3 - z1;
119 if (relief < kVfrFlatRelief)
120 return (eta > z1) ? 1.0 : 0.0;
121 if (eta <= z1)
122 return 0.0;
123 if (eta >= z3)
124 return 1.0;
125 if (eta <= z2) {
126 const double d21 = z2 - z1;
127 if (d21 < kVfrFlatRelief)
128 return 0.0; // measure-zero band z1==z2
129 const double t = eta - z1;
130 return t * t / (d21 * relief);
131 }
132 // z2 < eta < z3 (z3 > z2 strictly here)
133 const double d32 = z3 - z2;
134 const double t = z3 - eta;
135 return 1.0 - t * t / (relief * d32);
136}
137
151OPENSWMM_KERNEL_FN double vfrMeanDepthFromEtaExact(double z1, double z2, double z3,
152 double eta) noexcept {
153 const double zbar = (z1 + z2 + z3) / 3.0;
154 const double relief = z3 - z1;
155 if (relief < kVfrFlatRelief)
156 return (eta > zbar) ? (eta - zbar) : 0.0;
157 if (eta <= z1)
158 return 0.0;
159 if (eta >= z3)
160 return eta - zbar;
161 if (eta <= z2) {
162 const double d21 = z2 - z1;
163 if (d21 < kVfrFlatRelief)
164 return 0.0; // measure-zero band z1==z2
165 const double t = eta - z1;
166 return t * t * t / (3.0 * d21 * relief);
167 }
168 const double d32 = z3 - z2;
169 const double t = z3 - eta;
170 return (eta - zbar) + t * t * t / (3.0 * relief * d32);
171}
172
186OPENSWMM_KERNEL_FN double vfrStageAtWetFraction(double z1, double z2, double z3,
187 double eps) noexcept {
188 const double relief = z3 - z1;
189 const double d21 = z2 - z1;
190 // Lower branch reaches wet fraction (z2−z1)/(z3−z1) at η = z2.
191 if (eps <= d21 / relief)
192 return z1 + std::sqrt(eps * d21 * relief);
193 const double d32 = z3 - z2;
194 return z3 - std::sqrt((1.0 - eps) * relief * d32);
195}
196
214OPENSWMM_KERNEL_FN double vfrMeanDepthFromEta(double z1, double z2, double z3, double eta,
215 double eps) noexcept {
216 const double relief = z3 - z1;
217 if (eps <= 0.0 || relief < kVfrFlatRelief)
218 return vfrMeanDepthFromEtaExact(z1, z2, z3, eta);
219 const double eta_s = vfrStageAtWetFraction(z1, z2, z3, eps);
220 if (eta >= eta_s)
221 return vfrMeanDepthFromEtaExact(z1, z2, z3, eta);
222 const double h_s = vfrMeanDepthFromEtaExact(z1, z2, z3, eta_s);
223 const double h = h_s - eps * (eta_s - eta);
224 return (h > 0.0) ? h : 0.0;
225}
226
249OPENSWMM_KERNEL_FN double vfrEtaFromMeanDepth(double z1, double z2, double z3, double mean_depth,
250 double eps) noexcept {
251 const double zbar = (z1 + z2 + z3) / 3.0;
252 const double relief = z3 - z1;
253
254 // Flat (or degenerate) cell: the flat closure is exact.
255 if (relief < kVfrFlatRelief)
256 return zbar + ((mean_depth > 0.0) ? mean_depth : 0.0);
257
258 // Fully wet: flat closure is exact. Checked FIRST — it is the dominant case
259 // in deep water (e.g. flooded urban meshes) and skips the ε-tail's sqrt/cubic
260 // switch-point evaluation below, which every cell would otherwise pay each
261 // RHS. The fully-wet threshold z3−z̄ always exceeds the ε-tail depth h_s, so
262 // reordering is exact (the two branches never overlap).
263 if (mean_depth >= z3 - zbar)
264 return zbar + mean_depth;
265
266 // Regularized tail: below the switch depth h_s the closure is linear.
267 double eta_s = z1, h_s = 0.0;
268 if (eps > 0.0) {
269 eta_s = vfrStageAtWetFraction(z1, z2, z3, eps);
270 h_s = vfrMeanDepthFromEtaExact(z1, z2, z3, eta_s);
271 const double h = (mean_depth > 0.0) ? mean_depth : 0.0;
272 if (h <= h_s)
273 return eta_s - (h_s - h) / eps;
274 } else if (!(mean_depth > 0.0)) {
275 return z1; // exact relation, dry limit
276 }
277
278 // Lower branch (z1 < η ≤ z2): closed-form cube root.
279 const double d21 = z2 - z1;
280 const double h_at_z2 = d21 * d21 / (3.0 * relief);
281 if (mean_depth <= h_at_z2)
282 return z1 + std::cbrt(3.0 * mean_depth * d21 * relief);
283
284 // z2 == z3 (to rounding): the upper branch is an empty interval — the
285 // fully-wet check above and h_at_z2 meet at R/3 — but 1-ulp slivers must
286 // not reach the 1/(relief·d32) divisions below.
287 if (z3 - z2 < kVfrFlatRelief)
288 return zbar + mean_depth;
289
290 // Upper branch (z2 < η < z3): safeguarded Newton on the bracket [z2, z3].
291 // h̄ is strictly increasing (dh̄/dη = A_wet/A > 0 here) so this converges
292 // unconditionally. Mirrors the pre-existing render-path iteration.
293 const double d32 = z3 - z2;
294 const double denom = 3.0 * relief * d32;
295 double lo = z2, hi = z3;
296 double eta = zbar + mean_depth; // flat-closure initial guess
297 if (eta <= lo || eta >= hi)
298 eta = 0.5 * (lo + hi);
299 for (int it = 0; it < 64; ++it) {
300 const double dz3 = z3 - eta;
301 const double f = (eta - zbar) + dz3 * dz3 * dz3 / denom - mean_depth;
302 if (f > 0.0)
303 hi = eta;
304 else
305 lo = eta;
306 const double df = 1.0 - dz3 * dz3 / (relief * d32); // A_wet/A
307 double next = (df > 1.0e-12) ? eta - f / df : 0.5 * (lo + hi);
308 if (next <= lo || next >= hi)
309 next = 0.5 * (lo + hi); // safeguard
310 if (std::abs(next - eta) < 1.0e-12 * (1.0 + relief))
311 return next;
312 eta = next;
313 }
314 return eta;
315}
316
332OPENSWMM_KERNEL_FN double vfrDryEta(double z1, double z2, double z3, double eps) noexcept {
333 return vfrEtaFromMeanDepth(z1, z2, z3, 0.0, eps);
334}
335
353OPENSWMM_KERNEL_FN double vfrDEtaDMeanDepth(double z1, double z2, double z3, double eta,
354 double eps) noexcept {
355 double w = vfrWetFraction(z1, z2, z3, eta);
356 if (w < eps)
357 w = eps;
358 if (w < 1.0e-12)
359 w = 1.0e-12; // eps == 0 safety (exact relation, dry cell)
360 return 1.0 / w;
361}
362
363} // namespace openswmm::twoD
364
365#endif // OPENSWMM_ENGINE_2D_VFR_CLOSURE_HPP
#define OPENSWMM_KERNEL_FN
Definition ExplicitKokkosSurfaceSolver.cpp:22
Definition ExchangeLaw.hpp:60
OPENSWMM_KERNEL_FN double vfrMeanDepthFromEta(double z1, double z2, double z3, double eta, double eps) noexcept
Regularized cell-mean depth h̄(η): exact for η ≥ η_s (wet fraction ≥ eps), tangent-line continuation ...
Definition VfrClosure.hpp:214
OPENSWMM_KERNEL_FN void vfrSort3(double &z1, double &z2, double &z3) noexcept
Sort three vertex elevations in place so z1 <= z2 <= z3.
Definition VfrClosure.hpp:85
OPENSWMM_KERNEL_FN double vfrDryEta(double z1, double z2, double z3, double eps) noexcept
Dry-cell free surface of the regularized closure: η(V = 0). This is the value a dry cell's head must ...
Definition VfrClosure.hpp:332
constexpr double kVfrFlatRelief
Definition VfrClosure.hpp:75
OPENSWMM_KERNEL_FN double vfrWetFraction(double z1, double z2, double z3, double eta) noexcept
Wetted-area fraction A_wet/A of the planar-bed cell at stage eta. Piecewise C0, monotone from 0 (η ≤ ...
Definition VfrClosure.hpp:117
OPENSWMM_KERNEL_FN double vfrStageAtWetFraction(double z1, double z2, double z3, double eps) noexcept
Stage η_s at which the wetted-area fraction equals eps (0 < eps < 1). Inputs must be sorted and non-f...
Definition VfrClosure.hpp:186
OPENSWMM_KERNEL_FN double vfrDEtaDMeanDepth(double z1, double z2, double z3, double eta, double eps) noexcept
dη/dh̄ of the regularized closure at stage eta: 1/max(w(η), eps). Divide by the cell area A to get dη...
Definition VfrClosure.hpp:353
OPENSWMM_KERNEL_FN double vfrEtaFromMeanDepth(double z1, double z2, double z3, double mean_depth, double eps) noexcept
Regularized free-surface elevation η(h̄) — the solver closure.
Definition VfrClosure.hpp:249
OPENSWMM_KERNEL_FN double vfrMeanDepthFromEtaExact(double z1, double z2, double z3, double eta) noexcept
EXACT cell-mean depth h̄(η) of the planar-bed cell (per unit area; V = A·h̄). Returns 0 for η ≤ z1....
Definition VfrClosure.hpp:151