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
78OPENSWMM_KERNEL_FN void vfrSort3(double& z1, double& z2, double& z3) noexcept {
79 if (z1 > z2) { const double t = z1; z1 = z2; z2 = t; }
80 if (z2 > z3) { const double t = z2; z2 = z3; z3 = t; }
81 if (z1 > z2) { const double t = z1; z1 = z2; z2 = t; }
82}
83
87OPENSWMM_KERNEL_FN double vfrWetFraction(double z1, double z2, double z3,
88 double eta) noexcept {
89 const double relief = z3 - z1;
90 if (relief < kVfrFlatRelief) return (eta > z1) ? 1.0 : 0.0;
91 if (eta <= z1) return 0.0;
92 if (eta >= z3) return 1.0;
93 if (eta <= z2) {
94 const double d21 = z2 - z1;
95 if (d21 < kVfrFlatRelief) return 0.0; // measure-zero band z1==z2
96 const double t = eta - z1;
97 return t * t / (d21 * relief);
98 }
99 // z2 < eta < z3 (z3 > z2 strictly here)
100 const double d32 = z3 - z2;
101 const double t = z3 - eta;
102 return 1.0 - t * t / (relief * d32);
103}
104
107OPENSWMM_KERNEL_FN double vfrMeanDepthFromEtaExact(double z1, double z2, double z3,
108 double eta) noexcept {
109 const double zbar = (z1 + z2 + z3) / 3.0;
110 const double relief = z3 - z1;
111 if (relief < kVfrFlatRelief)
112 return (eta > zbar) ? (eta - zbar) : 0.0;
113 if (eta <= z1) return 0.0;
114 if (eta >= z3) return eta - zbar;
115 if (eta <= z2) {
116 const double d21 = z2 - z1;
117 if (d21 < kVfrFlatRelief) return 0.0; // measure-zero band z1==z2
118 const double t = eta - z1;
119 return t * t * t / (3.0 * d21 * relief);
120 }
121 const double d32 = z3 - z2;
122 const double t = z3 - eta;
123 return (eta - zbar) + t * t * t / (3.0 * relief * d32);
124}
125
128OPENSWMM_KERNEL_FN double vfrStageAtWetFraction(double z1, double z2, double z3,
129 double eps) noexcept {
130 const double relief = z3 - z1;
131 const double d21 = z2 - z1;
132 // Lower branch reaches wet fraction (z2−z1)/(z3−z1) at η = z2.
133 if (eps <= d21 / relief)
134 return z1 + std::sqrt(eps * d21 * relief);
135 const double d32 = z3 - z2;
136 return z3 - std::sqrt((1.0 - eps) * relief * d32);
137}
138
144OPENSWMM_KERNEL_FN double vfrMeanDepthFromEta(double z1, double z2, double z3,
145 double eta, double eps) noexcept {
146 const double relief = z3 - z1;
147 if (eps <= 0.0 || relief < kVfrFlatRelief)
148 return vfrMeanDepthFromEtaExact(z1, z2, z3, eta);
149 const double eta_s = vfrStageAtWetFraction(z1, z2, z3, eps);
150 if (eta >= eta_s)
151 return vfrMeanDepthFromEtaExact(z1, z2, z3, eta);
152 const double h_s = vfrMeanDepthFromEtaExact(z1, z2, z3, eta_s);
153 const double h = h_s - eps * (eta_s - eta);
154 return (h > 0.0) ? h : 0.0;
155}
156
165OPENSWMM_KERNEL_FN double vfrEtaFromMeanDepth(double z1, double z2, double z3,
166 double mean_depth, double eps) noexcept {
167 const double zbar = (z1 + z2 + z3) / 3.0;
168 const double relief = z3 - z1;
169
170 // Flat (or degenerate) cell: the flat closure is exact.
171 if (relief < kVfrFlatRelief)
172 return zbar + ((mean_depth > 0.0) ? mean_depth : 0.0);
173
174 // Fully wet: flat closure is exact. Checked FIRST — it is the dominant case
175 // in deep water (e.g. flooded urban meshes) and skips the ε-tail's sqrt/cubic
176 // switch-point evaluation below, which every cell would otherwise pay each
177 // RHS. The fully-wet threshold z3−z̄ always exceeds the ε-tail depth h_s, so
178 // reordering is exact (the two branches never overlap).
179 if (mean_depth >= z3 - zbar) return zbar + mean_depth;
180
181 // Regularized tail: below the switch depth h_s the closure is linear.
182 double eta_s = z1, h_s = 0.0;
183 if (eps > 0.0) {
184 eta_s = vfrStageAtWetFraction(z1, z2, z3, eps);
185 h_s = vfrMeanDepthFromEtaExact(z1, z2, z3, eta_s);
186 const double h = (mean_depth > 0.0) ? mean_depth : 0.0;
187 if (h <= h_s) return eta_s - (h_s - h) / eps;
188 } else if (!(mean_depth > 0.0)) {
189 return z1; // exact relation, dry limit
190 }
191
192 // Lower branch (z1 < η ≤ z2): closed-form cube root.
193 const double d21 = z2 - z1;
194 const double h_at_z2 = d21 * d21 / (3.0 * relief);
195 if (mean_depth <= h_at_z2)
196 return z1 + std::cbrt(3.0 * mean_depth * d21 * relief);
197
198 // z2 == z3 (to rounding): the upper branch is an empty interval — the
199 // fully-wet check above and h_at_z2 meet at R/3 — but 1-ulp slivers must
200 // not reach the 1/(relief·d32) divisions below.
201 if (z3 - z2 < kVfrFlatRelief) return zbar + mean_depth;
202
203 // Upper branch (z2 < η < z3): safeguarded Newton on the bracket [z2, z3].
204 // h̄ is strictly increasing (dh̄/dη = A_wet/A > 0 here) so this converges
205 // unconditionally. Mirrors the pre-existing render-path iteration.
206 const double d32 = z3 - z2;
207 const double denom = 3.0 * relief * d32;
208 double lo = z2, hi = z3;
209 double eta = zbar + mean_depth; // flat-closure initial guess
210 if (eta <= lo || eta >= hi) eta = 0.5 * (lo + hi);
211 for (int it = 0; it < 64; ++it) {
212 const double dz3 = z3 - eta;
213 const double f = (eta - zbar) + dz3 * dz3 * dz3 / denom - mean_depth;
214 if (f > 0.0) hi = eta; else lo = eta;
215 const double df = 1.0 - dz3 * dz3 / (relief * d32); // A_wet/A
216 double next = (df > 1.0e-12) ? eta - f / df : 0.5 * (lo + hi);
217 if (next <= lo || next >= hi) next = 0.5 * (lo + hi); // safeguard
218 if (std::abs(next - eta) < 1.0e-12 * (1.0 + relief)) return next;
219 eta = next;
220 }
221 return eta;
222}
223
228OPENSWMM_KERNEL_FN double vfrDryEta(double z1, double z2, double z3, double eps) noexcept {
229 return vfrEtaFromMeanDepth(z1, z2, z3, 0.0, eps);
230}
231
236OPENSWMM_KERNEL_FN double vfrDEtaDMeanDepth(double z1, double z2, double z3,
237 double eta, double eps) noexcept {
238 double w = vfrWetFraction(z1, z2, z3, eta);
239 if (w < eps) w = eps;
240 if (w < 1.0e-12) w = 1.0e-12; // eps == 0 safety (exact relation, dry cell)
241 return 1.0 / w;
242}
243
244} // namespace openswmm::twoD
245
246#endif // OPENSWMM_ENGINE_2D_VFR_CLOSURE_HPP
#define OPENSWMM_KERNEL_FN
Definition ExplicitKokkosSurfaceSolver.cpp:18
Definition NodeCoupling.cpp:16
OPENSWMM_KERNEL_FN double vfrMeanDepthFromEta(double z1, double z2, double z3, double eta, double eps) noexcept
Definition VfrClosure.hpp:144
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:78
OPENSWMM_KERNEL_FN double vfrDryEta(double z1, double z2, double z3, double eps) noexcept
Definition VfrClosure.hpp:228
constexpr double kVfrFlatRelief
Definition VfrClosure.hpp:75
OPENSWMM_KERNEL_FN double vfrWetFraction(double z1, double z2, double z3, double eta) noexcept
Definition VfrClosure.hpp:87
OPENSWMM_KERNEL_FN double vfrStageAtWetFraction(double z1, double z2, double z3, double eps) noexcept
Definition VfrClosure.hpp:128
OPENSWMM_KERNEL_FN double vfrDEtaDMeanDepth(double z1, double z2, double z3, double eta, double eps) noexcept
Definition VfrClosure.hpp:236
OPENSWMM_KERNEL_FN double vfrEtaFromMeanDepth(double z1, double z2, double z3, double mean_depth, double eps) noexcept
Definition VfrClosure.hpp:165
OPENSWMM_KERNEL_FN double vfrMeanDepthFromEtaExact(double z1, double z2, double z3, double eta) noexcept
Definition VfrClosure.hpp:107