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 <functional>
49#include <cstdint>
50#include <string>
51#include <vector>
52
53#include "ISurfaceSolver.hpp"
54#include "InertialEdges.hpp"
55#include "MomentumPhysics2D.hpp"
58#include "../data/SolverOptions2D.hpp" // Momentum2D
59
60namespace openswmm::detail {
62}
63namespace openswmm {
64class SWMMEngine;
66struct Table;
67}
70}
71namespace openswmm::twoD {
72struct Rk2CheckpointBinding;
73
74class SubsurfaceSolver; // G1: the two-zone groundwater kernel, if authored
75class CommonSourceStage;
76class ChannelGroundState;
77class SourceResultHistory;
78class EngineSourceResultHistory;
79
81public:
85
110 void initialize(MeshData& mesh, SurfaceStateData& state, SolverOptions2D& opts) override;
133 double advance(double t_current, double t_target) override;
139 double completedSourceTime() const noexcept override {
140 return initialized_ ? t_last_sync_ : -1.0;
141 }
142
154 void reinitialize(double t0) override;
166 void resyncFromVolumes(double t0) override;
177 void pinExternalSourceCells(const std::vector<int>& cells);
226 std::string saveCheckpoint(const std::string& path) const;
235 std::string loadCheckpoint(const std::string& path);
259 std::string configureRk2Reactions(const SimulationContext&);
270 std::string configureRk2BoundarySignals(SimulationContext&, double stage_scale,
271 double flow_scale);
285 void resolveRk2BoundarySignals(double time);
292 double surfaceReactionLoss(int row) const noexcept {
293 return row >= 0 && std::size_t(row) < rk_reacted_.size() ? rk_reacted_[row] : 0;
294 }
295
307 void finalize() override;
320 void refreshBoundaries();
321
328 long last_num_steps() const noexcept override { return last_steps_; }
334 double last_step_size() const noexcept override { return last_dt_; }
343 RunStats run_stats() const noexcept override;
350 bool is_initialized() const noexcept override { return initialized_; }
358 const std::vector<double>& last_coupling_exchange() const noexcept override { return exch_; }
359
369 double spillDeficit() const noexcept { return spill_deficit_; }
370
371private:
373 friend class SurfaceRouter2D;
382 std::string checkpointBytes(std::string& bytes) const;
391 std::string rk2CheckpointProfile() const;
398 std::string rk2CheckpointModel() const;
410 std::string rk2CheckpointModel(const Rk2CheckpointBinding*) const;
418 std::string rk2CheckpointBytes(std::string&) const;
427 std::string loadRk2CheckpointBytes(std::string);
443 std::string loadRk2CheckpointBytes(
444 std::string, const Rk2CheckpointBinding*,
445 const std::function<void(const ExplicitInertialSolver&, const SurfaceStateData&,
446 const SubsurfaceSolver&)>*,
447 const std::function<void()>*, bool inspect);
470 std::string loadCheckpointBytes(
471 std::string bytes, const std::array<double, 10>* router_boundary = nullptr,
472 const std::vector<double>* pending_outfalls = nullptr,
473 const std::function<void(const runoff::SourceWaterDriver&)>* validate_source = nullptr,
474 const std::function<void()>* before_install = nullptr,
475 const std::vector<int>* boundary_slots = nullptr, double* inspect_source_end = nullptr);
485 void refreshSourceResultBoundary();
486 double advanceImpl(double current, double target);
499 std::string rk2ThermalProfileError() const;
508 void advanceRk2Thermal(double seconds, double sample_time);
521 void advanceRk2BedContact(double seconds);
536 void advanceRk2Snow(double seconds, double start);
550 void advanceRk2SurfaceReactions(double seconds);
558 void syncRk2ReactionReport(SimulationContext&);
559 const ReactionData* rk_reactions_ = nullptr; // immutable model config
560 transport::RxWorkspace rk_reaction_workspace_;
561 std::vector<int> rk_reaction_rows_;
562 std::vector<double> rk_decay_, rk_reacted_, rk_reacted_reported_;
563 std::string rk_reaction_identity_;
564
565 // Recompute η/depth from state volumes for the whole mesh.
573 void reconstructAll();
574 // Flush every pending face accumulator into its cell (CSR gather over ALL
575 // cells) and refresh the closure of touched cells. MUST run before any
576 // tier/active-set reassignment: a cell deactivated or re-tiered with a
577 // pending side-accumulator strands flux whose counterpart the other side
578 // already applied — the backstop then realizes it as created volume.
590 void settleAccumulators();
591 // Apply lazily-accumulated sources (rain/held coupling) to INACTIVE cells
592 // over [t_last_sync_, t], rebuild the active cell/edge lists, and assign
593 // the LTS tiers (dt0_ = finest active CFL requirement).
612 void syncAndRebuild(double t);
613 // Tighten-only dt0_ refresh from CURRENT depths/speeds between rebuilds
614 // (dt0_ may only grow at syncAndRebuild, which reassigns the tiers).
625 void refreshDt0();
626 // Fire one tier's faces over their Δt: inertial update + Froude cap +
627 // face-cadence positivity share, booking ±ΔM into both side accumulators.
628 // global_step = every active cell fires at this same Δt (the window tail),
629 // so a face's exporter republishes its volume every time this face does
630 // and the positivity share is NOT divided down by a refire ratio.
643 void fireFaces(const std::vector<int>& faces, double dt_f, bool global_step = false);
644 // Fire one tier's cells over their Δt: gather + clear own-side face
645 // accumulators, apply sources, refresh closure + Perot vector; tier-0
646 // firings also evaluate the availability-clamped boundary edges.
647 // tier0 = this firing carries the tier-0 cadence work (boundary edges and
648 // the live junction exchange), which fires once per finest substep.
665 void fireCells(const std::vector<int>& cells, double dt_c, bool tier0);
667 template <bool kSwe, bool kFuse>
696 void fireCellsImpl(const std::vector<int>& cells, double dt_c, bool tier0);
697 // One halving-order macro cycle of nsub base substeps: tier k fires every
698 // 2^k substeps.
714 void runMacroCycle(double dt0, int nsub, double time);
715
716 MeshData* mesh_ = nullptr;
717 SurfaceStateData* state_ = nullptr;
718 SolverOptions2D* opts_ = nullptr;
719
720 void refreshConveyance();
721 InertialEdges edges_;
726 std::vector<double> q_;
729 std::vector<double> qcx_, qcy_;
730
731 // -----------------------------------------------------------------------
732 // Momentum closure (MOMENTUM_EQUATION, 2D_FULL_SWE plan §2.1). One
733 // marcher, three face laws; everything below the face/cell kernels
734 // (tiers, active sets, positivity, coupling, transport) is shared.
735 // -----------------------------------------------------------------------
740 bool has_quads_ = false;
744 std::vector<int> wall_ptr_, wall_slot_;
748 // Momentum packets share face_accum_ slots 2..5 in FULL_SWE.
752 double swe_bc_dqx_ = 0.0, swe_bc_dqy_ = 0.0;
756 bool second_order_ = false;
757 bool rk_transport_stage_ = false;
758 std::vector<double> rk_stage_mass_, rk_stage_volume_; // scratch beginning-state donor views
761 std::vector<double> gex_, gey_, gux_, guy_, gvx_, gvy_;
762 std::vector<double> rk_v0_, rk_qx0_, rk_qy0_;
765 std::vector<double> rk_inf0_, rk_bc0_, rk_ex0_, rk_drawn0_, rk_cpl0_;
777 void computeLimitedGradientsSwe();
778 MomentumPhysics2D momentum_physics_;
779 std::vector<int> wall_drag_slots_;
780 double wall_drag_energy_ = 0;
781 void applyMomentumPhysics(double seconds);
782 double runMomentumStep(double limit, double time);
783
799 void runRk2Step(double dt, double time);
812 void runRk2Step(double dt, double time, unsigned thermal_depth);
820 bool surfaceLatentCooling() const noexcept;
821 struct Rk2BoundarySignal {
822 int slot = -1, table = -1;
823 bool stage = false;
824 double scale = 1;
825 std::string name;
826 GwSourceSignal signal;
827 std::shared_ptr<const Table> curve{};
828 double x_scale = 1;
829 };
830 std::vector<Rk2BoundarySignal> rk_boundary_signals_;
831 double rk_boundary_anchor_ = 0;
832
838 bool front_rebuild_ = false;
839 std::vector<uint8_t> frontier_;
840 bool front_breach_ = false;
841 double h_on_front_ = 0.0;
844 std::vector<double> dw_slope_;
866 void fireFacesInertial(const std::vector<int>& faces, double dt_f, bool global_step);
886 void fireFacesSwe(const std::vector<int>& faces, double dt_f, bool global_step);
905 void fireFacesDiffusive(const std::vector<int>& faces, double dt_f, bool global_step);
926 void bookFaceSpecies(int e, int a, int b, double dM, double hf, double dt_f,
927 bool global_step) noexcept;
952 double boundaryFluxSwe(std::size_t k, int i, double dt_c,
953 const std::array<double, 4>* held = nullptr);
965 void refreshDiffusiveSlopes();
966 std::vector<uint8_t> cell_active_;
967 std::vector<int> active_cells_;
972 std::vector<int> active_faces_;
973 std::vector<uint8_t> pin_t0_;
975
976 // Tiered LTS. tier_[i] = k means cell i updates every 2^k base substeps
977 // with Δt = 2^k·dt0; face tier = min of its incident cells so a face
978 // always integrates at the finer cadence. Every face firing books the
979 // identical ±ΔM into the two logical water sides (single writer per face); each cell
980 // applies + clears its own side at its own firing — conservation across
981 // tier interfaces is exact by construction.
984 std::vector<uint8_t> rebuild_seed_;
985 std::vector<double> rebuild_dt_cell_;
988 std::vector<double> rebuild_dt_partial_;
989
990 std::vector<uint8_t> tier_;
991 std::vector<uint8_t> face_tier_;
996 std::vector<double> face_accum_;
997 std::size_t face_accum_stride_ = 2;
998
999 // S1 — species mass rides the SAME face accumulators, with each face
1000 // holding adjacent rows at [e * n_species + s]. Booked in fireFaces
1001 // immediately after the volume ΔM, from the FINAL qn1 (after the Froude
1002 // cap and the positivity share),
1003 // at the exporting cell's concentration read at that same substep;
1004 // gathered and cleared in fireCells alongside the volume side. That is
1005 // D-2DT2: the species flux inherits the volume flux's tier cadence rather
1006 // than reproducing it, so conservation across tier interfaces is the
1007 // marcher's own property and not a second one to prove. Empty unless
1008 // `state_->transport.active()`.
1009 std::vector<double> sacc_L_;
1010 std::vector<double> sacc_R_;
1015 bool species_on_ = false;
1030 double donorConc(int s, int cell) const noexcept;
1053 void sinkMassAtCellConc(int i, double dv_m3, std::vector<double>& ledger,
1054 double* per_point_ledger = nullptr) noexcept;
1055 // Sources share one water budget after face transfers have landed. Gross
1056 // constituent transfers are evaluated even when net water change is zero.
1057 // Returns false only when no source bookkeeping or volume change occurred.
1058 bool applyCellSources(int cell, double dt, double* boundary_donor = nullptr);
1059 bool applyPlainCellSources(int cell, double dt);
1060 // Selected only during an advance; external rebuild/settlement uses the full path.
1061 bool plain_sources_ = false;
1062 bool applyRuntimeSources(int cell, double dt);
1063 struct CellSourceStep {
1064 double area_dt = 0, rain = 0, coupling = 0, available = 0, infil = 0, evap = 0, out = 0,
1065 final_volume = 0, requested_infil = 0;
1066 double* ledger = nullptr;
1067 };
1068 bool prepareCellSources(int cell, double dt, CellSourceStep& source);
1069 void mixCellSourceRow(int cell, int row, double& mass, const CellSourceStep&);
1079 void prepareChannelGround(double seconds);
1080 void applyCellSourceRow(int cell, int row, double& mass, const CellSourceStep& source);
1081 enum class SourceLedger {
1082 Infiltration,
1083 CouplingOut,
1084 Rainfall,
1085 CouplingIn,
1086 Exfiltration,
1087 Boundary,
1088 ExternalIn,
1089 ExternalOut,
1090 BedExchange,
1091 Count
1092 };
1104 void bookSourceLedger(SourceLedger ledger, int species, double mass) noexcept;
1113 void flushSourceLedgers();
1121 int sourceThread() const noexcept;
1122 // Each worker owns a cache-line-padded slice: six species ledgers and
1123 // applied evaporation volume. Fold only after the parallel region ends.
1124 std::vector<double> source_ledgers_;
1125 std::size_t source_ledger_stride_ = 0;
1126 ChannelGroundState* channel_ground_ = nullptr;
1127 std::vector<CellSourceStep> channel_sources_;
1128 std::vector<uint8_t> channel_source_ready_, channel_temperature_mixed_;
1135 bool accumulators_pending_ = false;
1136 std::vector<std::vector<int>> cells_by_tier_;
1137 std::vector<std::vector<int>> edges_by_tier_;
1138 double dt0_ = 0.0;
1139 std::vector<int> bc_cell_;
1140 std::vector<int> bc_slot_;
1141 std::vector<int> gw_row_map_;
1142 std::vector<double> bc_accum_;
1144 std::vector<double> bc_q_;
1153
1154 // Live junction exchange (windowless coupling): state_->node_coupling
1155 // points, evaluated at tier-0 cadence against live 2D heads and the
1156 // routing-step 1D heads. exch_[k] = ∫Q_k dt (m³, + = 2D→1D), reset per
1157 // advance; node_drawn_ caps a step's total spill at the node's stored
1158 // volume so fill-and-spill thrash is structurally impossible.
1159 std::vector<double> exch_;
1160 std::vector<double> node_drawn_;
1167 double spill_deficit_ = 0.0;
1168
1169public:
1182 void setExchangeHeadSlopes(std::vector<double> slopes) { exch_head_slope_ = std::move(slopes); }
1183
1184private:
1185 std::vector<double> exch_head_slope_;
1186 double exch_tau_ = 0.0;
1187
1188 double t_last_sync_ = 0.0;
1189 CommonSourceStage* common_stage_ = nullptr;
1190 SourceResultHistory* source_history_ = nullptr;
1191 EngineSourceResultHistory* engine_source_history_ = nullptr;
1192 class EngineSourceResultStream* engine_source_stream_ = nullptr;
1193 bool checkpoint_ready_ = false;
1194 bool joint_checkpoint_ = false; // only SurfaceRouter2D may restore live 1D pairing
1203 std::string checkpointBoundary() const;
1213 void checkpointState(detail::CompletedSourceArchive& archive);
1224 void captureSourceResults();
1225 double source_phase_end_ = 0.0;
1226 int cycles_since_rebuild_ = 1000;
1238 void lazySourcesOnly(double t);
1239 long substeps_run_ = 0;
1240 long face_passes_ = 0;
1241 long last_steps_ = 0;
1242 double last_dt_ = 0.0;
1243 bool initialized_ = false;
1244
1245 // Active-fraction telemetry: (sim time, active cells) sampled at every
1246 // rebuild; dumped as CSV at finalize() when OPENSWMM_2D_MARCHER_TELEMETRY
1247 // names a file. The Phase-1 gate reads this to verify the thin-film budget
1248 // assumption on the Bellinge storm slice.
1249 std::vector<std::pair<double, int>> telemetry_;
1250 std::string telemetry_path_;
1254 double active_frac_min_ = 1.0e30;
1255 double active_frac_max_ = -1.0e30;
1256 double active_frac_sum_ = 0.0;
1257 long active_samples_ = 0;
1259 std::array<long, 8> tier_occupancy_{};
1260
1271 SubsurfaceSolver* gw_ = nullptr;
1272
1273public:
1285 void setSubsurface(SubsurfaceSolver* gw) noexcept { gw_ = gw; }
1297 void refreshSubsurfaceRows();
1298
1309 const InertialEdges& inertialEdges() const noexcept { return edges_; }
1310};
1311
1312} // namespace openswmm::twoD
1313
1314#endif // OPENSWMM_ENGINE_2D_EXPLICIT_INERTIAL_SOLVER_HPP
Immutable groundwater source series in elapsed simulation time.
Backend-neutral interface for the 2D surface-routing time integrator.
Unique interior-edge structure for the local-inertial momentum DOFs.
Reaction kinetics integration over one reaction step (phase R3): EUL / RK5 (Cash–Karp) / ROS2 / BDF2 ...
Configuration options for the 2D surface routing solver.
Definition CompletedSourceArchive.hpp:25
Owned completed-source water/snow and conservative-pollutant trial driver. Context and kernel histori...
Definition SourceWaterDriver.hpp:83
Definition ReactionIntegrator.hpp:89
Definition ChannelGround.hpp:29
Definition CommonSourceStage.hpp:44
Definition EngineSourceResultHistory.hpp:23
Definition EngineSourceResultStream.hpp:27
Definition ExplicitInertialSolver.hpp:80
void finalize() override
Detach source/checkpoint recorders, settle CPU surface packets and release marching work lists.
Definition ExplicitInertialSolver.cpp:4786
void reinitialize(double t0) override
Reconstruct externally edited volume after discarding stale surface momentum/side packets.
Definition ExplicitInertialSolver.cpp:4620
void setCommonSourceStage(CommonSourceStage &stage)
Internal qualification adapter; owned externally and attached at time zero.
Definition ExplicitInertialSolver.cpp:4735
void publishMomentumVelocity()
Definition ExplicitInertialSolver.cpp:4151
friend class SurfaceRouter2D
Definition ExplicitInertialSolver.hpp:373
double completedSourceTime() const noexcept override
Read the finest accepted source synchronization horizon.
Definition ExplicitInertialSolver.hpp:139
bool is_initialized() const noexcept override
Read the marcher's stored lifecycle activation flag.
Definition ExplicitInertialSolver.hpp:350
void setChannelGround(ChannelGroundState &)
Attach a borrowed active channel/soil finite thermal owner before first advance.
Definition ExplicitInertialSolver.cpp:4722
std::string saveCheckpoint(const std::string &path) const
Internal accepted CPU source boundary or standalone water-only RK2 component checkpoint; not a whole-...
Definition ExplicitInertialCheckpoint.cpp:649
void resumeSourceResultHistory(SourceResultHistory &history)
Bind a saved exact source-history prefix after a marcher restore.
Definition ExplicitInertialSolver.cpp:4776
void pinExternalSourceCells(const std::vector< int > &cells)
External outfall transfers keep these cells and their halo on tier zero.
Definition ExplicitInertialSolver.cpp:4704
const std::vector< double > & last_coupling_exchange() const noexcept override
Borrow the last advance's integrated live junction exchange.
Definition ExplicitInertialSolver.hpp:358
void refreshBoundaries()
Refresh changed perimeter topology between completed advances.
Definition ExplicitInertialSolver.cpp:4638
void setSubsurface(SubsurfaceSolver *gw) noexcept
Attach (or detach, with nullptr) the groundwater kernel. Must be called before the first advance and ...
Definition ExplicitInertialSolver.hpp:1285
double advance(double t_current, double t_target) override
Advance one accepted CPU surface/groundwater window to its target horizon.
Definition ExplicitInertialSolver.cpp:4306
long last_num_steps() const noexcept override
Read base substeps executed by the most recent accepted advance.
Definition ExplicitInertialSolver.hpp:328
RunStats run_stats() const noexcept override
Snapshot cumulative CPU marcher work and rebuild-sampled active/tier statistics.
Definition ExplicitInertialSolver.cpp:4837
double spillDeficit() const noexcept
C3: cumulative spill volume (m³) the coupled nodes could not supply from their ponded storage....
Definition ExplicitInertialSolver.hpp:369
void resolveRk2BoundarySignals(double time)
Evaluate owned project boundary signals at the current coupled stage time/state.
Definition ExplicitInertialSolver.cpp:3168
void setSourceResultHistory(SourceResultHistory &history)
Capture initial state and every finest accepted source interval, after all due groundwater firings/no...
Definition ExplicitInertialSolver.cpp:4749
void resyncFromVolumes(double t0) override
Reconstruct head/depth after authoritative external volume transfers without losing momentum.
Definition ExplicitInertialSolver.cpp:4692
double surfaceReactionLoss(int row) const noexcept
Read one row's cumulative qualified RK2 native reaction loss.
Definition ExplicitInertialSolver.hpp:292
const InertialEdges & inertialEdges() const noexcept
The unique interior-edge topology this marcher built. Exposed so the groundwater kernel can put its l...
Definition ExplicitInertialSolver.hpp:1309
std::string configureRk2Reactions(const SimulationContext &)
Configure the standalone coupled RK2 surface reaction owner at time zero.
Definition ExplicitInertialSolver.cpp:3859
void initialize(MeshData &mesh, SurfaceStateData &state, SolverOptions2D &opts) override
Prepare the CPU finite-volume marcher from authoritative surface volume and resolved model.
Definition ExplicitInertialSolver.cpp:185
std::string loadCheckpoint(const std::string &path)
Restore a component checkpoint into fresh initialized matching exclusive owners.
Definition ExplicitInertialCheckpoint.cpp:712
void refreshSubsurfaceRows()
Rebuild exact-name native surface-to-groundwater row mapping after owners are prepared.
Definition ExplicitInertialSolver.cpp:507
friend class openswmm::SWMMEngine
Definition ExplicitInertialSolver.hpp:374
friend struct ExplicitInertialSolverTestAccess
Definition ExplicitInertialSolver.hpp:372
double last_step_size() const noexcept override
Read the most recent accepted base or tail duration.
Definition ExplicitInertialSolver.hpp:334
void setExchangeHeadSlopes(std::vector< double > slopes)
Experimental (OPENSWMM_2D_HEAD_RAMP): per-coupling-point 1D head trend slope (m/s,...
Definition ExplicitInertialSolver.hpp:1182
std::string configureRk2BoundarySignals(SimulationContext &, double stage_scale, double flow_scale)
Resolve owned cursor-free surface boundary forcing for the fresh coupled RK2 owner.
Definition ExplicitInertialSolver.cpp:3068
Abstract time integrator for the 2D surface-routing ODE system.
Definition ISurfaceSolver.hpp:61
Definition MomentumPhysics2D.hpp:25
Definition SourceResultHistory.hpp:41
The two-zone kernel. Owned by SurfaceRouter2D; driven by the marcher's tier hooks.
Definition SubsurfaceSolver.hpp:128
Definition OutfallExchange.hpp:17
Native infiltration kernels with caller-managed accepted-step state. These methods mutate the supplie...
Definition Infiltration.cpp:34
Definition ExplicitInertialSolver.hpp:68
Definition ExchangeLaw.hpp:60
Momentum2D
Momentum closure of the explicit 2D marcher (2D_FULL_SWE_SHOCK_CAPTURING_PLAN_2026-09-05 §2).
Definition SolverOptions2D.hpp:195
@ LOCAL_INERTIAL
Definition SolverOptions2D.hpp:195
Definition ExchangeLaw.hpp:60
Definition ReactionData.hpp:58
Central, reentrant simulation context.
Definition SimulationContext.hpp:390
Definition TableData.hpp:154
Definition GwSourceForcing.hpp:27
Canonical unique interior-edge layout + per-cell incidence for the local-inertial scheme.
Definition InertialEdges.hpp:60
SoA storage for 2D mixed triangle/quad mesh geometry and topology.
Definition MeshData.hpp:65
Definition Rk2RuntimeSources.hpp:78
Definition SolverOptions2D.hpp:257
Definition SurfaceStateData.hpp:64