95#ifndef OPENSWMM_QUALITY_LARD_LAGRANGIAN_SOLVER_HPP
96#define OPENSWMM_QUALITY_LARD_LAGRANGIAN_SOLVER_HPP
254 std::vector<ChannelThermalRegion> out;
256 if (!initialized_ || layout.temp_row < 0)
258 std::vector<double> mean(layout.ns);
260 const double volume = store_.total_volume(
link) * .028316846592;
263 store_.mean_conc(
link, mean.data());
264 out.push_back({
link, 0, 0, 1, volume, mean[layout.temp_row]});
280 std::vector<ChannelSpeciesRegion> out;
282 if (!initialized_ || layout.temp_row < 0)
284 std::vector<double> mean(layout.ns);
286 const double volume = store_.total_volume(
link) * .028316846592;
289 store_.mean_conc(
link, mean.data());
294 for (
int p = 0; p < layout.np; ++p)
296 if (layout.age_row >= 0)
297 region.
row_names.emplace_back(
"__WATER_AGE__");
298 if (layout.temp_row >= 0)
299 region.
row_names.emplace_back(
"__TEMPERATURE__");
300 if (layout.msx_first >= 0)
301 for (
int row = layout.msx_first; row < layout.ns; ++row)
305 out.push_back(std::move(region));
326 const std::vector<ChannelSpeciesIncrement>& updates) {
328 if (!initialized_ || layout.temp_row < 0)
329 return "LARD channel species state is unavailable.";
330 for (
const auto& u : updates) {
331 if (u.link < 0 || u.link >= ctx.
n_links() || u.region != 0 ||
332 u.concentration_delta.size() != std::size_t(layout.ns))
333 return "LARD species increment has an invalid channel region or row count.";
334 if (u.concentration_delta[layout.temp_row] != 0)
335 return "LARD species update cannot modify the temperature row.";
336 std::vector<double> mean(layout.ns);
337 store_.mean_conc(u.link, mean.data());
338 for (
int k = 0; k < store_.count(u.link); ++k)
339 for (
int row = 0; row < layout.ns; ++row) {
340 const double delta = u.concentration_delta[row];
341 const double target = mean[row] + delta;
342 const double next = mean[row] > 0
343 ? store_.seg_conc(u.link, k, row) * target / mean[row]
345 if (!std::isfinite(delta) || !std::isfinite(next) ||
346 (row != layout.temp_row && next < 0))
347 return "LARD species increment is nonfinite or makes a carried mass negative.";
354 for (
const auto& u : updates) {
355 std::vector<double> mean(layout.ns);
356 store_.mean_conc(u.link, mean.data());
357 for (
int k = 0; k < store_.count(u.link); ++k)
358 for (
int row = 0; row < layout.ns; ++row)
359 if (row != layout.temp_row) {
360 const double target = mean[row] + u.concentration_delta[row];
362 mean[row] > 0 ? store_.seg_conc(u.link, k, row) * target / mean[row]
364 store_.set_seg_conc(u.link, k, row, next);
384 const std::vector<ChannelThermalIncrement>& updates) {
386 if (!initialized_ || layout.temp_row < 0)
387 return "LARD channel thermal state is unavailable.";
388 for (
const auto& u : updates) {
389 if (u.link < 0 || u.link >= ctx.
n_links() || u.region != 0 || !std::isfinite(u.celsius))
390 return "LARD thermal increment has an invalid channel region.";
391 for (
int k = 0; k < store_.count(u.link); ++k)
392 if (!std::isfinite(store_.seg_conc(u.link, k, layout.temp_row) + u.celsius))
393 return "LARD thermal increment is nonfinite.";
395 for (
const auto& u : updates)
396 for (
int k = 0; k < store_.count(u.link); ++k)
397 store_.set_seg_conc(u.link, k, layout.temp_row,
398 store_.seg_conc(u.link, k, layout.temp_row) + u.celsius);
433 auto& links = ctx.
links;
437 bool topo_dirty =
false;
438 for (
int l = 0; l < nl; ++l) {
439 const auto ul =
static_cast<std::size_t
>(l);
440 const double q = links.flow[ul];
443 const std::int8_t sign = (q >= 0.0) ? 1 : -1;
444 if (sign != flow_sign_[ul]) {
447 flow_sign_[ul] = sign;
451 if (topo_dirty || topo_.empty())
452 computeTopoOrder(ctx);
462 (dtq > 0.0 && dtq < dt_routing) ? static_cast<int>(std::ceil(dt_routing / dtq)) : 1;
463 const double dt = dt_routing /
static_cast<double>(nsub);
464 const double frac = 1.0 /
static_cast<double>(nsub);
465 for (
int sub = 0; sub < nsub; ++sub)
471 applyFluxesAndBed(ctx, dt_routing);
504 const bool age = (L.
age_row >= 0);
505 const bool heat = (L.
temp_row >= 0);
508 auto& nodes = ctx.
nodes;
509 auto& links = ctx.
links;
517 store_.add_species(L.
age_row, dt);
518 for (
int n = 0; n < nn; ++n)
519 ws.node_age[
static_cast<std::size_t
>(n)] += dt;
523 scratch_.assign(
static_cast<std::size_t
>(ns), 0.0);
524 treat_cin_.assign(
static_cast<std::size_t
>(np > 0 ? np : 0), 0.0);
525 for (
int l = 0; l < nl; ++l) {
526 const auto ul =
static_cast<std::size_t
>(l);
529 const double q = std::abs(links.flow[ul]);
530 const int dn = downstreamNode(ctx, l);
531 const int up = upstreamNode(ctx, l);
534 std::fill(scratch_.begin(), scratch_.end(), 0.0);
535 const double drained = store_.drain_back(l, q * dt, scratch_.data());
536 addToLedger(dn, drained, scratch_.data(), ns);
542 const double v_new = links.volume[ul];
543 const double v_rem = store_.total_volume(l);
544 if (v_rem > v_new && up >= 0) {
545 std::fill(scratch_.begin(), scratch_.end(), 0.0);
546 const double shed = store_.drain_front(l, v_rem - v_new, scratch_.data());
547 addToLedger(up, shed, scratch_.data(), ns);
552 for (
const int n : topo_) {
553 const auto un =
static_cast<std::size_t
>(n);
560 const double v_in = node_vol_in_[un];
567 v_in + nodes.qual_vol_in[un] * frac <= 0.0;
569 for (
int s = 0; s < ns; ++s) {
573 const bool is_age = (s == L.
age_row);
574 const bool is_temp = (s == L.
temp_row);
579 const auto xi = is_msx ? un *
static_cast<std::size_t
>(ns - L.
msx_first) +
580 static_cast<std::size_t
>(s - L.
msx_first)
583 un *
static_cast<std::size_t
>(ns) +
static_cast<std::size_t
>(s);
589 const auto ci = un *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(s);
595 const double st_old = is_age ? ws.node_age[un]
596 : is_temp ? hs.node_temp[un]
601 const double m_ext = is_age ? ws.node_age_vol_in[un] * dt
602 : is_temp ? hs.node_temp_vol_in[un] * dt
606 : nodes.qual_mass_in[ci] * dt;
607 double m = st_old * v_old + node_mass_in_[li] + m_ext;
610 if (!is_age && !is_temp && !is_msx && s <
static_cast<int>(treat_cin_.size()))
611 treat_cin_[
static_cast<std::size_t
>(s)] = node_mass_in_[li] + m_ext;
621 if (m < 0.0 && !is_temp) {
628 const double denom = v_old + v_in + nodes.qual_vol_in[un] * frac;
639 if (is_msx && !zero_bf)
641 v_in + nodes.qual_vol_in[un] * frac);
642 const double st_new = zero_bf ? 0.0 : ((denom > 1.0e-12) ? m / denom : st_old);
644 ws.node_age[un] = st_new;
646 hs.node_temp[un] = st_new;
650 nodes.conc[ci] = st_new;
651 node_mass_in_[li] = 0.0;
653 node_vol_in_[un] = 0.0;
667 const double v_in_total = v_in + nodes.qual_vol_in[un] * frac;
668 for (
int p = 0; p < np; ++p)
669 treat_cin_[
static_cast<std::size_t
>(p)] =
670 (v_in_total > 1.0e-12)
671 ? std::max(0.0, treat_cin_[
static_cast<std::size_t
>(p)]) / v_in_total
679 for (
const int l : node_out_links_[un]) {
680 const auto ul =
static_cast<std::size_t
>(l);
683 const double q = std::abs(links.flow[ul]);
686 const int dn = downstreamNode(ctx, l);
689 for (
int p = 0; p < np; ++p) {
690 const auto ni = un *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p);
691 const auto lp = ul *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p);
692 scratch_[
static_cast<std::size_t
>(p)] = nodes.conc[ni];
693 links.conc[lp] = nodes.conc[ni];
696 scratch_[
static_cast<std::size_t
>(L.
age_row)] = ws.node_age[un];
697 ws.link_age[ul] = ws.node_age[un];
700 scratch_[
static_cast<std::size_t
>(L.
temp_row)] = hs.node_temp[un];
701 hs.link_temp[ul] = hs.node_temp[un];
705 for (
int sp = 0; sp < nsp; ++sp) {
708 static_cast<std::size_t
>(sp)];
709 scratch_[
static_cast<std::size_t
>(L.
msx_first + sp)] = c;
711 static_cast<std::size_t
>(sp)] = c;
714 addToLedgerRate(dn, q * dt, scratch_.data(), ns);
719 for (
int l = 0; l < nl; ++l) {
720 const auto ul =
static_cast<std::size_t
>(l);
723 const int up = upstreamNode(ctx, l);
724 const double need = links.volume[ul] - store_.total_volume(l);
725 release_vol_[ul] = (need > 0.0) ? need : 0.0;
726 if (need <= 0.0 || up < 0)
728 const auto uu =
static_cast<std::size_t
>(up);
729 for (
int p = 0; p < np; ++p)
730 scratch_[
static_cast<std::size_t
>(p)] =
731 nodes.conc[uu *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)];
733 scratch_[
static_cast<std::size_t
>(L.
age_row)] = ws.node_age[uu];
735 scratch_[
static_cast<std::size_t
>(L.
temp_row)] = hs.node_temp[uu];
738 for (
int sp = 0; sp < nsp; ++sp)
739 scratch_[
static_cast<std::size_t
>(L.
msx_first + sp)] =
741 static_cast<std::size_t
>(sp)];
743 store_.push_front(l, need, scratch_.data());
756 for (
int l = 0; l < nl; ++l) {
757 const auto ul =
static_cast<std::size_t
>(l);
760 const double q = std::abs(links.flow[ul]);
766 const auto ur =
static_cast<std::size_t
>(row);
767 const double len = cond.length[ur];
768 const double vol = links.volume[ul];
769 if (len <= 0.0 || vol <= 0.0)
771 const double a_flow = vol / len;
772 const double ubar = q / a_flow;
773 const double h = links.depth[ul];
775 const double rh =
rwpt_hyd_radius(a_flow, h, links.xsect_geom1[ul], circ);
776 rwpt_.disperse(ctx, store_, l, ubar, h, rh, cond.roughness[ur], release_vol_[ul],
777 q * dt, dt, substep_counter_,
783 for (
int p = 0; p < np; ++p) {
787 const double f = std::exp(-k * dt);
788 double removed = store_.decay_species(p, f);
789 for (
int n = 0; n < nn; ++n) {
790 const auto idx =
static_cast<std::size_t
>(n) *
static_cast<std::size_t
>(np) +
791 static_cast<std::size_t
>(p);
793 removed += nodes.conc[idx] * (1.0 - f) * v;
794 nodes.conc[idx] *= f;
811 if (
static_cast<int>(msx_block_.size()) < nsp)
812 msx_block_.assign(
static_cast<std::size_t
>(nsp), 0.0);
813 if (
static_cast<int>(msx_poll_.size()) < np && np > 0)
814 msx_poll_.assign(
static_cast<std::size_t
>(np), 0.0);
815 for (
int l = 0; l < nl; ++l) {
816 const auto ul =
static_cast<std::size_t
>(l);
819 const int cnt = store_.count(l);
820 for (
int i = 0; i < cnt; ++i) {
821 for (
int sp = 0; sp < nsp; ++sp)
822 msx_block_[
static_cast<std::size_t
>(sp)] =
824 for (
int p = 0; p < np; ++p)
825 msx_poll_[
static_cast<std::size_t
>(p)] = store_.seg_conc(l, i, p);
829 const double seg_temp_c = (L.
temp_row >= 0)
831 : std::numeric_limits<double>::quiet_NaN();
833 np > 0 ? msx_poll_.data() :
nullptr, 0.0,
835 for (
int sp = 0; sp < nsp; ++sp)
836 store_.set_seg_conc(l, i, L.
msx_first + sp,
837 msx_block_[
static_cast<std::size_t
>(sp)]);
840 for (
int n = 0; n < nn; ++n) {
841 const auto un =
static_cast<std::size_t
>(n);
844 for (
int sp = 0; sp < nsp; ++sp)
845 msx_block_[
static_cast<std::size_t
>(sp)] =
846 rx.msx_node_conc[un *
static_cast<std::size_t
>(nsp) +
847 static_cast<std::size_t
>(sp)];
848 for (
int p = 0; p < np; ++p)
849 msx_poll_[
static_cast<std::size_t
>(p)] =
850 nodes.conc[un *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)];
851 const double hrt = (un < nodes.hrt.size()) ? nodes.hrt[un] : 0.0;
854 : std::numeric_limits<double>::quiet_NaN();
856 np > 0 ? msx_poll_.data() :
nullptr, hrt, node_temp_c,
858 for (
int sp = 0; sp < nsp; ++sp)
859 rx.msx_node_conc[un *
static_cast<std::size_t
>(nsp) +
860 static_cast<std::size_t
>(sp)] =
861 msx_block_[
static_cast<std::size_t
>(sp)];
887 const bool age = (L.
age_row >= 0);
888 const bool heat = (L.
temp_row >= 0);
890 auto& nodes = ctx.
nodes;
891 auto& links = ctx.
links;
896 for (
int l = 0; l < nl; ++l) {
897 const auto ul =
static_cast<std::size_t
>(l);
900 store_.mean_conc(l, scratch_.data());
901 for (
int p = 0; p < np; ++p)
902 links.conc[ul *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)] =
903 scratch_[
static_cast<std::size_t
>(p)];
912 if (store_.count(l) > 0)
913 ws.link_age[ul] = scratch_[
static_cast<std::size_t
>(L.
age_row)];
915 ws.link_age[ul] += dt_routing;
923 if (store_.count(l) > 0)
924 hs.link_temp[ul] = scratch_[
static_cast<std::size_t
>(L.
temp_row)];
928 if (L.
msx_first >= 0 && store_.count(l) > 0) {
930 for (
int sp = 0; sp < nsp; ++sp)
932 static_cast<std::size_t
>(sp)] =
933 scratch_[
static_cast<std::size_t
>(L.
msx_first + sp)];
937 links.conc_old = links.conc;
938 nodes.conc_old = nodes.conc;
970 a(substep_counter_, topo_, node_out_links_);
971 a.fixed(flow_sign_, node_mass_in_, node_vol_in_);
972 const int nn = int(node_vol_in_.size()), nl = int(flow_sign_.size());
973 if (topo_.size() != std::size_t(nn) || node_out_links_.size() != std::size_t(nn))
974 throw std::runtime_error(
"Invalid LARD topology shape.");
975 std::vector<bool> seen(nn,
false);
976 for (
int n : topo_) {
977 if (n < 0 || n >= nn || seen[n])
978 throw std::runtime_error(
"Invalid LARD checkpoint topology.");
981 for (
const auto& links : node_out_links_)
983 if (l < 0 || l >= nl)
984 throw std::runtime_error(
"Invalid LARD checkpoint link.");
985 for (
auto v : flow_sign_)
987 throw std::runtime_error(
"Invalid LARD flow direction.");
988 for (
const auto* v : {&node_mass_in_, &node_vol_in_})
990 if (!std::isfinite(x))
991 throw std::runtime_error(
"Nonfinite LARD inflow receipt.");
1025 void applyFluxesAndBed(SimulationContext& ctx,
double dt) {
1026 const SpeciesRowLayout L =
rowLayout(ctx);
1027 const bool heat_on = (L.temp_row >= 0);
1029 const bool flux_on =
1030 heat_on && (ctx.heat_config.surface_exchange || ctx.heat_config.radiative_exchange);
1031 if ((!flux_on && !bed_on) || !(dt > 0.0))
1033 namespace th = transport::heat;
1036 th::updateSolarForcing(ctx, dt);
1038 th::seedBedTemperature(ctx);
1039 const double t_gr = bed_on ? th::groundTemperature(ctx) : 0.0;
1040 const double rho = ctx.options.water_density;
1041 const double cp = ctx.options.water_specific_heat;
1042 constexpr double kSqFt = 0.09290304;
1043 constexpr double kCuFt = 0.028316846592;
1045 auto& hs = ctx.heat_state;
1046 const int nl = ctx.n_links();
1047 const int nn = ctx.n_nodes();
1052 for (
int n = 0; n < nn; ++n) {
1053 const auto un =
static_cast<std::size_t
>(n);
1054 if (un >= hs.node_temp.size())
1059 const double a =
node::getSurfArea(ctx.nodes, n, ctx.nodes.depth[un], &ctx.tables,
1060 unit_sys, &ctx.node_subtypes);
1063 const double t = hs.node_temp[un];
1066 th::relaxT(th::netFluxOut(ctx, ne, t), th::netFluxOut(ctx, ne, t + th::kProbeC),
1067 th::kProbeC, a * kSqFt, vol * kCuFt, dt, rho, cp);
1073 const int n_bed = L.np + ((L.msx_first >= 0) ? (L.ns - L.msx_first) : 0);
1074 auto& bed = ctx.bed_state;
1075 if (bed_on && n_bed > 0 &&
1076 (bed.n_species != n_bed ||
1077 bed.link_conc.size() !=
1078 static_cast<std::size_t
>(nl) *
static_cast<std::size_t
>(n_bed))) {
1079 bed.link_conc.assign(
static_cast<std::size_t
>(nl) *
static_cast<std::size_t
>(n_bed),
1081 bed.n_species = n_bed;
1084 for (
int l = 0; l < nl; ++l) {
1085 const auto ul =
static_cast<std::size_t
>(l);
1086 if (ul < ctx.heat_state.mesh_contact_link.size() &&
1087 ctx.heat_state.mesh_contact_link[ul])
1091 const int cnt = store_.count(l);
1094 const double vol_ft3 = store_.total_volume(l);
1095 if (!(vol_ft3 > 0.0))
1098 store_.mean_conc(l, scratch_.data());
1106 const double t_mean = scratch_[
static_cast<std::size_t
>(L.temp_row)];
1107 const double surf_m2 = flux_on ? th::linkFreeSurfaceFt2(ctx, l) * kSqFt : 0.0;
1109 bool bed_stepped =
false;
1110 if (bed_on && ul < bed.link_temp.size()) {
1112 const auto& sd = th::sedimentFor(ctx, le);
1113 const th::BedCoupling g =
1114 th::bedCouplingFromContact(ctx, sd, th::linkBedAreaM2(ctx, l), vol_ft3,
1115 th::groundTempFor(ctx, sd, t_gr));
1117 const th::PairStep ps = th::relaxPair(
1118 g, t_mean, bed.link_temp[ul],
1119 flux_on ? th::netFluxOut(ctx, le, t_mean) : 0.0,
1120 flux_on ? th::netFluxOut(ctx, le, t_mean + th::kProbeC) : 0.0,
1121 flux_on ? th::kProbeC : 0.0, surf_m2, dt);
1123 bed.link_temp[ul] += ps.dt_b;
1127 if (!bed_stepped && flux_on && surf_m2 > 0.0) {
1128 d_tw = th::relaxT(th::netFluxOut(ctx, le, t_mean),
1129 th::netFluxOut(ctx, le, t_mean + th::kProbeC), th::kProbeC,
1130 surf_m2, vol_ft3 * kCuFt, dt, rho, cp);
1133 for (
int i = 0; i < cnt; ++i)
1134 store_.set_seg_conc(l, i, L.temp_row,
1135 store_.seg_conc(l, i, L.temp_row) + d_tw);
1139 if (bed_on && n_bed > 0) {
1142 const auto& sd = th::sedimentFor(ctx, le);
1143 const double bed_m2 = th::linkBedAreaM2(ctx, l);
1144 const double vol_b = bed_m2 * sd.bed_thickness;
1145 const double q_exch = th::bedExchangeQ(sd, bed_m2);
1146 if (q_exch > 0.0 && vol_b > 0.0) {
1147 for (
int b = 0; b < n_bed; ++b) {
1150 const int row = (b < L.np) ? b : L.msx_first + (b - L.np);
1151 const double c_mean = scratch_[
static_cast<std::size_t
>(row)];
1152 double& cb = bed.link_conc[ul *
static_cast<std::size_t
>(n_bed) +
1153 static_cast<std::size_t
>(b)];
1154 const th::SolutePairStep st =
1155 th::exchangePair(c_mean, cb, vol_ft3 * kCuFt, vol_b, q_exch, dt);
1157 for (
int i = 0; i < cnt; ++i)
1158 store_.set_seg_conc(l, i, row,
1159 store_.seg_conc(l, i, row) + st.dc_w);
1180 void init(SimulationContext& ctx) {
1182 const SpeciesRowLayout L =
rowLayout(ctx);
1183 const int np = L.np;
1184 const int ns = L.ns;
1187 const bool age = (L.age_row >= 0);
1188 const bool heat = (L.temp_row >= 0);
1189 const int nn = ctx.n_nodes();
1190 const int nl = ctx.n_links();
1193 store_.resize(nl, ns, std::max(2, ctx.options.max_segments_per_link));
1194 flow_sign_.assign(
static_cast<std::size_t
>(nl), 1);
1195 node_mass_in_.assign(
static_cast<std::size_t
>(nn) *
static_cast<std::size_t
>(ns), 0.0);
1196 node_vol_in_.assign(
static_cast<std::size_t
>(nn), 0.0);
1197 scratch_.assign(
static_cast<std::size_t
>(ns), 0.0);
1198 treat_cin_.assign(
static_cast<std::size_t
>(np > 0 ? np : 0), 0.0);
1199 node_out_links_.assign(
static_cast<std::size_t
>(nn), {});
1200 release_vol_.assign(
static_cast<std::size_t
>(nl), 0.0);
1201 if (ctx.options.lard_rwpt)
1208 if (L.msx_first >= 0)
1218 auto& ws = ctx.water_age_state;
1219 if (ws.node_age.size() !=
static_cast<std::size_t
>(nn))
1220 ws.resize(nn, nl, ctx.n_subcatches());
1221 if (!ws.hotstart_loaded) {
1222 const double a0 = ctx.water_age_config
1225 std::fill(ws.node_age.begin(), ws.node_age.end(), a0);
1226 std::fill(ws.link_age.begin(), ws.link_age.end(), a0);
1247 auto& hstate = ctx.heat_state;
1250 if (hstate.node_temp.size() !=
static_cast<std::size_t
>(nn))
1251 hstate.resize(nn, nl, t0);
1252 if (!hstate.legacy_seeded) {
1253 std::fill(hstate.node_temp.begin(), hstate.node_temp.end(), t0);
1254 std::fill(hstate.link_temp.begin(), hstate.link_temp.end(), t0);
1256 hstate.legacy_seeded =
true;
1262 for (
int l = 0; l < nl; ++l) {
1263 const auto ul =
static_cast<std::size_t
>(l);
1264 store_.clear_link(l);
1267 const double v = ctx.links.volume[ul];
1270 for (
int p = 0; p < np; ++p)
1271 scratch_[
static_cast<std::size_t
>(p)] =
1272 ctx.links.conc[ul *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)];
1274 scratch_[
static_cast<std::size_t
>(L.age_row)] = ctx.water_age_state.link_age[ul];
1276 scratch_[
static_cast<std::size_t
>(L.temp_row)] = ctx.heat_state.link_temp[ul];
1277 if (L.msx_first >= 0) {
1278 const int nsp = ns - L.msx_first;
1279 for (
int sp = 0; sp < nsp; ++sp)
1280 scratch_[
static_cast<std::size_t
>(L.msx_first + sp)] =
1281 ctx.reactions.msx_link_conc[ul *
static_cast<std::size_t
>(nsp) +
1282 static_cast<std::size_t
>(sp)];
1284 store_.push_front(l, v, scratch_.data());
1285 flow_sign_[ul] = (ctx.links.flow[ul] >= 0.0) ? 1 : -1;
1287 computeTopoOrder(ctx);
1288 initialized_ =
true;
1298 int upstreamNode(
const SimulationContext& ctx,
int l)
const {
1299 const auto ul =
static_cast<std::size_t
>(l);
1300 return (flow_sign_[ul] >= 0) ? ctx.links.node1[ul] : ctx.links.node2[ul];
1309 int downstreamNode(
const SimulationContext& ctx,
int l)
const {
1310 const auto ul =
static_cast<std::size_t
>(l);
1311 return (flow_sign_[ul] >= 0) ? ctx.links.node2[ul] : ctx.links.node1[ul];
1327 void addToLedger(
int n,
double vol,
const double* mass,
int np) {
1328 const auto un =
static_cast<std::size_t
>(n);
1329 node_vol_in_[un] += vol;
1330 for (
int p = 0; p < np; ++p)
1331 node_mass_in_[un *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)] +=
1332 mass[
static_cast<std::size_t
>(p)];
1349 void addToLedgerRate(
int n,
double vol,
const double* conc,
int np) {
1350 const auto un =
static_cast<std::size_t
>(n);
1351 node_vol_in_[un] += vol;
1352 for (
int p = 0; p < np; ++p)
1353 node_mass_in_[un *
static_cast<std::size_t
>(np) +
static_cast<std::size_t
>(p)] +=
1354 vol * conc[
static_cast<std::size_t
>(p)];
1371 void computeTopoOrder(SimulationContext& ctx) {
1372 const int nn = ctx.n_nodes();
1373 const int nl = ctx.n_links();
1374 std::vector<int> indeg(
static_cast<std::size_t
>(nn), 0);
1375 for (
auto& v : node_out_links_)
1377 for (
int l = 0; l < nl; ++l) {
1378 const auto ul =
static_cast<std::size_t
>(l);
1379 const int up = upstreamNode(ctx, l);
1380 const int dn = downstreamNode(ctx, l);
1381 if (up < 0 || dn < 0)
1383 node_out_links_[
static_cast<std::size_t
>(up)].push_back(l);
1384 if (std::abs(ctx.links.flow[ul]) >
kTinyFlow)
1385 indeg[
static_cast<std::size_t
>(dn)] += 1;
1388 topo_.reserve(
static_cast<std::size_t
>(nn));
1390 for (
int n = 0; n < nn; ++n)
1391 if (indeg[
static_cast<std::size_t
>(n)] == 0)
1393 std::vector<char> seen(
static_cast<std::size_t
>(nn), 0);
1395 while (qi < q.size()) {
1396 const int n = q[qi++];
1397 if (seen[
static_cast<std::size_t
>(n)])
1399 seen[
static_cast<std::size_t
>(n)] = 1;
1401 for (
const int l : node_out_links_[
static_cast<std::size_t
>(n)]) {
1402 if (std::abs(ctx.links.flow[
static_cast<std::size_t
>(l)]) <=
kTinyFlow)
1404 const int dn = downstreamNode(ctx, l);
1405 if (dn >= 0 && --indeg[
static_cast<std::size_t
>(dn)] == 0)
1409 for (
int n = 0; n < nn; ++n)
1410 if (!seen[
static_cast<std::size_t
>(n)])
1414 SegmentStore store_;
1415 RwptDispersion rwpt_;
1416 std::vector<double> release_vol_;
1417 std::uint64_t substep_counter_ = 0;
1418 std::vector<int> topo_;
1419 std::vector<std::vector<int>> node_out_links_;
1420 std::vector<std::int8_t> flow_sign_;
1421 std::vector<double> node_mass_in_;
1422 std::vector<double> node_vol_in_;
1423 std::vector<double> scratch_;
1427 std::vector<double> treat_cin_;
1428 std::vector<double> msx_block_;
1429 std::vector<double> msx_poll_;
1430 bool initialized_ =
false;
Plan H6b — bed conduction, deep-ground conduction, and hyporheic exchange, integrated SIMULTANEOUSLY ...
Versioned archives of completed source-boundary state.
Plan D-H5e — the single node/link surface-flux binding.
Plan PE — resolve per-element attributes, and read them.
[INITIAL_QUALITY] reserved-species (WATER_AGE/__TEMPERATURE__) helpers for the per-engine seed sites ...
D-NS1: one clamp-bookkeeping seam for all three quality engines.
Node hydraulics — volume/depth/head conversions, surface area, overflow.
Water quality routing — constituent transport, mixing, decay.
Geometric quantities required by reaction components.
Phase R4 — reaction binding for the LEGACY (CSTR) quality engine.
Resolution and application of species reaction source terms.
X3b: RWPT longitudinal dispersion on LARD segments.
LARD segment slabs — per-link ring buffers of plug-flow segments.
The central, reentrant simulation context for the new engine.
Solar radiation inputs and calculations for thermal components.
The Domain × Species-class transport contract (OPT plan §5, E2).
Global unit conversion factors — matching legacy SWMM Ucf[]/Qcf[].
const std::string & name_of(int idx) const
Borrow a registered name by its ordered index.
Definition NameIndex.hpp:186
Definition CompletedSourceArchive.hpp:25
const std::string & bytes() const
Borrow the complete owned byte buffer.
Definition CompletedSourceArchive.hpp:51
Definition LagrangianSolver.hpp:195
void publish(SimulationContext &ctx, double dt_routing)
Per-routing-step publication: link means + the conc_old convention. dt_routing ages the held state of...
Definition LagrangianSolver.hpp:882
std::string applyThermalIncrements(SimulationContext &ctx, const std::vector< ChannelThermalIncrement > &updates)
Add caller channel temperature increments uniformly to represented segments then republish.
Definition LagrangianSolver.hpp:383
void step(SimulationContext &ctx, double dt_routing)
One routing step of LTD transport. Lazily initializes on the first call (needs router-set volumes,...
Definition LagrangianSolver.hpp:419
void restoreCheckpointState(const std::string &bytes, SimulationContext &ctx)
Restore solver-owned completed-source state against a matching prepared context.
Definition LagrangianSolver.hpp:227
bool initialized() const noexcept
Read whether this segment owner completed initialization.
Definition LagrangianSolver.hpp:241
std::vector< ChannelThermalRegion > thermalRegions(const SimulationContext &ctx) const
Export one mean-temperature channel region per wet represented link.
Definition LagrangianSolver.hpp:253
std::string applySpeciesIncrements(SimulationContext &ctx, const std::vector< ChannelSpeciesIncrement > &updates)
Apply caller concentration deltas to represented nonthermal channel rows then republish.
Definition LagrangianSolver.hpp:325
void substep(SimulationContext &ctx, double dt, double frac)
One LTD substep: AGE → DRAIN → MIX(+passthrough) → RELEASE → DECAY.
Definition LagrangianSolver.hpp:498
std::vector< ChannelSpeciesRegion > speciesRegions(const SimulationContext &ctx) const
Export mean carried-species tuples for each wet represented channel link.
Definition LagrangianSolver.hpp:279
std::string checkpointState() const
Export a validated completed-source snapshot of solver-owned segment/particle/topology state.
Definition LagrangianSolver.hpp:207
void checkpoint(detail::CompletedSourceArchive &a)
Serialize/restore fixed particle state then validate position/ordinate values.
Definition RwptDispersion.hpp:255
void checkpoint(detail::CompletedSourceArchive &a)
Serialize or restore fixed-sized segment ring arrays and validate restored numerical state.
Definition SegmentStore.hpp:82
Definition LagrangianSolver.hpp:125
double rwpt_hyd_radius(double area, double depth, double diam, bool circular)
Hydraulic radius: exact for CIRCULAR from flow area, ≈ depth otherwise (wide-channel v1 approximation...
Definition RwptDispersion.hpp:226
constexpr double kTinyFlow
cfs; below this a link moves nothing
Definition LagrangianSolver.hpp:127
SpeciesRowLayout rowLayout(const SimulationContext &ctx)
The single source of truth for the row layout. Reserved rows are appended after the pollutants in a f...
Definition LagrangianSolver.hpp:169
double getSurfArea(const NodeData &nodes, int idx, double depth, TableData *tables, int unit_sys, const NodeSubtypes *subs)
Evaluate the raw derivative of storage volume with depth.
Definition Node.cpp:335
double storeVolume(const NodeData &nodes, int idx, double depth, double volume)
Evaluate a transport store using an explicit depth and booked volume.
Definition Node.cpp:460
constexpr double ZERO_VOLUME
1 liter in ft3
Definition QualityRouting.hpp:58
bool nodeIsReactor(const SimulationContext &ctx, int node)
Select storage or retained-water node mixing.
Definition QualityRouting.cpp:145
void bookNegativeAgeClamp(SimulationContext &ctx, int node)
Age-row clamp: counted, not ledgered (no age row until A2c) and not warned per-clamp (see bookNegativ...
Definition NegativeSources.hpp:99
void bookNegativeSourceClamp(SimulationContext &ctx, int node, int p, double shortfall)
Book one pollutant-row clamp: count it and un-book the shortfall from the external-extraction ledger ...
Definition NegativeSources.hpp:71
void updateUntreatedStorageHrt(SimulationContext &ctx, double dt)
Advance residence time only for untreated storage nodes under active native reactions.
Definition QualityRouting.cpp:204
void applyNodeTreatment(SimulationContext &ctx, int j, double dt, double q_raw, const double *cin)
Resolve one node's compiled co-treatment laws and book accepted pollutant removal.
Definition QualityRouting.cpp:2078
void fillChannelThermalGeometry(const SimulationContext &ctx, ChannelThermalRegion ®ion) noexcept
Publish native conduit thermal region geometry in SI.
Definition BedExchange.cpp:388
bool bedExchangeEnabled(const SimulationContext &ctx) noexcept
Test current native finite-bed heat family process enable.
Definition BedExchange.cpp:251
ClassEnables network1DEnables(const SimulationContext &ctx) noexcept
Resolve allocation-free native network class counts from current project policy.
Definition TransportPolicy.cpp:133
void ensureMsxState(SimulationContext &ctx)
Lazily size + seed the MSX element state; warn once about the R4b transport limitation when any RATE ...
Definition ReactionLegacyBinding.cpp:95
void applyInitialTempOverrides(SimulationContext &ctx)
Apply TEMPERATURE rows onto heat_state.node_temp/link_temp (degC). No-op on unsized arrays.
Definition InitialQualitySeeds.hpp:147
void applyInitialAgeOverrides(SimulationContext &ctx)
Apply WATER_AGE rows onto water_age_state.node_age/link_age (hours -> seconds). No-op when a hotstart...
Definition InitialQualitySeeds.hpp:118
bool legacyReactionsActive(const SimulationContext &ctx)
Test configured and compiled native reaction readiness.
Definition ReactionLegacyBinding.cpp:413
double reactionBoosterMass(SimulationContext &ctx, int node, int species, double mass, double mixed_volume, double arriving_volume)
Compute and book the first matching node/species booster inventory addition.
Definition ReactionSources.hpp:60
void reactSpeciesBlock(SimulationContext &ctx, bool tank, double dt, double *species_block, const double *pollut, double hrt_seconds, double temp_c, int element)
React one LARD-owned tuple with held HRT/temperature and optional element geometry.
Definition ReactionLegacyBinding.cpp:377
int getUnitSystem(int flow_units)
Determine unit system (0=US, 1=SI) from flow units.
Definition UnitConversion.cpp:33
Definition ExchangeLaw.hpp:60
@ CONDUIT
Definition LinkData.hpp:58
@ OUTFALL
Definition NodeData.hpp:65
@ INITIAL_STATE
water in the network at t = 0
Definition WaterAgeData.hpp:59
@ CIRCULAR
Definition LinkData.hpp:65
@ INITIAL_STATE
water in the network at t = 0
Definition HeatData.hpp:79
Definition ChannelThermalData.hpp:32
int link
Definition ChannelThermalData.hpp:33
std::vector< std::string > row_names
Definition ChannelThermalData.hpp:35
std::vector< double > concentration
Definition ChannelThermalData.hpp:36
int region
Definition ChannelThermalData.hpp:33
double volume
Definition ChannelThermalData.hpp:34
static HeatElement link(int i)
Construct a LINK heat-element descriptor.
Definition HeatOverrideData.hpp:112
static HeatElement node(int i)
Construct a NODE heat-element descriptor.
Definition HeatOverrideData.hpp:105
std::vector< double > node_temp
[node], °C
Definition HeatData.hpp:438
ConduitData conduits
Definition LinkSubtypes.hpp:682
int conduit_row(int i) const noexcept
Resolve a base link key into the conduit side table.
Definition LinkSubtypes.hpp:895
std::vector< double > k_decay
First-order decay coefficient (1/sec). The [POLLUTANTS] Kdecay column is 1/day: the parser divides by...
Definition PollutantData.hpp:96
std::vector< std::string > species_name
cold
Definition ReactionData.hpp:103
std::vector< double > msx_ext_mass_in
Definition ReactionData.hpp:300
std::vector< double > msx_node_conc
Definition ReactionData.hpp:289
std::vector< double > msx_link_conc
Definition ReactionData.hpp:290
std::vector< double > qual_routing_reacted
Quality mass lost to decay.
Definition SimulationContext.hpp:1324
Central, reentrant simulation context.
Definition SimulationContext.hpp:390
struct openswmm::SimulationContext::MassBalance mass_balance
LinkSubtypes link_subtypes
Relational per-subtype link side-tables (Phase 6) — the link analogue of node_subtypes....
Definition SimulationContext.hpp:588
ReactionData reactions
Multispecies reaction system (EPANET-MSX conventions), parsed from the reactions component's config f...
Definition SimulationContext.hpp:624
int n_nodes() const noexcept
Number of nodes.
Definition SimulationContext.hpp:2357
NodeData nodes
All node state and properties.
Definition SimulationContext.hpp:563
WaterAgeState water_age_state
Definition SimulationContext.hpp:640
TreatmentData treatment
Definition SimulationContext.hpp:764
LinkData links
All link state and properties.
Definition SimulationContext.hpp:580
int n_links() const noexcept
Number of links.
Definition SimulationContext.hpp:2360
HeatState heat_state
Definition SimulationContext.hpp:656
NameIndex pollutant_names
Pollutant name → pollutant index.
Definition SimulationContext.hpp:748
SimulationOptions options
Parsed simulation options (from [OPTIONS] section).
Definition SimulationContext.hpp:452
PollutantData pollutants
Pollutant definitions and per-object quality state.
Definition SimulationContext.hpp:606
bool outfall_backflow_zero
[OPTIONS] OUTFALL_BACKFLOW_QUALITY LAST|ZERO — quality carried by reverse flow at outfalls (false = L...
Definition SimulationOptions.hpp:338
int rwpt_seed
[OPTIONS] RWPT_SEED — deterministic counter-RNG seed (D-L6). Same seed ⇒ bit-identical runs at any th...
Definition SimulationOptions.hpp:306
bool lard_rwpt
[OPTIONS] DISPERSION RWPT|OFF — LARD RWPT dispersion (X3b; strategy §5; the GUI plan's Lagrangian-gro...
Definition SimulationOptions.hpp:300
double quality_step
[OPTIONS] QUALITY_STEP — transport substep, seconds (HH:MM:SS or seconds; 0 = follow ROUTING_STEP).
Definition SimulationOptions.hpp:280
std::vector< bool > has_treatment
Per-node flag: true if any pollutant has a treatment expression.
Definition QualityData.hpp:208
The segment store's species-row layout, computed in ONE place.
Definition LagrangianSolver.hpp:149
int np
pollutant rows occupy [0, np)
Definition LagrangianSolver.hpp:150
int temp_row
temperature row index, or -1 (H7b)
Definition LagrangianSolver.hpp:152
int ns
total rows the store carries
Definition LagrangianSolver.hpp:155
int age_row
water-age row index, or -1 when absent
Definition LagrangianSolver.hpp:151
int msx_first
Definition LagrangianSolver.hpp:153
Allocation-free class enables for one domain — what the engines size from. n_msx is the reactions com...
Definition TransportPolicy.hpp:110
bool temperature
Definition TransportPolicy.hpp:114
bool age
Definition TransportPolicy.hpp:113
int n_pollut
Definition TransportPolicy.hpp:111
int n_msx
Definition TransportPolicy.hpp:112
Phase H2 — latent and sensible heat exchange at the water surface (heat plan §2.1; CSHComponent §4....