5#ifndef LINE_SOLVERS_FLUID_FLUID_CLOSURES_H
6#define LINE_SOLVERS_FLUID_FLUID_CLOSURES_H
61inline double closure_normcdf(
double z) {
return 0.5 * std::erfc(-z / std::sqrt(2.0)); }
65 return std::exp(-0.5 * z * z) / std::sqrt(2.0 * 3.14159265358979323846);
84 double cov_nc = 0.0) {
86 double th2 = s2 - 2.0 * cov_nc + vc;
87 if (th2 < 0.0) th2 = 0.0;
88 if (!(th2 > 0.0) || std::isinf(c)) {
101 const double th = std::sqrt(th2);
102 const double d = (n - c) / th;
105 r.
h = n * (1.0 - Phid) + c * Phid - th * phid;
119 const double hi = std::min(n, c);
120 const bool at_lo = r.
h < 0.0;
121 const bool at_hi = r.
h > hi;
122 r.
h = std::min(std::max(r.
h, 0.0), hi);
126 r.
dh = (n < c) ? 1.0 : 0.0;
128 if (at_lo || at_hi) r.
d2h = 0.0;
144 if (lldrow.empty()) {
149 const std::size_t L = lldrow.size();
154 if (n >=
static_cast<double>(L)) {
158 const std::size_t k =
static_cast<std::size_t
>(std::floor(n));
159 const double frac = n -
static_cast<double>(k);
160 r.
h = lldrow[k - 1] * (1.0 - frac) + lldrow[k] * frac;
161 r.
dh = lldrow[k] - lldrow[k - 1];
169 double A = 0.0, B = 0.0, C = 0.0;
180inline PsiSegment psi_segment(
double p,
double q,
double c,
const std::vector<double>& lldrow,
182 const std::size_t L = lldrow.size();
183 double a0 = 0.0, a1 = 0.0;
184 if (p >=
static_cast<double>(L)) {
186 }
else if (p < 1.0) {
189 const std::size_t k =
static_cast<std::size_t
>(std::floor(p));
190 a1 = lldrow[k] - lldrow[k - 1];
191 a0 = lldrow[k - 1] - a1 *
static_cast<double>(k);
194 if (is_inf || !std::isfinite(c) || q <= c) {
208 double M0 = 0.0, M1 = 0.0, M2 = 0.0;
211inline TruncMoments trunc_moments(
double p,
double q,
double n,
double s) {
212 const double zp = (p - n) / s;
215 const bool qinf = std::isinf(q);
220 m.M1 = n * m.M0 + s * (pp - pq);
221 m.M2 = qinf ? (n * n + s * s) * m.M0 + s * ((p + n) * pp)
222 : (n * n + s * s) * m.M0 + s * ((p + n) * pp - (q + n) * pq);
227inline ClosureValue psi_point(
double u,
double c,
const std::vector<double>& lldrow,
bool is_inf) {
229 const double base = is_inf ? u : std::min(u, c);
230 const double dbase = is_inf ? 1.0 : ((u < c) ? 1.0 : 0.0);
233 r.dh = dbase * a.h + base * a.dh;
238 r.d2h = 2.0 * dbase * a.dh;
268 const std::vector<double>& lldrow,
bool is_inf) {
269 if (lldrow.empty()) {
285 const std::size_t L = lldrow.size();
286 if (!(s2 > 0.0))
return detail::psi_point(n, c, lldrow, is_inf);
288 const double s = std::sqrt(s2);
291 std::vector<double> bps;
292 for (std::size_t k = 0; k <= L; ++k) bps.push_back(
static_cast<double>(k));
293 if (!is_inf && std::isfinite(c) && c >
static_cast<double>(L)) bps.push_back(c);
294 std::sort(bps.begin(), bps.end());
295 bps.erase(std::unique(bps.begin(), bps.end()), bps.end());
300 double Bprev = 0.0, Cprev = 0.0;
301 for (std::size_t k = 0; k < bps.size(); ++k) {
302 const double p = bps[k];
303 const double q = (k + 1 < bps.size()) ? bps[k + 1] : std::numeric_limits<double>::infinity();
304 const detail::PsiSegment g = detail::psi_segment(p, q, c, lldrow, is_inf);
305 const detail::TruncMoments mo = detail::trunc_moments(p, q, n, s);
306 r.
h += g.A * mo.M0 + g.B * mo.M1 + g.C * mo.M2;
307 r.
dh += g.B * mo.M0 + 2.0 * g.C * mo.M1;
308 r.
d2h += 2.0 * g.C * mo.M0;
311 const double jump = (g.B + 2.0 * g.C * p) - (Bprev + 2.0 * Cprev * p);
346 for (std::size_t j = 0; j < r.size() && inside; ++j) {
347 if (!(r[j] >= -zt)) inside =
false;
348 if (capped && !(r[j] <= xb[j] + zt)) inside =
false;
351 for (std::size_t j = 0; j < r.size(); ++j) {
352 r[j] = std::max(r[j], 0.0);
353 if (capped) r[j] = std::min(r[j], xb[j]);
355 for (std::size_t it = 0; it <= r.size(); ++it) {
357 for (std::size_t j = 0; j < r.size(); ++j)
sum += r[j];
358 const double d = tot -
sum;
359 if (std::fabs(d) <= zt)
break;
360 std::vector<double> slack(r.size(), 0.0);
361 for (std::size_t j = 0; j < r.size(); ++j)
362 slack[j] = (d > 0.0) ? (capped ? (xb[j] - r[j]) : 1.0) : r[j];
364 for (std::size_t j = 0; j < slack.size(); ++j) tsl += slack[j];
365 if (tsl <= zt)
break;
366 for (std::size_t j = 0; j < r.size(); ++j) {
367 r[j] = std::max(r[j] + d * slack[j] / tsl, 0.0);
368 if (capped) r[j] = std::min(r[j], xb[j]);
375 std::vector<double>
s;
377 std::vector<double>
cn;
406 const double lo = 1.0, hi = 4.0;
411 }
else if (ratio >= hi) {
415 const double t = (ratio - lo) / (hi - lo);
416 w.
tau = 1.0 - t * t * (3.0 - 2.0 * t);
417 w.
dtau = -6.0 * t * (1.0 - t) / (hi - lo);
438 bool want_cov =
false) {
439 const std::size_t n = x.size();
441 out.
s.assign(n, 0.0);
444 out.
cn.assign(n, 0.0);
448 std::vector<double> u(n, 0.0);
450 for (std::size_t j = 0; j < n; ++j) {
454 if (v <= 0.0)
return out;
456 for (std::size_t j = 0; j < n; ++j) out.
s[j] = u[j] / v;
458 for (std::size_t j = 0; j < n; ++j)
459 for (std::size_t m = 0; m < n; ++m)
460 J(j, m) = (j == m ? wv[j] / v : 0.0) - (u[j] / (v * v)) * wv[m];
462 if (want_jac) plugin_jac(out.
ds);
464 bool have_cov = C.
rows() == n && C.
cols() == n;
467 for (std::size_t a = 0; a < n && !any; ++a)
468 for (std::size_t b = 0; b < n && !any; ++b)
469 if (C(a, b) != 0.0) any =
true;
472 if (!have_cov)
return out;
474 std::vector<double> cuv(n, 0.0);
476 for (std::size_t j = 0; j < n; ++j) {
478 for (std::size_t b = 0; b < n; ++b) acc += C(j, b) * wv[b];
479 cuv[j] = wv[j] * acc;
486 if (tw.
tau <= 0.0 && !want_jac)
return out;
487 const double tau = tw.
tau;
488 const double dtau_scale = tw.
dtau * (-2.0 * cvv / (v * v * v));
494 std::vector<double> an(n, 0.0);
496 for (std::size_t j = 0; j < n; ++j) {
498 for (std::size_t b = 0; b < n; ++b) acc += C(j, b);
502 std::vector<double> cn0(n, 0.0);
503 for (std::size_t j = 0; j < n; ++j) cn0[j] = wv[j] * an[j] / v - u[j] * (cvn / (v * v));
504 for (std::size_t j = 0; j < n; ++j) out.
cn[j] = tau * cn0[j];
506 for (std::size_t j = 0; j < n; ++j)
507 for (std::size_t m = 0; m < n; ++m)
509 tau * (-(wv[j] * an[j]) * (wv[m] / (v * v)) -
510 (j == m ? (cvn / (v * v)) * wv[j] : 0.0) +
511 (2.0 * cvn / (v * v * v)) * u[j] * wv[m]) +
512 cn0[j] * dtau_scale * wv[m];
515 std::vector<double> scorr(n, 0.0);
516 for (std::size_t j = 0; j < n; ++j)
517 scorr[j] = -cuv[j] / (v * v) + (u[j] * cvv) / (v * v * v);
518 for (std::size_t j = 0; j < n; ++j) out.
s[j] += tau * scorr[j];
520 for (std::size_t j = 0; j < n; ++j)
521 for (std::size_t m = 0; m < n; ++m)
522 out.
ds(j, m) += tau * ((2.0 / (v * v * v)) * cuv[j] * wv[m] +
523 (j == m ? (cvv / (v * v * v)) * wv[j] : 0.0) -
524 (3.0 * cvv / (v * v * v * v)) * u[j] * wv[m]) +
525 scorr[j] * dtau_scale * wv[m];
535 bool all_nonneg =
true;
536 for (std::size_t j = 0; j < n; ++j)
539 for (std::size_t j = 0; j < n; ++j)
540 if (out.
s[j] < 0.0) out.
s[j] = 0.0;
547 std::vector<bool> act(n,
false);
549 bool any_act =
false;
550 for (std::size_t j = 0; j < n; ++j) {
551 act[j] = out.
s[j] > 0.0;
558 for (std::size_t j = 0; j < n; ++j) out.
s[j] = u[j] / v;
559 if (want_jac) plugin_jac(out.
ds);
562 std::vector<double> snew(n, 0.0);
564 std::vector<double> dT(n, 0.0);
565 for (std::size_t m = 0; m < n; ++m)
566 for (std::size_t j = 0; j < n; ++j)
567 if (act[j]) dT[m] += out.
ds(j, m);
569 for (std::size_t j = 0; j < n; ++j) {
570 if (!act[j])
continue;
571 for (std::size_t m = 0; m < n; ++m)
572 dsnew(j, m) = out.
ds(j, m) / tot - (out.
s[j] / (tot * tot)) * dT[m];
576 for (std::size_t j = 0; j < n; ++j)
577 if (act[j]) snew[j] = out.
s[j] / tot;
601 const std::vector<double>& vk,
bool want_jac) {
602 const std::size_t K = xk.size();
604 out.
s.assign(K, 0.0);
611 "fluid_gps_share: GPS closes its capacity share by enumerating the 2^K backlog "
612 "patterns of a station, and this station carries " +
614 " classes. Above 12 the enumeration is no longer tractable; use method 'closing' with a "
615 "DPS station instead");
618 for (std::size_t k = 0; k < K; ++k) sw += wk_in[k];
619 if (sw <= 0.0)
return out;
620 std::vector<double> wk(K, 0.0);
621 for (std::size_t k = 0; k < K; ++k) wk[k] = wk_in[k] / sw;
629 std::vector<double> p(K, 0.0), dp(K, 0.0);
630 for (std::size_t k = 0; k < K; ++k) {
632 const double sd = std::sqrt(vk[k]);
633 const double z = (xk[k] - 0.5) / sd;
637 p[k] = (xk[k] > 0.0) ? 1.0 : 0.0;
643 const unsigned long long masks = 1ULL << K;
644 for (
unsigned long long mask = 1; mask < masks; ++mask) {
646 for (std::size_t k = 0; k < K; ++k)
647 if (mask & (1ULL << k)) W += wk[k];
648 if (W <= 0.0)
continue;
649 std::vector<double> q(K, 0.0);
651 for (std::size_t k = 0; k < K; ++k) {
652 q[k] = (mask & (1ULL << k)) ? p[k] : (1.0 - p[k]);
655 for (std::size_t k = 0; k < K; ++k)
656 if (mask & (1ULL << k)) out.
s[k] += prodq * wk[k] / W;
657 if (!want_jac)
continue;
658 for (std::size_t m = 0; m < K; ++m) {
660 for (std::size_t j = 0; j < K; ++j)
661 if (j != m) prodm *= q[j];
662 const double sgn = (mask & (1ULL << m)) ? 1.0 : -1.0;
663 for (std::size_t k = 0; k < K; ++k)
664 if (mask & (1ULL << k)) dsdp(k, m) += sgn * prodm * wk[k] / W;
669 for (std::size_t k = 0; k < K; ++k)
670 for (std::size_t m = 0; m < K; ++m) out.
ds(k, m) = dsdp(k, m) * dp[m];
UnsupportedError(const std::string &what)
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense matrix and non-owning view.
ClosureValue fluid_lld_scaling(const std::vector< double > &lldrow, double n)
Port of fluid_lld_scaling.m: the limited load-dependent multiplier alpha at a CONTINUOUS population,...
ClosureValue fluid_capacity_closure(double n, double c, double s2, const std::vector< double > &lldrow, bool is_inf)
Port of fluid_capacity_closure.m: E[psi(X)] and its derivative, where psi(n) = min(n,...
ClosureValue fluid_min_closure(double n, double c, double s2, double vc=0.0, double cov_nc=0.0)
Port of fluid_min_closure.m: E[min(X,Y)] for jointly normal X, Y, and its derivative with respect to ...
ShareValue fluid_share_closure(const std::vector< double > &x, const std::vector< double > &wv, const Matrix< double > &C, bool want_jac, bool want_cov=false)
Port of fluid_share_closure.m: E[w_j X_j / sum_m w_m X_m] by the delta method, and its Jacobian at fi...
ExpansionWeight fluid_expansion_weight(double ratio)
Port of local_expansion_weight in fluid_share_closure.m.
ShareValue fluid_gps_share(const std::vector< double > &xk, const std::vector< double > &wk_in, const std::vector< double > &vk, bool want_jac)
Port of fluid_gps_share.m: the expected capacity share of a GPS station under a normal marginal,...
void fluid_project_rate(std::vector< double > &r, const std::vector< double > &xb, bool capped, double tot)
Port of local_project_rate in ode_rates_closing_factors.m: project a jointly closed per-coordinate se...
double closure_normpdf(double z)
The standard normal pdf.
double closure_normcdf(double z)
The standard normal cdf, without a statistics library.
A closure's value and its first two derivatives with respect to the first mean.
How much of the second-order correction the series admits, and d tau / d ratio.
A share closure's value and Jacobian, and the joint-closure covariance.
static constexpr double FineTol
static constexpr double Zero