5#ifndef LINE_SOLVERS_FLUID_FLUID_ODES_H
6#define LINE_SOLVERS_FLUID_FLUID_ODES_H
88 std::vector<std::vector<std::size_t>>
qidx;
89 std::vector<std::vector<std::size_t>>
kic;
125 std::vector<Matrix<double>>
cov;
128 for (std::size_t i = 0; i <
sigma2.size(); ++i)
129 if (
sigma2[i] > 0.0)
return true;
134 if (i >=
cov.size() ||
cov[i].rows() == 0)
return nullptr;
172 std::vector<double>& out) {
173 const std::size_t nr = B.
rows();
175 if (tg.empty() || B.
cols() == 0)
return;
176 if (tt <= tg.front()) {
177 for (std::size_t i = 0; i < nr; ++i) out[i] = B(i, 0);
180 if (tt >= tg.back()) {
181 for (std::size_t i = 0; i < nr; ++i) out[i] = B(i, B.
cols() - 1);
185 for (std::size_t k = 0; k < tg.size(); ++k)
186 if (tg[k] <= tt) j = k;
187 if (j + 1 >= tg.size()) {
188 for (std::size_t i = 0; i < nr; ++i) out[i] = B(i, j);
191 const double w = (tt - tg[j]) / (tg[j + 1] - tg[j]);
192 for (std::size_t i = 0; i < nr; ++i) out[i] = (1.0 - w) * B(i, j) + w * B(i, j + 1);
207 std::vector<lang::SchedStrategy>
sched;
216 std::vector<std::vector<double>>
lld;
228 if (
sn.disabled[i][r])
return false;
230 return d.
D0.rows() > 0 && d.
D0.rows() == d.
D0.cols();
241void fluid_mu_phi(
const lang::Distrib<T>& d, std::vector<double>& mu, std::vector<double>& phi) {
242 const std::size_t n = d.
D0.rows();
245 for (std::size_t k = 0; k < n; ++k) {
250 phi[k] = (d0kk == 0.0) ? 1.0 : rowD1 / (-d0kk);
256std::vector<double> fluid_pie(
const lang::Distrib<T>& d) {
257 const std::size_t n = d.D0.rows();
258 if (n == 0)
return std::vector<double>{1.0};
259 if (n == 1)
return std::vector<double>{1.0};
261 m.D0 = Matrix<double>(n, n, 0.0);
262 m.D1 = Matrix<double>(n, n, 0.0);
263 for (std::size_t a = 0; a < n; ++a)
264 for (std::size_t b = 0; b < n; ++b) {
265 m.D0(a, b) = num_traits<T>::to_double(d.D0(a, b));
266 m.D1(a, b) = num_traits<T>::to_double(d.D1(a, b));
283 const std::size_t M =
sn.nstations, K =
sn.nclasses;
285 L.
qidx.assign(M, std::vector<std::size_t>(K, 0));
286 L.
kic.assign(M, std::vector<std::size_t>(K, 0));
287 L.
enabled.assign(M, std::vector<bool>(K,
false));
288 std::size_t cursor = 0;
289 for (std::size_t i = 0; i < M; ++i)
290 for (std::size_t r = 0; r < K; ++r) {
291 L.
qidx[i][r] = cursor;
292 if (detail::fluid_service_defined(
sn, i, r)) {
294 L.
kic[i][r] =
sn.service[i][r].D0.rows();
296 cursor += L.
kic[i][r];
323 const std::size_t M =
sn.nstations, K =
sn.nclasses;
329 std::vector<std::vector<std::vector<double>>> mu(M, std::vector<std::vector<double>>(K));
330 std::vector<std::vector<std::vector<double>>> phi(M, std::vector<std::vector<double>>(K));
331 std::vector<std::vector<std::vector<double>>> pie(M, std::vector<std::vector<double>>(K));
332 for (std::size_t i = 0; i < M; ++i)
333 for (std::size_t r = 0; r < K; ++r) {
335 pie[i][r] = std::vector<double>{1.0};
338 detail::fluid_mu_phi(
sn.service[i][r], mu[i][r], phi[i][r]);
339 pie[i][r] = detail::fluid_pie(
sn.service[i][r]);
344 const std::size_t S =
sn.nof_stateful();
345 const bool have_rt =
sn.rt.rows() == S * K;
346 std::vector<std::size_t> keep_idx;
347 keep_idx.reserve(M * K);
348 for (std::size_t i = 0; i < M; ++i) {
349 const std::size_t isf =
sn.stateful_of_station(i + 1) - 1;
350 for (std::size_t c = 0; c < K; ++c) keep_idx.push_back(isf * K + c);
355 for (std::size_t a = 0; a < S * K; ++a)
356 for (std::size_t b = 0; b < S * K; ++b)
360 const auto route = [&](std::size_t i, std::size_t c, std::size_t j, std::size_t l) ->
double {
361 if (!have_rt)
return 0.0;
362 return rt_st(i * K + c, j * K + l);
366 for (std::size_t i = 0; i < M; ++i)
367 for (std::size_t c = 0; c < K; ++c) {
368 if (!L.
enabled[i][c])
continue;
369 for (std::size_t j = 0; j < M; ++j)
370 for (std::size_t l = 0; l < K; ++l) {
371 const double p = route(i, c, j, l);
372 if (!(p > 0.0))
continue;
373 for (std::size_t ki = 0; ki < L.
kic[i][c]; ++ki)
374 for (std::size_t kj = 0; kj < L.
kic[j][l]; ++kj) {
379 const double pj = kj < pie[j][l].size() ? pie[j][l][kj] : 0.0;
380 e.
rate_base = phi[i][c][ki] * mu[i][c][ki] * p * pj;
389 for (std::size_t i = 0; i < M; ++i)
390 for (std::size_t c = 0; c < K; ++c) {
391 if (!L.
enabled[i][c])
continue;
399 for (std::size_t ki = 0; ki < L.
kic[i][c]; ++ki)
400 for (std::size_t kp = 0; kp < L.
kic[i][c]; ++kp) {
401 if (kp == ki)
continue;
414 sys.
weight.assign(M, std::vector<double>(K, 1.0));
415 double closed_pop = 0.0;
416 for (std::size_t r = 0; r < K; ++r)
417 if (std::isfinite(
sn.classes[r].population)) closed_pop +=
sn.classes[r].population;
418 for (std::size_t i = 0; i < M; ++i) {
419 sys.
sched[i] =
sn.stations[i].sched;
420 const double c =
sn.stations[i].nservers;
423 sys.
nservers[i] = std::isfinite(c) ? c : closed_pop;
426 for (std::size_t r = 0; r < K && r <
sn.stations[i].schedparam.size(); ++r)
431 sys.
lld.assign(M, std::vector<double>());
432 for (std::size_t i = 0; i < M; ++i) {
433 const std::vector<T>& row =
sn.stations[i].lldscaling;
435 for (std::size_t k = 0; k < row.size(); ++k)
437 if (row.empty() || all_one)
continue;
438 sys.
lld[i].resize(row.size());
439 for (std::size_t k = 0; k < row.size(); ++k)
458 for (std::size_t e = 0; e < sys.
events.size(); ++e) {
459 D(sys.
events[e].minus, e) -= 1.0;
460 D(sys.
events[e].plus, e) += 1.0;
487 std::vector<double>& g) {
489 const std::size_t M = L.
qidx.size();
490 const std::size_t K = M ? L.
qidx[0].size() : 0;
492 const bool gaussian = cl.
gaussian();
494 for (std::size_t i = 0; i < M; ++i) {
495 const std::vector<double>& lld = sys.
lld[i];
498 const std::size_t blo = K ? L.
qidx[i][0] : 0;
499 const std::size_t bhi = K ? L.
qidx[i][K - 1] + L.
kic[i][K - 1] : 0;
500 switch (sys.
sched[i]) {
504 if (lld.empty())
break;
506 for (std::size_t p = blo; p < bhi; ++p) ni += x[p];
507 if (!(ni > 0.0))
break;
509 for (std::size_t p = blo; p < bhi; ++p) g[p] = x[p] / ni * h.
h;
515 for (std::size_t k = 0; k < K; ++k) {
516 if (!L.
enabled[i][k])
continue;
517 const std::size_t b = L.
qidx[i][k], n = L.
kic[i][k];
519 for (std::size_t p = 1; p < n; ++p) rest += x[b + p];
528 for (std::size_t p = blo; p < bhi; ++p) ni += x[p];
529 if ((gaussian || !lld.empty()) && ni > 0.0) {
533 for (std::size_t p = blo; p < bhi; ++p) g[p] = x[p] / ni * h.
h;
540 const std::size_t nb = bhi - blo;
541 std::vector<double> xb(x + blo, x + bhi), wv(nb, 1.0);
543 std::vector<double> rb(nb, 0.0);
544 for (std::size_t p = 0; p < nb; ++p)
545 rb[p] = sh.
s[p] * h.
h + h.
dh * sh.
cn[p];
547 for (std::size_t p = 0; p < nb; ++p) g[blo + p] = rb[p];
550 const double s = sys.
nservers[i] / ni;
551 for (std::size_t p = blo; p < bhi; ++p) g[p] = x[p] * s;
570 for (std::size_t k = 0; k < K; ++k) wsum += sys.
weight[i][k];
571 if (wsum <= 0.0)
break;
572 const std::size_t nb = bhi - blo;
573 std::vector<double> wv(nb, 0.0);
574 for (std::size_t k = 0; k < K; ++k) {
575 if (!L.
enabled[i][k])
continue;
576 const std::size_t b = L.
qidx[i][k] - blo;
577 for (std::size_t p = 0; p < L.
kic[i][k]; ++p)
578 wv[b + p] = sys.
weight[i][k] / wsum;
580 double xi = 0.0, wx = 0.0;
581 for (std::size_t p = 0; p < nb; ++p) {
583 wx += wv[p] * x[blo + p];
585 if (!(xi > 0.0) || !(wx > 0.0))
break;
588 const std::vector<double> xb(x + blo, x + bhi);
591 std::vector<double> rb(nb, 0.0);
592 for (std::size_t p = 0; p < nb; ++p)
593 rb[p] = sh.
s[p] * psi.
h + psi.
dh * sh.
cn[p];
595 for (std::size_t p = 0; p < nb; ++p) g[blo + p] = rb[p];
609 "ode_rates_closing_factors: multi-server GPS stations are not supported, as "
610 "in the reference: the backlog closure splits ONE server by weight");
611 std::vector<double> xk(K, 0.0), vk(K, 0.0), wk(K, 0.0);
612 for (std::size_t k = 0; k < K; ++k) {
614 if (!L.
enabled[i][k])
continue;
615 const std::size_t b = L.
qidx[i][k], n = L.
kic[i][k];
616 for (std::size_t p = 0; p < n; ++p) xk[k] += x[b + p];
617 if (Ci ==
nullptr)
continue;
619 for (std::size_t p = 0; p < n; ++p)
620 for (std::size_t q = 0; q < n; ++q)
621 v += (*Ci)(b - blo + p, b - blo + q);
622 vk[k] = std::max(0.0, v);
628 for (std::size_t p = blo; p < bhi; ++p) ni += x[p];
631 for (std::size_t k = 0; k < K; ++k) {
632 if (!L.
enabled[i][k] || !(xk[k] > 0.0))
continue;
633 const std::size_t b = L.
qidx[i][k], n = L.
kic[i][k];
634 for (std::size_t p = 0; p < n; ++p)
635 g[b + p] = x[b + p] / xk[k] * sk.
s[k] * a;
659 return [sys, n](
double t,
const double* x,
double* dx) {
660 std::vector<double> g(x, x + n);
662 for (std::size_t i = 0; i < n; ++i) dx[i] = 0.0;
670 if (r == 0.0)
continue;
676 std::vector<double> mult;
678 for (std::size_t k = 0; k < sys.
events.size(); ++k) {
680 const double m = k < mult.size() ? mult[k] : 1.0;
682 if (r == 0.0)
continue;
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
The exception types the port throws.
The moment closures the fluid drift is built from: fluid_min_closure.m, fluid_capacity_closure....
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
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,...
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
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,...
void fluid_rates_closing_factors(const FluidOdeSystem &sys, const double *x, std::vector< double > &g)
Port of ode_rates_closing_factors: the state-dependent factor g(x), in place.
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...
FluidOdeSystem fluid_ode_system(const qn::NetworkStruct< T > &sn)
Build the drift of sn: the port of ode_jumps_new and ode_rate_base fused into one pass.
std::function< void(double, const double *, double *)> fluid_drift(const FluidOdeSystem &sys)
The drift dx/dt, ready to hand to the integrator.
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...
Matrix< double > fluid_jump_matrix(const FluidOdeSystem &sys)
The reference's dense jump matrix D, (nstates x nevents), rebuilt from the two-index event form this ...
void fluid_interpcols(const std::vector< double > &tg, const Matrix< double > &B, double tt, std::vector< double > &out)
Port of fluid_interpcols.m: clamped piecewise-linear interpolation of the columns of B at a scalar ti...
void fluid_rates_closing(const FluidOdeSystem &sys, const double *x, std::vector< double > &g)
The reference's ode_rates_closing name, kept for the first-order callers.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Matrix< T > dtmc_stochcomp(const Matrix< T > &P, const std::vector< std::size_t > &keep)
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
A queueing network and its refreshed NetworkStruct.
A closure's value and its first two derivatives with respect to the first mean.
The second moment the drift closes its non-linear terms with, i.e.
std::vector< Matrix< double > > cov
per station, 0x0 keeps the plug-in share
const Matrix< double > * cov_of(std::size_t i) const
std::vector< double > sigma2
per station; empty selects first order
double sigma2_of(std::size_t i) const
bool gaussian() const
any(sigma2 > 0), the reference's GLOBAL gaussian flag.
std::size_t event_idx
state entry whose g(x) drives this rate
double rate_base
the model-fixed part of the rate
Where each (station, class) block sits in the state vector.
std::vector< std::vector< std::size_t > > qidx
0-based first index of (i,r)
std::size_t nstates
length of the state vector
std::vector< std::vector< bool > > enabled
whether (i,r) is served at all
std::vector< std::vector< std::size_t > > kic
phases held by (i,r); 0 when disabled
std::size_t n_departures
How many leading entries of events are DEPARTURES (a job completing at one block and starting at anot...
std::vector< double > nservers
per station, already finite
FluidClosure closure
The second moment the closures read; empty is the first-order drift.
FluidRateMult ratemult
solver_fluid_ratemult's multiplier; empty is the autonomous drift.
std::vector< std::vector< double > > lld
sn.lldscaling(i,:) per station, EMPTY when the station has none or when every entry is one – the refe...
std::vector< FluidEvent > events
std::vector< std::vector< double > > weight
per station, per class (DPS, GPS)
std::vector< lang::SchedStrategy > sched
per station
The assembled drift: the layout, the events, and the per-station schedule.
Matrix< double > Mmat
(nevents x ngrid)
std::vector< double > tgrid
strictly increasing, one column per entry
A share closure's value and Jacobian, and the joint-closure covariance.
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.