OpenSWMM Engine  6.0.0-alpha.1
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.1)
Loading...
Searching...
No Matches
KokkosSurfaceKernels.hpp
Go to the documentation of this file.
1
36
37#ifndef OPENSWMM_ENGINE_2D_GPU_KOKKOS_SURFACE_KERNELS_HPP
38#define OPENSWMM_ENGINE_2D_GPU_KOKKOS_SURFACE_KERNELS_HPP
39
40#include <Kokkos_Core.hpp>
41
42namespace openswmm::twoD::gpu {
43
44// ---------------------------------------------------------------------------
45// Execution space, selected at build time by the plugin backend:
46// OPENSWMM_GPU_EXECSPACE_CUDA -> Kokkos::Cuda (Phase 2 device backend, NVIDIA)
47// OPENSWMM_GPU_EXECSPACE_HIP -> Kokkos::HIP (Phase 5 device backend, AMD)
48// OPENSWMM_GPU_EXECSPACE_SYCL -> Kokkos::SYCL (Phase 5 device backend, Intel)
49// OPENSWMM_GPU_EXECSPACE_OPENMP -> Kokkos::OpenMP (Phase 1 host backend)
50// When none is defined (e.g. the standalone Kokkos parity test) we fall back
51// to Kokkos' default space. Selecting explicitly matters once Kokkos is built
52// with several spaces enabled: the *device* space wins DefaultExecutionSpace, so
53// an OpenMP plugin linked against a CUDA-enabled Kokkos would silently run on
54// the GPU. The macro pins each plugin to the space its CMake target intends.
55//
56// HIP and SYCL are top-level Kokkos:: names as of Kokkos 4.x (the project pins
57// 4.7.04 via vcpkg-overlays/kokkos); on older Kokkos they live under
58// Kokkos::Experimental::{HIP,SYCL}. Per strategy §6.1 these two typedef lines
59// are the *entire* solver-side change needed to add the AMD and Intel backends;
60// the kernels below use only Kokkos:: math and KOKKOS_LAMBDA, so they compile
61// to every backend unchanged.
62//
63// All buffers live in ExecSpace's memory_space. Under OpenMP that is host
64// memory, so the host<->device deep_copies in the solver are no-ops; under CUDA,
65// HIP, or SYCL they become real device transfers, confined to advance()
66// boundaries — the RHS, preconditioner and vector ops all run device-resident.
67// ---------------------------------------------------------------------------
68#if defined(OPENSWMM_GPU_EXECSPACE_CUDA)
69using ExecSpace = Kokkos::Cuda;
70#elif defined(OPENSWMM_GPU_EXECSPACE_HIP)
71using ExecSpace = Kokkos::HIP;
72#elif defined(OPENSWMM_GPU_EXECSPACE_SYCL)
73using ExecSpace = Kokkos::SYCL;
74#elif defined(OPENSWMM_GPU_EXECSPACE_OPENMP)
75using ExecSpace = Kokkos::OpenMP;
76#else
77using ExecSpace = Kokkos::DefaultExecutionSpace;
78#endif
79using MemSpace = ExecSpace::memory_space;
80
81using DView = Kokkos::View<double*, MemSpace>;
82using CDView = Kokkos::View<const double*, MemSpace>;
83using IView = Kokkos::View<int*, MemSpace>;
84using CIView = Kokkos::View<const int*, MemSpace>;
85
100
109
110 // Per-edge boundary conditions [n_tri*3]; extent 0 ⇒ all boundary edges are
111 // no-flux walls (the default, and what the standalone parity test gets).
112 // Mirror of SurfaceRouter2D's BoundaryData; edge_bc_head / edge_bc_flow are
113 // resolved on the host each step (timeseries / rating) and re-uploaded.
118};
119
120// ---------------------------------------------------------------------------
121// Device-side scalar helpers (mirror SurfaceFluxCalculator.{hpp,cpp}).
122// ---------------------------------------------------------------------------
123
125KOKKOS_INLINE_FUNCTION
126double evapSink(double rate, double depth, double dry_depth) {
127 if (rate <= 0.0 || depth <= 0.0) return 0.0;
128 if (depth >= dry_depth) return rate;
129 const double t = depth / dry_depth;
130 return rate * t * t * (3.0 - 2.0 * t);
131}
132
133KOKKOS_INLINE_FUNCTION double sq(double x) { return x * x; }
134
135KOKKOS_INLINE_FUNCTION int nbr_of(int e, int n0, int n1, int n2) {
136 return (e == 0) ? n0 : (e == 1) ? n1 : n2;
137}
138
145KOKKOS_INLINE_FUNCTION double regSqrt(double x, double eps) {
146 if (eps <= 0.0 || x >= eps) return Kokkos::sqrt(x);
147 const double inv = 1.0 / Kokkos::sqrt(eps);
148 return (1.5 * inv) * x - (0.5 * inv / eps) * x * x;
149}
150
151// Boundary types (mirror BoundaryData.hpp BoundaryType); plain ints so the
152// device kernel needs no host enum header.
153enum : int { BC_WALL = 0, BC_NORMAL_FLOW = 1, BC_SPECIFIED_STAGE = 2,
155
156// Boundary-edge flux (inflow-positive contribution to the owning cell; outward
157// discharge is negative). Byte-for-byte mirror of SurfaceFluxCalculator's
158// boundaryEdgeFlux so every backend agrees. h_bc / q_flow are resolved on the
159// host (timeseries / rating) and uploaded; a RATING_CURVE arrives as a per-metre
160// flow in q_flow, handled identically to SPECIFIED_FLOW.
161KOKKOS_INLINE_FUNCTION
162double boundaryEdgeFlux(int bc, double slope, double h_bc, double q_flow,
163 double depth, double head, double tri_cz, double area,
164 double L, double n, double dh_eps) {
165 switch (bc) {
166 case BC_NORMAL_FLOW: {
167 if (slope <= 0.0 || depth <= 0.0 || n <= 0.0) return 0.0;
168 const double h53 = depth * Kokkos::cbrt(depth * depth);
169 return -(h53 * Kokkos::sqrt(slope) / n) * L;
170 }
172 case BC_RATING_CURVE:
173 return -q_flow * L;
174 case BC_SPECIFIED_STAGE: {
175 if (n <= 0.0) return 0.0;
176 const double dh = head - h_bc;
177 const double dx_b = (L > 1.0e-12) ? (2.0 * area) / (3.0 * L) : 0.0;
178 if (dx_b <= 1.0e-12) return 0.0;
179 const double h_up = (dh > 0.0) ? depth
180 : Kokkos::fmax(h_bc - tri_cz, 0.0);
181 if (h_up <= 0.0) return 0.0;
182 const double h53 = h_up * Kokkos::cbrt(h_up * h_up);
183 const double sgn = (dh > 0.0) ? 1.0 : (dh < 0.0 ? -1.0 : 0.0);
184 return -h53 * sgn * regSqrt(Kokkos::fabs(dh), dh_eps) * L
185 / (n * Kokkos::sqrt(dx_b));
186 }
187 default: return 0.0; // WALL
188 }
189}
190
191// ---------------------------------------------------------------------------
192// RHS pipeline. Evaluates ydot = f(y) for the VOLUME formulation: y is the cell
193// water volume V; the free surface η = tri_cz + V/A and mean depth h̄ = V/A are
194// reconstructed per cell and drive the flux pipeline. Mirrors
195// CvodeSurfaceSolver::rhs_fn step-for-step.
196// ---------------------------------------------------------------------------
197inline void evaluateRhs(const MeshViews& m, const StateViews& s,
198 DView y, DView ydot,
199 double dry_depth, double limiter_eps, double dh_eps) {
200 const int nt = m.n_tri;
201 const int nv = m.n_vert;
202
203 // Local copies of the View handles so the device lambdas capture only
204 // Views (never the host-side structs).
205 auto tri_cz = m.tri_cz; auto tri_area = m.tri_area;
206 auto tri_cx = m.tri_cx; auto tri_cy = m.tri_cy; auto mn = m.mannings_n;
207 auto nb0 = m.tri_nbr0; auto nb1 = m.tri_nbr1; auto nb2 = m.tri_nbr2;
208 auto e_len = m.edge_length; auto e_nx = m.edge_nx; auto e_ny = m.edge_ny;
209 auto e_conv = m.edge_conveyance;
210 auto v_ptr = m.vert_ptr; auto v_idx = m.vert_idx; auto v_wt = m.vert_wt;
211
212 auto head = s.head; auto depth = s.depth;
213 auto gx = s.grad_hx; auto gy = s.grad_hy;
214 auto gxl = s.grad_hx_lim; auto gyl = s.grad_hy_lim;
215 auto vhead = s.vert_head; auto eflux = s.edge_flux;
216 auto rain = s.rainfall; auto coup = s.coupling_flux; auto evap = s.evap_rate;
217 auto bc_type = s.edge_bc_type; auto bc_slope = s.edge_bed_slope;
218 auto bc_head = s.edge_bc_head; auto bc_flow = s.edge_bc_flow;
219 const bool has_bc = (bc_type.extent(0) > 0);
220
221 // 1. Unpack y -> head, depth (VOLUME formulation): flat-cell closure
222 // h̄ = V/A, η = tri_cz + h̄ (mirrors reconstructFromVolume).
223 Kokkos::parallel_for("rhs_unpack", Kokkos::RangePolicy<ExecSpace>(0, nt),
224 KOKKOS_LAMBDA(int i) {
225 const double A = tri_area(i);
226 const double v = y(i) > 0.0 ? y(i) : 0.0;
227 const double d = (A > 1.0e-30) ? v / A : 0.0;
228 depth(i) = d;
229 head(i) = tri_cz(i) + d;
230 });
231
232 // 2. Reconstruct head at vertices (pseudo-Laplacian, CSR gather).
233 Kokkos::parallel_for("rhs_vertex_heads", Kokkos::RangePolicy<ExecSpace>(0, nv),
234 KOKKOS_LAMBDA(int b) {
235 const int start = v_ptr(b);
236 const int end = v_ptr(b + 1);
237 double h = 0.0;
238 for (int k = start; k < end; ++k) h += v_wt(k) * head(v_idx(k));
239 vhead(b) = h;
240 });
241
242 // 3. Unlimited gradients (Green-Gauss).
243 Kokkos::parallel_for("rhs_grad_unlimited", Kokkos::RangePolicy<ExecSpace>(0, nt),
244 KOKKOS_LAMBDA(int i) {
245 const double inv_area = (tri_area(i) > 1.0e-30) ? 1.0 / tri_area(i) : 0.0;
246 const int n0 = nb0(i), n1 = nb1(i), n2 = nb2(i);
247 double ax = 0.0, ay = 0.0;
248 for (int e = 0; e < 3; ++e) {
249 const int idx = i * 3 + e;
250 const int nbr = nbr_of(e, n0, n1, n2);
251 const double h_edge = (nbr >= 0) ? 0.5 * (head(i) + head(nbr))
252 : head(i);
253 ax += h_edge * e_nx(idx) * e_len(idx);
254 ay += h_edge * e_ny(idx) * e_len(idx);
255 }
256 gx(i) = ax * inv_area;
257 gy(i) = ay * inv_area;
258 });
259
260 // 4. Jawahar-Kamath limited gradients (reads stage-3 neighbour gradients).
261 Kokkos::parallel_for("rhs_grad_limited", Kokkos::RangePolicy<ExecSpace>(0, nt),
262 KOKKOS_LAMBDA(int i) {
263 const double eps2 = limiter_eps * limiter_eps;
264 const double q0 = sq(gx(i)) + sq(gy(i)) + eps2;
265 const int n0 = nb0(i), n1 = nb1(i), n2 = nb2(i);
266 double q[3], gxn[3], gyn[3];
267 for (int e = 0; e < 3; ++e) {
268 const int nbr = nbr_of(e, n0, n1, n2);
269 if (nbr >= 0) {
270 q[e] = sq(gx(nbr)) + sq(gy(nbr)) + eps2;
271 gxn[e] = gx(nbr);
272 gyn[e] = gy(nbr);
273 } else {
274 q[e] = q0;
275 gxn[e] = gx(i);
276 gyn[e] = gy(i);
277 }
278 }
279 const double m0 = q[0] * q[1] * q[2];
280 const double m1 = q0 * q[1] * q[2];
281 const double m2 = q0 * q[0] * q[2];
282 const double m3 = q0 * q[0] * q[1];
283 const double denom = m0 + m1 + m2 + m3;
284 const double w0 = m0 / denom, w1 = m1 / denom,
285 w2 = m2 / denom, w3 = m3 / denom;
286 gxl(i) = w0 * gx(i) + w1 * gxn[0] + w2 * gxn[1] + w3 * gxn[2];
287 gyl(i) = w0 * gy(i) + w1 * gyn[0] + w2 * gyn[1] + w3 * gyn[2];
288 });
289
290 // 5. Edge fluxes (collapsed-FD Manning diffusive wave, hydrostatic upwind).
291 Kokkos::parallel_for("rhs_edge_flux", Kokkos::RangePolicy<ExecSpace>(0, nt),
292 KOKKOS_LAMBDA(int i) {
293 const int n0 = nb0(i), n1 = nb1(i), n2 = nb2(i);
294 for (int e = 0; e < 3; ++e) {
295 const int idx = i * 3 + e;
296 const int nbr = nbr_of(e, n0, n1, n2);
297 if (nbr < 0) {
298 // Domain boundary: no-flux wall, or the configured BC.
299 eflux(idx) = has_bc
300 ? boundaryEdgeFlux(bc_type(idx), bc_slope(idx),
301 bc_head(idx), bc_flow(idx), depth(i), head(i),
302 tri_cz(i), tri_area(i), e_len(idx), mn(i), dh_eps)
303 : 0.0;
304 continue;
305 }
306
307 const double h_L = head(i), h_R = head(nbr);
308 const int up = (h_L >= h_R) ? i : nbr;
309 const double depth_up = depth(up);
310 if (depth_up <= 0.0) { eflux(idx) = 0.0; continue; }
311
312 const double dxx = tri_cx(i) - tri_cx(nbr);
313 const double dxy = tri_cy(i) - tri_cy(nbr);
314 const double dx = Kokkos::sqrt(dxx * dxx + dxy * dxy);
315 if (dx < 1.0e-12) { eflux(idx) = 0.0; continue; }
316
317 const double dh = h_L - h_R;
318 const double abs_dh = Kokkos::fabs(dh);
319 const double sign_dh = (dh > 0.0) ? 1.0 : (dh < 0.0 ? -1.0 : 0.0);
320 const double h53 = depth_up * Kokkos::cbrt(depth_up * depth_up);
321 const double n_up = mn(up);
322 const double xi = e_len(idx);
323
324 double F_e = -h53 * sign_dh * regSqrt(abs_dh, dh_eps) * xi
325 / (n_up * Kokkos::sqrt(dx));
326
327 // No explicit wet/dry shutoff: under the flat volume closure the
328 // source-side depth (= V/A) vanishes smoothly as the cell empties,
329 // so h^(5/3) → 0 and the flux shuts off C¹-smoothly with no 1 mm
330 // Hermite band (the depth_up ≤ 0 guard above zeroes a dry source).
331 // The √|Δη| is C¹-regularized (regSqrt) so the transmissivity stays
332 // bounded as the surface flattens (deep-water stiffness).
333 F_e *= e_conv(idx);
334 eflux(idx) = F_e;
335 }
336 });
337
338 // 6. Assemble RHS (VOLUME): dV/dt = ΣF + A·(rain + coupling − evapSink). No
339 // 1/A — V is the conserved state, so interior fluxes telescope exactly.
340 Kokkos::parallel_for("rhs_assemble", Kokkos::RangePolicy<ExecSpace>(0, nt),
341 KOKKOS_LAMBDA(int i) {
342 const double area = tri_area(i);
343 double fs = 0.0;
344 for (int e = 0; e < 3; ++e) fs += eflux(i * 3 + e);
345 ydot(i) = fs + area * (rain(i) + coup(i)
346 - evapSink(evap(i), depth(i), dry_depth));
347 });
348}
349
350// ---------------------------------------------------------------------------
351// Jacobi preconditioner (mirrors psetup_fn / psolve_fn).
352// ---------------------------------------------------------------------------
353
355inline void precondSetup(const MeshViews& m, const StateViews& s) {
356 const int nt = m.n_tri;
357 auto nb0 = m.tri_nbr0; auto nb1 = m.tri_nbr1; auto nb2 = m.tri_nbr2;
358 auto tri_area = m.tri_area;
359 auto head = s.head; auto eflux = s.edge_flux; auto D = s.precond_diag;
360 Kokkos::parallel_for("prec_setup", Kokkos::RangePolicy<ExecSpace>(0, nt),
361 KOKKOS_LAMBDA(int i) {
362 constexpr double dh_floor = 1.0e-9;
363 const int n0 = nb0(i), n1 = nb1(i), n2 = nb2(i);
364 double T_sum = 0.0;
365 for (int e = 0; e < 3; ++e) {
366 const int nbr = nbr_of(e, n0, n1, n2);
367 if (nbr < 0) continue;
368 const double dh = Kokkos::fabs(head(i) - head(nbr));
369 const double F = Kokkos::fabs(eflux(i * 3 + e));
370 T_sum += F / Kokkos::fmax(dh, dh_floor);
371 }
372 const double inv_area = (tri_area(i) > 1.0e-30) ? 1.0 / tri_area(i) : 0.0;
373 D(i) = -T_sum * inv_area;
374 });
375}
376
378inline void precondSolve(const StateViews& s, DView r, DView z, double gamma) {
379 const int nt = static_cast<int>(s.precond_diag.extent(0));
380 auto D = s.precond_diag;
381 Kokkos::parallel_for("prec_solve", Kokkos::RangePolicy<ExecSpace>(0, nt),
382 KOKKOS_LAMBDA(int i) {
383 constexpr double m_floor = 1.0e-12;
384 double mval = 1.0 - gamma * D(i);
385 if (Kokkos::fabs(mval) < m_floor) mval = Kokkos::copysign(m_floor, mval);
386 z(i) = r(i) / mval;
387 });
388}
389
390} // namespace openswmm::twoD::gpu
391
392#endif // OPENSWMM_ENGINE_2D_GPU_KOKKOS_SURFACE_KERNELS_HPP
Definition ArkodeKokkosSurfaceSolver.cpp:29
Kokkos::View< int *, MemSpace > IView
mutable int array
Definition KokkosSurfaceKernels.hpp:83
@ BC_NORMAL_FLOW
Definition KokkosSurfaceKernels.hpp:153
@ BC_WALL
Definition KokkosSurfaceKernels.hpp:153
@ BC_SPECIFIED_FLOW
Definition KokkosSurfaceKernels.hpp:154
@ BC_SPECIFIED_STAGE
Definition KokkosSurfaceKernels.hpp:153
@ BC_RATING_CURVE
Definition KokkosSurfaceKernels.hpp:154
void evaluateRhs(const MeshViews &m, const StateViews &s, DView y, DView ydot, double dry_depth, double limiter_eps, double dh_eps)
Definition KokkosSurfaceKernels.hpp:197
Kokkos::DefaultExecutionSpace ExecSpace
Definition KokkosSurfaceKernels.hpp:77
KOKKOS_INLINE_FUNCTION double sq(double x)
Definition KokkosSurfaceKernels.hpp:133
Kokkos::View< double *, MemSpace > DView
mutable double array
Definition KokkosSurfaceKernels.hpp:81
ExecSpace::memory_space MemSpace
Definition KokkosSurfaceKernels.hpp:79
Kokkos::View< const double *, MemSpace > CDView
const double array
Definition KokkosSurfaceKernels.hpp:82
Kokkos::View< const int *, MemSpace > CIView
const int array
Definition KokkosSurfaceKernels.hpp:84
void precondSetup(const MeshViews &m, const StateViews &s)
Build the per-cell diagonal D from the most recent edge fluxes/heads.
Definition KokkosSurfaceKernels.hpp:355
KOKKOS_INLINE_FUNCTION double evapSink(double rate, double depth, double dry_depth)
Depth-limited evaporation sink (mirrors evapSink()).
Definition KokkosSurfaceKernels.hpp:126
KOKKOS_INLINE_FUNCTION double boundaryEdgeFlux(int bc, double slope, double h_bc, double q_flow, double depth, double head, double tri_cz, double area, double L, double n, double dh_eps)
Definition KokkosSurfaceKernels.hpp:162
KOKKOS_INLINE_FUNCTION int nbr_of(int e, int n0, int n1, int n2)
Definition KokkosSurfaceKernels.hpp:135
KOKKOS_INLINE_FUNCTION double regSqrt(double x, double eps)
Definition KokkosSurfaceKernels.hpp:145
void precondSolve(const StateViews &s, DView r, DView z, double gamma)
Apply (I − γD)^{-1} element-wise: z = r / (1 − γ·D).
Definition KokkosSurfaceKernels.hpp:378
double * y
Definition odesolve.c:28
Immutable mesh geometry/topology mirrored to device once at initialize().
Definition KokkosSurfaceKernels.hpp:87
int n_vert
Definition KokkosSurfaceKernels.hpp:89
CIView vert_ptr
[n_vert+1]
Definition KokkosSurfaceKernels.hpp:96
CIView tri_nbr1
Definition KokkosSurfaceKernels.hpp:92
CDView tri_area
Definition KokkosSurfaceKernels.hpp:91
CIView tri_nbr2
[n_tri]
Definition KokkosSurfaceKernels.hpp:92
CDView tri_cy
Definition KokkosSurfaceKernels.hpp:91
CDView mannings_n
[n_tri]
Definition KokkosSurfaceKernels.hpp:91
CDView tri_cz
Definition KokkosSurfaceKernels.hpp:91
CDView edge_ny
Definition KokkosSurfaceKernels.hpp:93
int n_tri
Definition KokkosSurfaceKernels.hpp:88
CDView vert_wt
[nnz]
Definition KokkosSurfaceKernels.hpp:98
CIView vert_idx
[nnz]
Definition KokkosSurfaceKernels.hpp:97
CIView tri_nbr0
Definition KokkosSurfaceKernels.hpp:92
CDView edge_nx
Definition KokkosSurfaceKernels.hpp:93
CDView tri_cx
Definition KokkosSurfaceKernels.hpp:91
CDView edge_conveyance
[n_tri*3]
Definition KokkosSurfaceKernels.hpp:93
CDView edge_length
Definition KokkosSurfaceKernels.hpp:93
Mutable per-step state mirrored on device.
Definition KokkosSurfaceKernels.hpp:102
DView grad_hy_lim
[n_tri]
Definition KokkosSurfaceKernels.hpp:104
DView coupling_flux
Definition KokkosSurfaceKernels.hpp:107
DView vert_head
[n_vert]
Definition KokkosSurfaceKernels.hpp:105
DView grad_hy
Definition KokkosSurfaceKernels.hpp:104
DView head
Definition KokkosSurfaceKernels.hpp:103
DView edge_bc_flow
SPECIFIED_FLOW / RATING_CURVE per-metre flow (per step)
Definition KokkosSurfaceKernels.hpp:117
DView precond_diag
[n_tri] Jacobi diag
Definition KokkosSurfaceKernels.hpp:108
IView edge_bc_type
BoundaryType per edge (0=WALL..4=RATING_CURVE)
Definition KokkosSurfaceKernels.hpp:114
DView grad_hx
Definition KokkosSurfaceKernels.hpp:104
DView grad_hx_lim
Definition KokkosSurfaceKernels.hpp:104
DView edge_bed_slope
NORMAL_FLOW bed slope (static)
Definition KokkosSurfaceKernels.hpp:115
DView edge_bc_head
SPECIFIED_STAGE prescribed head (per step)
Definition KokkosSurfaceKernels.hpp:116
DView evap_rate
[n_tri] sources
Definition KokkosSurfaceKernels.hpp:107
DView depth
[n_tri]
Definition KokkosSurfaceKernels.hpp:103
DView edge_flux
[n_tri*3]
Definition KokkosSurfaceKernels.hpp:106
DView rainfall
Definition KokkosSurfaceKernels.hpp:107