OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
ExplicitInertialSolver.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
43
44#ifndef OPENSWMM_ENGINE_2D_EXPLICIT_INERTIAL_SOLVER_HPP
45#define OPENSWMM_ENGINE_2D_EXPLICIT_INERTIAL_SOLVER_HPP
46
47#include <array>
48#include <cstdint>
49#include <string>
50#include <vector>
51
52#include "ISurfaceSolver.hpp"
53#include "InertialEdges.hpp"
54#include "../data/SolverOptions2D.hpp" // Momentum2D
55
56namespace openswmm::twoD {
57
58class SubsurfaceSolver; // G1: the two-zone groundwater kernel, if authored
59
61public:
62 void initialize(MeshData& mesh, SurfaceStateData& state,
63 SolverOptions2D& opts) override;
64 double advance(double t_current, double t_target) override;
65 void reinitialize(double t0) override;
66 void resyncFromVolumes(double t0) override;
67 void finalize() override;
68
69 long last_num_steps() const noexcept override { return last_steps_; }
70 double last_step_size() const noexcept override { return last_dt_; }
71 RunStats run_stats() const noexcept override;
72 bool is_initialized() const noexcept override { return initialized_; }
73 const std::vector<double>& last_coupling_exchange()
74 const noexcept override {
75 return exch_;
76 }
77
82 double spillDeficit() const noexcept { return spill_deficit_; }
83
84private:
85 // Recompute η/depth from state volumes for the whole mesh.
86 void reconstructAll();
87 // Flush every pending face accumulator into its cell (CSR gather over ALL
88 // cells) and refresh the closure of touched cells. MUST run before any
89 // tier/active-set reassignment: a cell deactivated or re-tiered with a
90 // pending side-accumulator strands flux whose counterpart the other side
91 // already applied — the backstop then realizes it as created volume.
92 void settleAccumulators();
93 // Apply lazily-accumulated sources (rain/held coupling) to INACTIVE cells
94 // over [t_last_sync_, t], rebuild the active cell/edge lists, and assign
95 // the LTS tiers (dt0_ = finest active CFL requirement).
96 void syncAndRebuild(double t);
97 // Tighten-only dt0_ refresh from CURRENT depths/speeds between rebuilds
98 // (dt0_ may only grow at syncAndRebuild, which reassigns the tiers).
99 void refreshDt0();
100 // Fire one tier's faces over their Δt: inertial update + Froude cap +
101 // face-cadence positivity share, booking ±ΔM into both side accumulators.
102 // global_step = every active cell fires at this same Δt (the window tail),
103 // so a face's exporter republishes its volume every time this face does
104 // and the positivity share is NOT divided down by a refire ratio.
105 void fireFaces(const std::vector<int>& faces, double dt_f,
106 bool global_step = false);
107 // Fire one tier's cells over their Δt: gather + clear own-side face
108 // accumulators, apply sources, refresh closure + Perot vector; tier-0
109 // firings also evaluate the availability-clamped boundary edges.
110 // tier0 = this firing carries the tier-0 cadence work (boundary edges and
111 // the live junction exchange), which fires once per finest substep.
112 void fireCells(const std::vector<int>& cells, double dt_c, bool tier0);
114 template <bool kSwe>
115 void fireCellsImpl(const std::vector<int>& cells, double dt_c, bool tier0);
116 // One halving-order macro cycle of nsub base substeps: tier k fires every
117 // 2^k substeps.
118 void runMacroCycle(double dt0, int nsub);
119
120 MeshData* mesh_ = nullptr;
121 SurfaceStateData* state_ = nullptr;
122 SolverOptions2D* opts_ = nullptr;
123
124 InertialEdges edges_;
128 std::vector<double> q_;
131 std::vector<double> qcx_, qcy_;
132
133 // -----------------------------------------------------------------------
134 // Momentum closure (MOMENTUM_EQUATION, 2D_FULL_SWE plan §2.1). One
135 // marcher, three face laws; everything below the face/cell kernels
136 // (tiers, active sets, positivity, coupling, transport) is shared.
137 // -----------------------------------------------------------------------
142 bool has_quads_ = false;
146 std::vector<int> wall_ptr_, wall_slot_;
150 std::vector<double> macc_x_L_, macc_x_R_, macc_y_L_, macc_y_R_;
154 double swe_bc_dqx_ = 0.0, swe_bc_dqy_ = 0.0;
158 bool second_order_ = false;
159 std::vector<double> gex_, gey_, gux_, guy_, gvx_, gvy_;
160 std::vector<double> rk_v0_, rk_qx0_, rk_qy0_;
163 std::vector<double> rk_inf0_, rk_bc0_, rk_ex0_, rk_drawn0_, rk_cpl0_;
164 void computeLimitedGradientsSwe();
168 void runRk2Step(double dt);
174 bool front_rebuild_ = false;
175 std::vector<uint8_t> frontier_;
176 bool front_breach_ = false;
177 double h_on_front_ = 0.0;
180 std::vector<double> dw_slope_;
182 void fireFacesInertial(const std::vector<int>& faces, double dt_f, bool global_step);
183 void fireFacesSwe(const std::vector<int>& faces, double dt_f, bool global_step);
184 void fireFacesDiffusive(const std::vector<int>& faces, double dt_f, bool global_step);
187 void bookFaceSpecies(int e, int a, int b, double dM, double hf, double dt_f,
188 bool global_step) noexcept;
192 double boundaryFluxSwe(std::size_t k, int i, double dt_c);
194 void refreshDiffusiveSlopes();
195 std::vector<uint8_t> cell_active_;
196 std::vector<int> active_cells_;
201 std::vector<int> active_faces_;
202 std::vector<uint8_t> pin_t0_;
204
205 // Tiered LTS. tier_[i] = k means cell i updates every 2^k base substeps
206 // with Δt = 2^k·dt0; face tier = min of its incident cells so a face
207 // always integrates at the finer cadence. Every face firing books the
208 // identical ±ΔM into facc_L_/facc_R_ (single writer per face); each cell
209 // applies + clears its own side at its own firing — conservation across
210 // tier interfaces is exact by construction.
213 std::vector<uint8_t> rebuild_seed_;
214 std::vector<double> rebuild_dt_cell_;
217 std::vector<double> rebuild_dt_partial_;
218
219 std::vector<uint8_t> tier_;
220 std::vector<uint8_t> face_tier_;
221 std::vector<double> facc_L_;
222 std::vector<double> facc_R_;
223
224 // S1 — species mass rides the SAME face accumulators, one pair per
225 // species, [s * ne + e]. Booked in fireFaces immediately after the volume
226 // ΔM, from the FINAL qn1 (after the Froude cap and the positivity share),
227 // at the exporting cell's concentration read at that same substep;
228 // gathered and cleared in fireCells alongside the volume side. That is
229 // D-2DT2: the species flux inherits the volume flux's tier cadence rather
230 // than reproducing it, so conservation across tier interfaces is the
231 // marcher's own property and not a second one to prove. Empty unless
232 // `state_->transport.active()`.
233 std::vector<double> sacc_L_;
234 std::vector<double> sacc_R_;
239 double donorConc(int s, int cell) const noexcept;
244 void sinkMassAtCellConc(int i, double dv_m3, std::vector<double>& ledger,
245 double* per_point_ledger = nullptr) noexcept;
248 void addRainMass(int i, double rain_m3) noexcept;
251 void addCouplingSourceMass(int i, double area_dt) noexcept;
254 void sinkTemperatureWithEvap(int i, double evap_m3) noexcept;
261 bool accumulators_pending_ = false;
262 std::vector<std::vector<int>> cells_by_tier_;
263 std::vector<std::vector<int>> edges_by_tier_;
264 double dt0_ = 0.0;
265 std::vector<int> bc_cell_;
266 std::vector<int> bc_slot_;
267 std::vector<double> bc_accum_;
269 std::vector<double> bc_q_;
278
279 // Live junction exchange (windowless coupling): state_->node_coupling
280 // points, evaluated at tier-0 cadence against live 2D heads and the
281 // routing-step 1D heads. exch_[k] = ∫Q_k dt (m³, + = 2D→1D), reset per
282 // advance; node_drawn_ caps a step's total spill at the node's stored
283 // volume so fill-and-spill thrash is structurally impossible.
284 std::vector<double> exch_;
285 std::vector<double> node_drawn_;
292 double spill_deficit_ = 0.0;
293
294public:
299 void setExchangeHeadSlopes(std::vector<double> slopes) {
300 exch_head_slope_ = std::move(slopes);
301 }
302private:
303 std::vector<double> exch_head_slope_;
304 double exch_tau_ = 0.0;
305
306 double t_last_sync_ = 0.0;
307 int cycles_since_rebuild_ = 1000;
310 void lazySourcesOnly(double t);
311 long substeps_run_ = 0;
312 long face_passes_ = 0;
313 long last_steps_ = 0;
314 double last_dt_ = 0.0;
315 bool initialized_ = false;
316
317 // Active-fraction telemetry: (sim time, active cells) sampled at every
318 // rebuild; dumped as CSV at finalize() when OPENSWMM_2D_MARCHER_TELEMETRY
319 // names a file. The Phase-1 gate reads this to verify the thin-film budget
320 // assumption on the Bellinge storm slice.
321 std::vector<std::pair<double, int>> telemetry_;
322 std::string telemetry_path_;
324 std::array<long, 8> tier_occupancy_{};
325
336 SubsurfaceSolver* gw_ = nullptr;
337
338public:
341 void setSubsurface(SubsurfaceSolver* gw) noexcept { gw_ = gw; }
342
347 const InertialEdges& inertialEdges() const noexcept { return edges_; }
348};
349
350} // namespace openswmm::twoD
351
352#endif // OPENSWMM_ENGINE_2D_EXPLICIT_INERTIAL_SOLVER_HPP
Backend-neutral interface for the 2D surface-routing time integrator.
Unique interior-edge structure for the local-inertial momentum DOFs.
Configuration options for the 2D surface routing solver.
Definition ExplicitInertialSolver.hpp:60
void finalize() override
Release all backend resources.
Definition ExplicitInertialSolver.cpp:2252
void reinitialize(double t0) override
Reinitialize the integrator at t0 after external state edits.
Definition ExplicitInertialSolver.cpp:2224
bool is_initialized() const noexcept override
True once initialize() has completed and the solver is ready.
Definition ExplicitInertialSolver.hpp:72
const std::vector< double > & last_coupling_exchange() const noexcept override
Definition ExplicitInertialSolver.hpp:73
void setSubsurface(SubsurfaceSolver *gw) noexcept
Definition ExplicitInertialSolver.hpp:341
double advance(double t_current, double t_target) override
Definition ExplicitInertialSolver.cpp:2070
long last_num_steps() const noexcept override
Number of internal integrator steps in the last advance() call.
Definition ExplicitInertialSolver.hpp:69
RunStats run_stats() const noexcept override
Read cumulative statistics. Default: zeros (backend has no counters).
Definition ExplicitInertialSolver.cpp:2274
double spillDeficit() const noexcept
Definition ExplicitInertialSolver.hpp:82
void resyncFromVolumes(double t0) override
Definition ExplicitInertialSolver.cpp:2244
const InertialEdges & inertialEdges() const noexcept
Definition ExplicitInertialSolver.hpp:347
void initialize(MeshData &mesh, SurfaceStateData &state, SolverOptions2D &opts) override
One-time setup. mesh and state must outlive the solver.
Definition ExplicitInertialSolver.cpp:65
double last_step_size() const noexcept override
Last internal step size used by the integrator.
Definition ExplicitInertialSolver.hpp:70
void setExchangeHeadSlopes(std::vector< double > slopes)
Definition ExplicitInertialSolver.hpp:299
Abstract time integrator for the 2D surface-routing ODE system.
Definition ISurfaceSolver.hpp:63
The two-zone kernel. Owned by SurfaceRouter2D; driven by the marcher's tier hooks.
Definition SubsurfaceSolver.hpp:97
Definition NodeCoupling.cpp:16
Momentum2D
Momentum closure of the explicit 2D marcher (2D_FULL_SWE_SHOCK_CAPTURING_PLAN_2026-09-05 §2).
Definition SolverOptions2D.hpp:157
@ LOCAL_INERTIAL
Definition SolverOptions2D.hpp:158
Canonical unique interior-edge layout + per-cell incidence for the local-inertial scheme.
Definition InertialEdges.hpp:61
SoA storage for 2D mixed triangle/quad mesh geometry and topology.
Definition MeshData.hpp:67
Configuration for the 2D surface routing solver.
Definition SolverOptions2D.hpp:208
Definition SurfaceStateData.hpp:59