OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
PressurizedHeadSolver.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
94
95#ifndef OPENSWMM_ENGINE_FV_PRESSURIZED_HEAD_SOLVER_HPP
96#define OPENSWMM_ENGINE_FV_PRESSURIZED_HEAD_SOLVER_HPP
97
98#include <cstdint>
99#include <vector>
100
101#include "FvKernels.hpp"
102#include "INetworkSolver.hpp"
103#include "NetworkMeshData.hpp"
105
106namespace openswmm::fv {
107
112 const NetworkMeshData* mesh = nullptr;
114 const FvOptions* opts = nullptr;
115 const FvStepForcing* forcing = nullptr;
116
117 // Face scratch (owned by ExplicitFvSolver).
118 double* f_mass = nullptr;
119 double* f_sstar = nullptr;
123
124 // Cell scratch.
125 const double* cell_eta = nullptr;
126 double* cell_u = nullptr;
127
132 const std::vector<double>* uf_grad = nullptr;
133
134 const std::vector<int>* active_faces = nullptr;
135 const std::vector<char>* cell_active = nullptr;
136
137 // Node machinery (owned by ExplicitFvSolver).
138 const std::vector<double>* node_qstruct = nullptr;
139 std::vector<double>* node_carry = nullptr;
140 const std::vector<std::uint8_t>* node_alg = nullptr;
141 const std::vector<double>* node_vfull = nullptr;
142 const std::vector<std::uint8_t>* node_lat_div = nullptr;
143 const std::vector<double>* cell_qlat = nullptr;
144};
145
147public:
170 enum : std::uint8_t { kNone = 0, kFull = 1, kDelta = 2 };
171
189 bool classify(const PressurizedView& v);
190
208 void solve(const PressurizedView& v, double dt);
209
229 solveCandidate(const PressurizedView& v, double dt,
248 bool verifyCandidateEndpoint(const PressurizedView& v, double dt,
250
264 void finalizeCells(const PressurizedView& v);
265
273 const std::vector<std::uint8_t>& foldedNodes() const noexcept { return node_fold_; }
274
280 bool anyImplicit() const noexcept { return !imp_faces_.empty(); }
281
282 // -- static predicates shared with the census edit ----------------------
283
296 static bool cellPressurized(const NetworkMeshData& mesh, const NetworkStateData& state,
297 int cell) {
298 const auto uc = static_cast<std::size_t>(cell);
299 if (!state.cell_tpa.empty())
300 return state.cell_tpa[uc] != 0;
301 const FvGeometry& g = mesh.geom[static_cast<std::size_t>(mesh.cell_geom[uc])];
302 return !g.is_open && state.cell_h[uc] >= g.y_crown;
303 }
304
319 static std::uint8_t faceModeOf(const NetworkMeshData& mesh, const NetworkStateData& state,
320 int f) {
321 const auto uf = static_cast<std::size_t>(f);
322 if (mesh.face_gate[uf] != 0)
323 return kNone;
324 if (mesh.face_culvert[uf] >= 0)
325 return kNone;
326 const int cl = mesh.face_cl[uf];
327 const int cr = mesh.face_cr[uf];
328 const int nd = mesh.face_node[uf];
329 if (nd < 0 && (cl < 0 || cr < 0))
330 return kNone;
331
332 bool pl = false, pr = false, cell_side_press = false;
333 if (cl >= 0) {
334 pl = cellPressurized(mesh, state, cl);
335 cell_side_press = cell_side_press || pl;
336 }
337 if (cr >= 0) {
338 pr = cellPressurized(mesh, state, cr);
339 cell_side_press = cell_side_press || pr;
340 }
341 if (nd >= 0) {
342 const bool gp = ghostPressurized(mesh, state, f);
343 if (cl < 0)
344 pl = gp;
345 if (cr < 0)
346 pr = gp;
347 }
348 if (!cell_side_press)
349 return kNone;
350 return (pl && pr) ? kFull : kDelta;
351 }
352
365 static bool ghostPressurized(const NetworkMeshData& mesh, const NetworkStateData& state,
366 int f) {
367 const auto uf = static_cast<std::size_t>(f);
368 const int nd = mesh.face_node[uf];
369 if (nd < 0)
370 return false;
371 const int other = (mesh.face_cl[uf] >= 0) ? mesh.face_cl[uf] : mesh.face_cr[uf];
372 if (other < 0)
373 return false;
374 const FvGeometry& g =
375 mesh.geom[static_cast<std::size_t>(mesh.cell_geom[static_cast<std::size_t>(other)])];
376 if (g.is_open)
377 return false;
378 // TPA (issue #156 Phase 5): a SEALED node against a flagged interior
379 // cell carries the pressurized column at sub-atmospheric head — the
380 // ghost is pressurized even though head < crown (the same rule
381 // faceSide uses for its ghost regime). Without this the sealed
382 // sub-crown junction's faces degrade to kDelta, the fold pass skips
383 // the node, and the implicit path breaks the column (the P4 pinned
384 // known-issue).
385 if (!state.cell_tpa.empty() && state.cell_tpa[static_cast<std::size_t>(other)] != 0 &&
386 mesh.node_sur_depth[static_cast<std::size_t>(nd)] > 0.0)
387 return true;
388 const double hg = state.node_head[static_cast<std::size_t>(nd)] - mesh.face_zb[uf];
389 return hg >= g.y_crown;
390 }
391
392private:
393 struct ImpFace {
394 int face = -1;
395 std::uint8_t mode = kNone;
396 // Unknown index of each side (-1 = known/Dirichlet). Left/right in
397 // the FACE frame, so the assembly's sign convention is the flux's.
398 int unk_l = -1;
399 int unk_r = -1;
400 // Dirichlet head of a known side (valid where unk < 0).
401 double dir_l = 0.0;
402 double dir_r = 0.0;
403 // Time-n heads of both sides (the DELTA form needs them).
404 double eta_l = 0.0;
405 double eta_r = 0.0;
406 double alpha = 1.0;
407 double cond = 0.0;
408 double qstar = 0.0;
409 };
410
411 // classify() products, persistent to avoid per-substep allocation.
412 std::vector<std::uint8_t> cell_press_;
413 std::vector<std::uint8_t> node_fold_;
414 std::vector<int> cell_unk_;
415 std::vector<int> node_unk_;
416 std::vector<ImpFace> imp_faces_;
417
418 // Unknown table: entity id (cell c → c; node n → n_cells + n), time-n
419 // head, storage coefficient (T·Δx/Δt for cells, 0 for folded junctions —
420 // filled with Δt at solve time), and component id.
421 std::vector<int> unk_entity_;
422 std::vector<double> unk_eta0_;
423 std::vector<double> unk_store_;
424 std::vector<double> unk_rhs_ext_;
425 std::vector<int> unk_comp_;
426 std::vector<double> unk_head_;
427
428 // Union-find over unknowns.
429 std::vector<int> uf_parent_;
436 int ufFind(int i);
445 void ufUnion(int a, int b);
446
447 // Per-component scratch.
448 std::vector<int> comp_ptr_, comp_unks_;
449 std::vector<double> thom_a_, thom_b_, thom_c_, thom_d_;
450 std::vector<int> path_order_, degree_, adj_ptr_, adj_idx_;
451 std::vector<double> adj_c_;
452 std::vector<double> cg_r_, cg_p_, cg_ap_, cg_z_;
453
462 void buildComponents();
477 void prepare(const PressurizedView& v, double dt);
489 void assembleRhs(const PressurizedView& v, double dt);
504 void solveComponent(int comp, const PressurizedView& v, double dt);
517 void backSubstitute(const PressurizedView& v);
518
530 void linearSolve(const std::vector<int>& unks, const std::vector<double>& diag,
531 const std::vector<double>& rhs);
532
533 // diag/rhs assembled per solve (indexed by unknown).
534 std::vector<double> diag_, rhs_;
535 std::vector<std::uint8_t> unk_dirichlet_;
536 std::vector<double> unk_dirvalue_;
537 NonlinearPressureStorage candidate_;
538 std::vector<NonlinearPressureStorage::Row> candidate_rows_;
539 std::vector<NonlinearPressureStorage::Edge> candidate_edges_;
540 std::vector<double> candidate_head_, candidate_residual_, candidate_budget_;
541};
542
543} // namespace openswmm::fv
544
545#endif // OPENSWMM_ENGINE_FV_PRESSURIZED_HEAD_SOLVER_HPP
Single-source scalar kernels for the explicit FV 1D network solver.
Backend-neutral interface for the explicit FV 1D network integrator.
SoA storage for the 1D finite-volume network mesh and its state.
Nonlinear pressure and physical storage equations for finite-volume trials.
Definition PressurizedHeadSolver.hpp:146
void solve(const PressurizedView &v, double dt)
Predict fully pressurized face discharges with an implicit head system.
Definition PressurizedHeadSolver.cpp:447
bool verifyCandidateEndpoint(const PressurizedView &v, double dt, const NonlinearPressureStorage::Controls &controls)
Check the actual post-update endpoint against the last nonlinear candidate equations.
Definition PressurizedHeadSolver.cpp:656
NonlinearPressureStorage::Result solveCandidate(const PressurizedView &v, double dt, const NonlinearPressureStorage::Controls &controls)
Try a guarded nonlinear SLOT-storage pressure predictor.
Definition PressurizedHeadSolver.cpp:595
static bool cellPressurized(const NetworkMeshData &mesh, const NetworkStateData &state, int cell)
Test pressure membership shared by classification and timestep census.
Definition PressurizedHeadSolver.hpp:296
@ kDelta
Definition PressurizedHeadSolver.hpp:170
@ kNone
Definition PressurizedHeadSolver.hpp:170
@ kFull
Definition PressurizedHeadSolver.hpp:170
bool classify(const PressurizedView &v)
Rebuild pressure membership, implicit face ownership and union-find connectivity.
Definition PressurizedHeadSolver.cpp:166
void finalizeCells(const PressurizedView &v)
Replace pressure-cell discharge with its solved face through-flow after conservative update.
Definition PressurizedHeadSolver.cpp:1060
const std::vector< std::uint8_t > & foldedNodes() const noexcept
Borrow the last classification folded-node mask.
Definition PressurizedHeadSolver.hpp:273
bool anyImplicit() const noexcept
Report whether the last classification contains any fully implicit face.
Definition PressurizedHeadSolver.hpp:280
static std::uint8_t faceModeOf(const NetworkMeshData &mesh, const NetworkStateData &state, int f)
Classify one face as explicit, full pressure or a pressure/free transition.
Definition PressurizedHeadSolver.hpp:319
static bool ghostPressurized(const NetworkMeshData &mesh, const NetworkStateData &state, int f)
Test the node-side ghost pressure state in the adjacent cell geometry.
Definition PressurizedHeadSolver.hpp:365
Definition Controls.cpp:47
Definition AcceptedWaterVolumes.hpp:8
Cross-section closure for one conduit's cell chain.
Definition NetworkMeshData.hpp:71
uint8_t is_open
open section: no crown, no slot taper
Definition NetworkMeshData.hpp:100
double y_crown
Definition NetworkMeshData.hpp:86
Knobs for FLOW_ROUTING FV.
Definition FvOptions.hpp:123
Definition INetworkSolver.hpp:58
SoA mesh geometry and topology for the FV network solver.
Definition NetworkMeshData.hpp:123
std::vector< int > cell_geom
index into geom
Definition NetworkMeshData.hpp:129
std::vector< double > node_sur_depth
SURCHARGE_DEPTH above the rim (ft)
Definition NetworkMeshData.hpp:361
std::vector< double > face_zb
bed elevation at the interface (ft)
Definition NetworkMeshData.hpp:219
std::vector< int > face_cr
right cell (-1 ⇒ node on the right)
Definition NetworkMeshData.hpp:162
std::vector< int > face_cl
left cell (-1 ⇒ node on the left)
Definition NetworkMeshData.hpp:161
std::vector< int > face_node
coupled node (-1 for interior faces)
Definition NetworkMeshData.hpp:163
std::vector< int > face_culvert
Definition NetworkMeshData.hpp:218
std::vector< uint8_t > face_gate
Definition NetworkMeshData.hpp:213
std::vector< FvGeometry > geom
Definition NetworkMeshData.hpp:401
Mutable solver state — the conserved variables and the node volumes.
Definition NetworkMeshData.hpp:519
Definition NonlinearPressureStorage.hpp:44
Definition NonlinearPressureStorage.hpp:65
Definition PressurizedHeadSolver.hpp:111
const std::vector< int > * active_faces
Definition PressurizedHeadSolver.hpp:134
kernels::FaceState * f_state_r
Definition PressurizedHeadSolver.hpp:121
NetworkStateData * state
Definition PressurizedHeadSolver.hpp:113
const std::vector< std::uint8_t > * node_lat_div
Definition PressurizedHeadSolver.hpp:142
const std::vector< double > * uf_grad
Definition PressurizedHeadSolver.hpp:132
const double * cell_eta
Definition PressurizedHeadSolver.hpp:125
const std::vector< double > * cell_qlat
Definition PressurizedHeadSolver.hpp:143
const FvStepForcing * forcing
Definition PressurizedHeadSolver.hpp:115
const FvOptions * opts
Definition PressurizedHeadSolver.hpp:114
const NetworkMeshData * mesh
Definition PressurizedHeadSolver.hpp:112
const std::vector< double > * node_qstruct
Definition PressurizedHeadSolver.hpp:138
kernels::FaceFlux * f_flux
Definition PressurizedHeadSolver.hpp:122
std::vector< double > * node_carry
Definition PressurizedHeadSolver.hpp:139
double * cell_u
Definition PressurizedHeadSolver.hpp:126
const std::vector< std::uint8_t > * node_alg
Definition PressurizedHeadSolver.hpp:140
const std::vector< double > * node_vfull
Definition PressurizedHeadSolver.hpp:141
double * f_sstar
Definition PressurizedHeadSolver.hpp:119
kernels::FaceState * f_state_l
Definition PressurizedHeadSolver.hpp:120
double * f_mass
Definition PressurizedHeadSolver.hpp:118
const std::vector< char > * cell_active
Definition PressurizedHeadSolver.hpp:135
Result of a face flux evaluation.
Definition FvKernels.hpp:259
One side of a face after hydrostatic reconstruction.
Definition FvKernels.hpp:245