OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
QuadVfr.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
61
62#ifndef OPENSWMM_ENGINE_2D_QUAD_VFR_HPP
63#define OPENSWMM_ENGINE_2D_QUAD_VFR_HPP
64
65#include <cmath>
66
67#include "VfrClosure.hpp"
68
69namespace openswmm::twoD {
70
73inline constexpr int kQuadVfrZ = 6;
74
88OPENSWMM_KERNEL_FN double quadWetFraction(const double* zs, double A1, double A2,
89 double eta) noexcept {
90 const double w1 = vfrWetFraction(zs[0], zs[1], zs[2], eta);
91 const double w2 = vfrWetFraction(zs[3], zs[4], zs[5], eta);
92 return (A1 * w1 + A2 * w2) / (A1 + A2);
93}
94
110OPENSWMM_KERNEL_FN double quadMeanDepthFromEta(const double* zs, double A1, double A2, double eta,
111 double eps) noexcept {
112 const double d1 = vfrMeanDepthFromEta(zs[0], zs[1], zs[2], eta, eps);
113 const double d2 = vfrMeanDepthFromEta(zs[3], zs[4], zs[5], eta, eps);
114 return (A1 * d1 + A2 * d2) / (A1 + A2);
115}
116
134OPENSWMM_KERNEL_FN double quadMeanDepthSlope(const double* zs, double A1, double A2, double eta,
135 double eps) noexcept {
136 double s = 0.0;
137 for (int k = 0; k < 2; ++k) {
138 const double z1 = zs[3 * k], z2 = zs[3 * k + 1], z3 = zs[3 * k + 2];
139 const double A = (k == 0) ? A1 : A2;
140 const double relief = z3 - z1;
141 double w;
142 if (eps <= 0.0 || relief < kVfrFlatRelief) {
143 w = vfrWetFraction(z1, z2, z3, eta);
144 } else {
145 const double eta_s = vfrStageAtWetFraction(z1, z2, z3, eps);
146 if (eta >= eta_s) {
147 w = vfrWetFraction(z1, z2, z3, eta);
148 } else {
149 const double d = vfrMeanDepthFromEta(z1, z2, z3, eta, eps);
150 w = (d > 0.0) ? eps : 0.0;
151 }
152 }
153 s += A * w;
154 }
155 return s / (A1 + A2);
156}
157
177OPENSWMM_KERNEL_FN double quadEtaFromMeanDepth(const double* zs, double A1, double A2,
178 double mean_depth, double eps) noexcept {
179 const double A = A1 + A2;
180 const double zbar1 = (zs[0] + zs[1] + zs[2]) / 3.0;
181 const double zbar2 = (zs[3] + zs[4] + zs[5]) / 3.0;
182 const double zw = (A1 * zbar1 + A2 * zbar2) / A;
183 const double ztop = (zs[2] > zs[5]) ? zs[2] : zs[5];
184 const double zlow = (zs[0] < zs[3]) ? zs[0] : zs[3];
185 const double relief = ztop - zlow;
186
187 // Flat (or degenerate) cell: the flat closure is exact.
188 if (relief < kVfrFlatRelief)
189 return zw + ((mean_depth > 0.0) ? mean_depth : 0.0);
190
191 // Fully wet: both sub-triangles submerged — flat closure exact.
192 if (mean_depth >= ztop - zw)
193 return zw + mean_depth;
194
195 // Dry limit: the stage at which the regularised sum reaches zero — the
196 // lower of the two sub-triangle dry stages (each tail ends at η_s − h_s/ε).
197 const double dry1 = vfrDryEta(zs[0], zs[1], zs[2], eps);
198 const double dry2 = vfrDryEta(zs[3], zs[4], zs[5], eps);
199 const double lo0 = (dry1 < dry2) ? dry1 : dry2;
200 if (!(mean_depth > 0.0))
201 return lo0;
202
203 // Safeguarded Newton on the monotone sum over [lo0, ztop].
204 double lo = lo0, hi = ztop;
205 double eta = zw + mean_depth; // flat-closure guess
206 if (eta <= lo || eta >= hi)
207 eta = 0.5 * (lo + hi);
208 for (int it = 0; it < 80; ++it) {
209 const double f = quadMeanDepthFromEta(zs, A1, A2, eta, eps) - mean_depth;
210 if (f > 0.0)
211 hi = eta;
212 else
213 lo = eta;
214 const double df = quadMeanDepthSlope(zs, A1, A2, eta, eps);
215 double next = (df > 1.0e-12) ? eta - f / df : 0.5 * (lo + hi);
216 if (next <= lo || next >= hi)
217 next = 0.5 * (lo + hi); // safeguard
218 if (std::abs(next - eta) < 1.0e-13 * (1.0 + relief))
219 return next;
220 eta = next;
221 }
222 return eta;
223}
224
239OPENSWMM_KERNEL_FN double quadDEtaDMeanDepth(const double* zs, double A1, double A2, double eta,
240 double eps) noexcept {
241 double w = quadMeanDepthSlope(zs, A1, A2, eta, eps);
242 if (w < eps)
243 w = eps;
244 if (w < 1.0e-12)
245 w = 1.0e-12;
246 return 1.0 / w;
247}
248
264inline int quadVfrPrecompute(const double* x, const double* y, const double* z, double* zs,
265 double& A1, double& A2) noexcept {
266 // Sort the four cyclic positions by elevation (stable insertion sort so
267 // ties keep cyclic order — any consistent choice is a valid split).
268 int p[4] = {0, 1, 2, 3};
269 for (int i = 1; i < 4; ++i) {
270 const int key = p[i];
271 int j = i - 1;
272 while (j >= 0 && z[p[j]] > z[key]) {
273 p[j + 1] = p[j];
274 --j;
275 }
276 p[j + 1] = key;
277 }
278 const int n1 = p[0], n2 = p[1], n3 = p[2], n4 = p[3];
279 auto adjacent = [](int a, int b) {
280 const int d = (a - b + 4) % 4;
281 return d == 1 || d == 3;
282 };
283
284 int t1[3], t2[3], kase;
285 if (!adjacent(n1, n4)) { // Case 1: n1–n4 is a diagonal
286 kase = 1;
287 t1[0] = n1;
288 t1[1] = n2;
289 t1[2] = n4;
290 t2[0] = n1;
291 t2[1] = n3;
292 t2[2] = n4;
293 } else if (adjacent(n2, n1)) { // Case 2: diagonal n2–n4
294 kase = 2;
295 t1[0] = n1;
296 t1[1] = n2;
297 t1[2] = n4;
298 t2[0] = n2;
299 t2[1] = n3;
300 t2[2] = n4;
301 } else { // Case 3: diagonal n3–n4
302 kase = 3;
303 t1[0] = n1;
304 t1[1] = n3;
305 t1[2] = n4;
306 t2[0] = n2;
307 t2[1] = n3;
308 t2[2] = n4;
309 }
310 auto tri_area = [&](const int* t) {
311 const double dx1 = x[t[1]] - x[t[0]], dy1 = y[t[1]] - y[t[0]];
312 const double dx2 = x[t[2]] - x[t[0]], dy2 = y[t[2]] - y[t[0]];
313 return 0.5 * std::abs(dx1 * dy2 - dx2 * dy1);
314 };
315 A1 = tri_area(t1);
316 A2 = tri_area(t2);
317 zs[0] = z[t1[0]];
318 zs[1] = z[t1[1]];
319 zs[2] = z[t1[2]];
320 zs[3] = z[t2[0]];
321 zs[4] = z[t2[1]];
322 zs[5] = z[t2[2]];
323 vfrSort3(zs[0], zs[1], zs[2]);
324 vfrSort3(zs[3], zs[4], zs[5]);
325 // Degenerate sub-triangle (collinear): fold everything into the other so
326 // the closure stays well-defined (A1 + A2 > 0 was validated upstream).
327 if (!(A1 > 0.0)) {
328 A1 = 0.0;
329 }
330 if (!(A2 > 0.0)) {
331 A2 = 0.0;
332 }
333 return kase;
334}
335
336} // namespace openswmm::twoD
337
338#endif // OPENSWMM_ENGINE_2D_QUAD_VFR_HPP
#define OPENSWMM_KERNEL_FN
Definition ExplicitKokkosSurfaceSolver.cpp:22
Volume–free-surface (VFR) closure for a planar-bed triangular cell.
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
constexpr int kQuadVfrZ
Definition QuadVfr.hpp:73
OPENSWMM_KERNEL_FN double quadEtaFromMeanDepth(const double *zs, double A1, double A2, double mean_depth, double eps) noexcept
Regularised free-surface elevation η(h̄) of the quad — the solver closure. Monotone inverse of quadMe...
Definition QuadVfr.hpp:177
int quadVfrPrecompute(const double *x, const double *y, const double *z, double *zs, double &A1, double &A2) noexcept
Choose the B&S 2007 diagonal for a quad and emit its precomputed VFR data.
Definition QuadVfr.hpp:264
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 quadDEtaDMeanDepth(const double *zs, double A1, double A2, double eta, double eps) noexcept
dη/dh̄ of the regularised quad closure at stage η: 1/max(slope, eps).
Definition QuadVfr.hpp:239
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 quadMeanDepthFromEta(const double *zs, double A1, double A2, double eta, double eps) noexcept
Regularised cell-mean depth h̄(η) of the quad — the area-weighted sum of the two sub-triangle closure...
Definition QuadVfr.hpp:110
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 quadWetFraction(const double *zs, double A1, double A2, double eta) noexcept
Wetted-area fraction of the quad at stage η (dh̄/dη away from the ε-tail). zs: six sorted elevations ...
Definition QuadVfr.hpp:88
OPENSWMM_KERNEL_FN double quadMeanDepthSlope(const double *zs, double A1, double A2, double eta, double eps) noexcept
Slope dh̄/dη of the REGULARISED sum at η: each sub-triangle contributes its wet fraction above its sw...
Definition QuadVfr.hpp:134
double * y
Definition odesolve.c:37