77#ifndef OPENSWMM_QUALITY_LARD_RWPT_DISPERSION_HPP
78#define OPENSWMM_QUALITY_LARD_RWPT_DISPERSION_HPP
94constexpr double kDm = 1.3454e-8;
102 z += 0x9e3779b97f4a7c15ULL;
103 z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
104 z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
105 return z ^ (z >> 31);
109 std::uint64_t stepc, std::uint64_t particle,
110 std::uint64_t draw) {
115 return (
static_cast<double>(k >> 11) + 0.5) * (1.0 / 9007199254740992.0);
119 std::uint64_t stepc, std::uint64_t particle,
120 std::uint64_t draw) {
123 return std::sqrt(-2.0 * std::log(u1)) *
124 std::cos(6.283185307179586 * u2);
132inline double rwpt_u_dev(
double eta,
double ubar,
double ustar,
138 return (ustar /
kKappa) * (1.0 + std::log(std::max(eta,
kEtaMin)));
139 return 1.5 * ubar * eta * (2.0 - eta) - ubar;
143inline double rwpt_d_eta(
double eta,
double h,
double ustar,
bool turbulent) {
144 if (!turbulent)
return kDm / (h * h);
145 const double e = std::min(std::max(eta, 0.0), 1.0);
146 return kKappa * ustar * e * (1.0 - e) / h;
150 if (!turbulent)
return 0.0;
151 return kKappa * ustar * (1.0 - 2.0 * eta) / h;
158 if (!circular || diam <= 0.0 || depth <= 0.0)
return std::max(depth, 0.0);
160 std::min(std::max(1.0 - 2.0 * depth / diam, -1.0), 1.0);
161 const double theta = 2.0 * std::acos(ratio);
162 if (theta <= 1.0e-6)
return std::max(depth, 0.0);
163 const double a = diam * diam / 8.0 * (theta - std::sin(theta));
164 const double p = diam * theta / 2.0;
166 return (p > 0.0) ? a / p : std::max(depth, 0.0);
177 const std::size_t total =
178 static_cast<std::size_t
>(nl_) *
static_cast<std::size_t
>(npart_);
179 zeta_.assign(total, -1.0);
180 eta_.assign(total, 0.5);
181 seg_idx_.assign(total, 0);
193 double ubar,
double h,
double rh,
double n_manning,
194 double v_in,
double q_dt,
double dt, std::uint64_t stepc,
195 std::uint64_t seed, std::vector<double>& delta) {
197 const int nseg = segs.
count(l);
198 if (nseg < 2 || h <= 1.0e-3 || ubar <= 0.0)
return;
200 if (vol <= 0.0)
return;
204 const double sf = (n_manning * ubar) * (n_manning * ubar) /
205 (2.208 * std::pow(std::max(rh, 1.0e-3), 4.0 / 3.0));
207 std::sqrt(32.174 * std::max(rh, 1.0e-3) * sf);
211 bounds_.assign(
static_cast<std::size_t
>(nseg) + 1, 0.0);
212 for (
int j = 0; j < nseg; ++j)
213 bounds_[
static_cast<std::size_t
>(j) + 1] =
214 bounds_[
static_cast<std::size_t
>(j)] + segs.
seg_volume(l, j);
216 const std::size_t base =
static_cast<std::size_t
>(l) *
217 static_cast<std::size_t
>(npart_);
218 for (
int p = 0; p < npart_; ++p) {
219 const std::size_t ip = base +
static_cast<std::size_t
>(p);
220 double z = zeta_[ip];
226 z = vol * (
static_cast<double>(p) + 0.5) /
227 static_cast<double>(npart_);
228 e =
rwpt_uniform(seed,
static_cast<std::uint64_t
>(l), stepc,
229 static_cast<std::uint64_t
>(p), 7);
235 seed,
static_cast<std::uint64_t
>(l),
237 static_cast<std::uint64_t
>(p), 8));
239 stepc,
static_cast<std::uint64_t
>(p), 9);
243 const int j_from = locate(z, nseg);
251 (
rwpt_u_dev(e, ubar, ustar, turbulent) / ubar) * q_dt;
255 const double d_eta =
rwpt_d_eta(e, h, ustar, turbulent);
258 rwpt_normal(seed,
static_cast<std::uint64_t
>(l), stepc,
259 static_cast<std::uint64_t
>(p), 3) *
260 std::sqrt(std::max(2.0 * d_eta * dt, 0.0));
261 while (e < 0.0 || e > 1.0) {
263 if (e > 1.0) e = 2.0 - e;
269 while (z < 0.0 || z > vol) {
271 if (z > vol) z = 2.0 * vol - z;
274 const int j_to = locate(z, nseg);
279 if (j_to != j_from) {
280 const double vshare = vol /
static_cast<double>(npart_);
282 const int stepdir = (j_to > j_from) ? 1 : -1;
284 const int jn = j + stepdir;
294 bounds_[
static_cast<std::size_t
>(std::max(j, jn))];
295 const double pen = std::min(std::abs(z - bnd), vshare);
296 for (
int s = 0; s < ns; ++s)
297 delta[
static_cast<std::size_t
>(s)] =
299 exchange(segs, l, j, jn, delta.data(), ns);
315 int locate(
double z,
int nseg)
const {
317 while (j + 1 < nseg &&
318 z >= bounds_[
static_cast<std::size_t
>(j) + 1])
330 void exchange(SegmentStore& segs,
int l,
int j,
int jn,
331 const double* delta,
int ns) {
332 for (
int s = 0; s < ns; ++s) {
333 const double vj = segs.seg_volume(l, j);
334 const double vn = segs.seg_volume(l, jn);
335 if (vj <= 0.0 || vn <= 0.0)
continue;
336 const double cj = segs.seg_conc(l, j, s);
337 const double cn = segs.seg_conc(l, jn, s);
339 std::min({delta[s], cj * vj,
340 std::max(0.0, (cj - cn)) * vn});
341 if (take <= 0.0)
continue;
342 segs.set_seg_conc(l, j, s, (cj * vj - take) / vj);
343 segs.set_seg_conc(l, jn, s, (cn * vn + take) / vn);
347 int nl_ = 0, npart_ = 0;
348 std::vector<double> zeta_, eta_;
349 std::vector<int> seg_idx_;
350 std::vector<double> bounds_;
LARD segment slabs — per-link ring buffers of plug-flow segments.
The central, reentrant simulation context for the new engine.
Persistent particle field + the per-substep exchange operator.
Definition RwptDispersion.hpp:172
void disperse(SimulationContext &ctx, SegmentStore &segs, int l, double ubar, double h, double rh, double n_manning, double v_in, double q_dt, double dt, std::uint64_t stepc, std::uint64_t seed, std::vector< double > &delta)
One substep of dispersive exchange on link l's segments.
Definition RwptDispersion.hpp:192
void resize(int n_links, int n_per_link=kRwptParticlesPerLink)
Definition RwptDispersion.hpp:174
Definition SegmentStore.hpp:66
double seg_conc(int l, int i, int s) const
Definition SegmentStore.hpp:91
int count(int l) const
Definition SegmentStore.hpp:80
double total_volume(int l) const
Definition SegmentStore.hpp:84
double seg_volume(int l, int i) const
Definition SegmentStore.hpp:90
int species() const
Definition SegmentStore.hpp:81
Definition LagrangianSolver.hpp:122
constexpr double kKappa
von Kármán
Definition RwptDispersion.hpp:92
constexpr int kRwptParticlesPerLink
Definition RwptDispersion.hpp:91
double rwpt_d_eta_grad(double eta, double h, double ustar, bool turbulent)
Definition RwptDispersion.hpp:148
std::uint64_t rwpt_hash(std::uint64_t z)
Definition RwptDispersion.hpp:101
double rwpt_hyd_radius(double area, double depth, double diam, bool circular)
Definition RwptDispersion.hpp:156
constexpr double kReTurb
Definition RwptDispersion.hpp:96
constexpr double kDm
molecular diffusivity, ft²/s
Definition RwptDispersion.hpp:94
double rwpt_normal(std::uint64_t seed, std::uint64_t link, std::uint64_t stepc, std::uint64_t particle, std::uint64_t draw)
Standard normal (Box–Muller on two keyed uniforms).
Definition RwptDispersion.hpp:118
double rwpt_d_eta(double eta, double h, double ustar, bool turbulent)
Definition RwptDispersion.hpp:143
constexpr double kEtaMin
log-law floor
Definition RwptDispersion.hpp:95
double rwpt_uniform(std::uint64_t seed, std::uint64_t link, std::uint64_t stepc, std::uint64_t particle, std::uint64_t draw)
Uniform in (0,1), keyed. draw distinguishes multiple draws per particle.
Definition RwptDispersion.hpp:108
constexpr double kNuWater
kinematic viscosity, ft²/s
Definition RwptDispersion.hpp:93
double rwpt_u_dev(double eta, double ubar, double ustar, bool turbulent)
Velocity DEVIATION u(η) − ū. Turbulent: log-law; laminar: parabola.
Definition RwptDispersion.hpp:132
Definition NodeCoupling.cpp:16
Central, reentrant simulation context.
Definition SimulationContext.hpp:353