5#ifndef LINE_SOLVERS_ENV_SOLVER_ENV_MEANFIELD_H
6#define LINE_SOLVERS_ENV_SOLVER_ENV_MEANFIELD_H
106 std::string
da =
"courtois";
136namespace meanfield_detail {
139inline void refuse_inexact(
const std::string&
da) {
141 "SolverENV compression: the '" +
da +
142 "' decomposition needs transcendental arithmetic, because every one of ctmc_courtois, "
143 "ctmc_kms, ctmc_takahashi and ctmc_multi reports epsMAX, a subdominant eigenvalue "
144 "modulus with no rational closed form; rerun with --arith double or real");
148inline void check_partition(
const MacroPartition& MS, std::size_t n) {
149 std::vector<bool> seen(n,
false);
150 std::size_t total = 0;
151 for (
const std::vector<std::size_t>& blk : MS) {
152 if (blk.
empty())
throw InputError(
"SolverENV compression: a macro-state is empty");
153 for (std::size_t s : blk) {
155 throw InputError(
"SolverENV compression: a macro-state names stage " +
156 std::to_string(s + 1) +
", which does not exist");
158 throw InputError(
"SolverENV compression: stage " + std::to_string(s + 1) +
159 " appears in more than one macro-state");
166 "SolverENV compression: the macro-states must cover every stage; the partition "
168 std::to_string(total) +
" of " + std::to_string(n));
174 for (std::size_t i = 0; i < E; ++i) MS[i] = std::vector<std::size_t>{i};
181 out.reserve(ms.size() - 1);
182 for (std::size_t k = 0; k < ms.size(); ++k) {
184 std::vector<std::size_t> m = ms[i];
185 m.insert(m.end(), ms[j].begin(), ms[j].end());
188 out.push_back(ms[k]);
207 meanfield_detail::check_partition(MS, Q.
rows());
210 meanfield_detail::refuse_inexact(
opt.da);
215 for (std::size_t i = 0; i < Q.
rows(); ++i)
216 for (std::size_t j = 0; j < Q.
cols(); ++j) {
217 const T a =
num_abs(T(Q(i, j)));
218 if (a > qmax) qmax = a;
222 if (
opt.da ==
"courtois") {
228 }
else if (
opt.da ==
"kms") {
234 }
else if (
opt.da ==
"takahashi") {
240 }
else if (
opt.da ==
"multi") {
246 for (std::size_t i = 0; i < MS.size(); ++i) MSS[i] = std::vector<std::size_t>{i};
254 "SolverENV compression: unknown decomposition '" +
opt.da +
255 "'; options.config.da is one of courtois, kms, takahashi, multi");
273 const std::size_t E = e.
nstages();
275 for (std::size_t a = 0; a < E; ++a)
276 for (std::size_t b = 0; b < E; ++b)
277 if (e.
arc(a, b).enabled) E0(a, b) = e.
arc(a, b).dist.rate();
294 const std::size_t E = Eutil.
rows();
298 if (std::isnan(best_eps))
return best;
300 for (std::size_t i = 0; i < E; ++i)
301 for (std::size_t j = i + 1; j < E; ++j) {
303 meanfield_detail::merge_blocks(meanfield_detail::singletons(E), i, j);
306 if (!std::isnan(te) && te < best_eps) {
330 const std::size_t E = Eutil.
rows();
331 std::vector<MacroPartition> beam{meanfield_detail::singletons(E)};
335 for (std::size_t depth = 1; depth < E; ++depth) {
336 std::vector<std::pair<double, MacroPartition>> cand;
338 for (std::size_t i = 0; i < ms.size(); ++i)
339 for (std::size_t j = i + 1; j < ms.size(); ++j) {
340 const MacroPartition trial = meanfield_detail::merge_blocks(ms, i, j);
343 if (std::isnan(te) || !(te > 0.0))
continue;
345 opt.env_alpha *
static_cast<double>(depth);
346 cand.push_back(std::make_pair(cost, trial));
347 if (cost < best_cost) {
353 if (cand.empty())
break;
356 std::stable_sort(cand.begin(), cand.end(),
357 [](
const std::pair<double, MacroPartition>& a,
358 const std::pair<double, MacroPartition>& b) { return a.first < b.first; });
360 for (std::size_t i = 0; i <
opt.beam_width && i < cand.size(); ++i)
361 beam.push_back(cand[i].second);
377 std::shared_ptr<Environment<T>>
env;
403 const std::size_t E = e0.
nstages();
411 "SolverENV compression",
412 "a macro-state is built as one stage network carrying the pmicro-weighted average of "
413 "its members' station rates, and a layered model has no such rate table -- only the "
414 "layers SolverLN derives from it");
420 for (std::size_t a = 0; a < E; ++a)
421 for (std::size_t b = 0; b < E; ++b) {
422 if (!e0.
arc(a, b).enabled)
continue;
425 "SolverENV compression: the transition from stage " + std::to_string(a + 1) +
426 " to " + std::to_string(b + 1) +
427 " is not exponential, and the NCD decomposition reads the environment as a "
428 "CTMC through E0 = getRate(); aggregating it would silently replace the "
429 "transition by an exponential of the same mean, so it is refused instead");
436 if (!
opt.partition.empty()) {
437 meanfield_detail::check_partition(
opt.partition, E);
438 c.
MS =
opt.partition;
439 }
else if (E <=
opt.beam_above_stages) {
444 const std::size_t Ec = c.
MS.size();
456 c.
pmacro.assign(Ec, zero);
457 for (std::size_t i = 0; i < Ec; ++i)
458 for (std::size_t s : c.
MS[i]) c.
pmacro[i] += c.
p[s];
460 for (std::size_t i = 0; i < Ec; ++i) {
468 for (std::size_t i = 0; i < Ec; ++i)
469 for (std::size_t j = 0; j < Ec; ++j)
470 for (std::size_t mi : c.
MS[i])
475 for (std::size_t x = 0; x < Ec; ++x) {
477 for (std::size_t h = 0; h < Ec; ++h)
481 if (!(tot > 0.0))
continue;
482 for (std::size_t k = 0; k < Ec; ++k) {
483 if (k == x)
continue;
491 c.
env = std::make_shared<Environment<T>>(e0.
name() +
"-compressed", Ec);
492 for (std::size_t i = 0; i < Ec; ++i) {
493 const std::size_t first = c.
MS[i][0];
495 const std::size_t M =
sn.nstations, K =
sn.nclasses;
496 for (std::size_t s : c.
MS[i])
497 if (e0.
stage(s).model.nstations != M || e0.
stage(s).model.nclasses != K)
499 "SolverENV compression: macro-state " + std::to_string(i + 1) +
500 " merges stages with different stations or classes, whose rates cannot be "
501 "averaged entrywise");
502 for (std::size_t m = 0; m < M; ++m) {
508 for (std::size_t k = 0; k < K; ++k) {
510 for (std::size_t s : c.
MS[i])
511 acc += T(c.
pmicro[s] * e0.
stage(s).model.rates(m, k));
516 c.
env->set_stage(i, e0.
stage(first).name +
"+", e0.
stage(first).type,
sn);
519 for (std::size_t i = 0; i < Ec; ++i) {
520 bool any_out =
false;
521 for (std::size_t j = 0; j < Ec; ++j) {
522 if (i == j)
continue;
529 const std::size_t fi = c.
MS[i][0],
fj = c.
MS[j][0];
530 const bool want =
static_cast<bool>(e0.
arc(fi,
fj).reset);
531 for (std::size_t a : c.
MS[i])
532 for (std::size_t b : c.
MS[j])
533 if (e0.
arc(a, b).enabled &&
static_cast<bool>(e0.
arc(a, b).reset) != want)
535 "SolverENV compression: the arcs folding into the macro transition " +
536 std::to_string(i + 1) +
" -> " + std::to_string(j + 1) +
537 " do not agree on whether a reset policy applies, and one macro arc "
538 "can carry only one; split the partition so that reset policies are "
539 "uniform within it");
541 e0.
arc(fi,
fj).reset);
546 "SolverENV compression: macro-state " + std::to_string(i + 1) +
547 " has no outgoing transition, so the compressed environment is absorbing; the "
548 "partition merged a whole recurrent class into one block");
573 const std::size_t Ec = c.
MS.size();
576 "SolverENV compression: the macro probabilities do not match the environment they "
577 "are being applied to");
604namespace meanfield_detail {
609 std::vector<std::size_t> out;
610 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
641 const std::size_t E = e.nstages();
642 if (E == 0)
return out;
646 if (e.has_lqn_stages())
return out;
648 const std::vector<std::size_t> nodes = cache_nodes_of(sn1);
649 if (nodes.empty())
return out;
650 const std::size_t
nc = nodes.size(), K = sn1.
nclasses;
657 if (o.stage_solver !=
"fluid")
return out;
661 std::vector<double> tend(E, 0.0);
662 for (std::size_t s = 0; s < E; ++s) {
663 double t1 = o.timespan_end;
664 if (!std::isfinite(t1) || !(t1 > 0.0)) t1 = 20.0 *
mam::map_mean(e.hold_time[s].map());
668 std::vector<std::vector<std::vector<T> > > entry(E, std::vector<std::vector<T> >(nc));
669 std::vector<std::vector<fluid::FluidCacheqnTranCache<T> > > tran(E);
670 std::vector<std::vector<std::vector<T> > > wmass(E, std::vector<std::vector<T> >(nc));
671 std::vector<std::vector<std::vector<T> > > exit_occ(E, std::vector<std::vector<T> >(nc));
672 std::vector<T> prev_flat;
673 const int sweeps = o.iter_max > 0 ? o.iter_max : 1;
675 for (
int sweep = 0; sweep < sweeps; ++sweep) {
676 for (std::size_t s = 0; s < E; ++s) {
677 fluid::FluidOptions fo;
680 for (std::size_t c = 0; c < nc; ++c) {
681 std::size_t idx = tran[s].size();
682 for (std::size_t q = 0; q < tran[s].size(); ++q)
683 if (tran[s][q].node == nodes[c]) idx = q;
684 if (idx == tran[s].size())
continue;
685 const fluid::FluidCacheqnTranCache<T>& tc = tran[s][idx];
687 for (std::size_t k = 0; k < tc.arate.size(); ++k)
688 Lam += num_traits<T>::to_double(tc.arate[k]);
689 if (!(Lam > 0.0))
continue;
692 std::vector<double> treal(tc.t.size(), 0.0);
693 for (std::size_t j = 0; j < tc.t.size(); ++j)
694 treal[j] = num_traits<T>::to_double(tc.t[j]) / Lam;
695 const std::vector<double> F =
mam::map_cdf(e.hold_time[s].map(), treal);
696 std::vector<T> w(treal.size(), num_traits<T>::from_int(0));
697 for (std::size_t j = 1; j < treal.size(); ++j)
698 w[j] = num_traits<T>::from_double(F[j] - F[j - 1]);
702 for (std::size_t j = 0; j < w.size(); ++j) sw += num_traits<T>::to_double(w[j]);
703 if (!(sw > 0.0) || tc.xocc.rows() == 0)
continue;
704 std::vector<T> xo(tc.xocc.rows(), num_traits<T>::from_int(0));
705 for (std::size_t a = 0; a < tc.xocc.rows(); ++a) {
706 T acc = num_traits<T>::from_int(0);
707 for (std::size_t j = 0; j < w.size() && j < tc.xocc.cols(); ++j)
708 acc = T(acc + T(tc.xocc(a, j) * w[j]));
709 xo[a] = T(acc / num_traits<T>::from_double(sw));
716 std::vector<std::vector<std::vector<T> > > next(E, std::vector<std::vector<T> >(nc));
717 for (std::size_t s = 0; s < E; ++s)
718 for (std::size_t c = 0; c < nc; ++c) {
720 for (std::size_t h = 0; h < E; ++h) {
721 const double po = e.prob_orig(h, s);
722 if (!(po > 0.0) || exit_occ[h][c].empty())
continue;
723 if (acc.empty()) acc.assign(exit_occ[h][c].size(), num_traits<T>::from_int(0));
724 if (acc.size() != exit_occ[h][c].size())
continue;
725 for (std::size_t a = 0; a < acc.size(); ++a)
726 acc[a] = T(acc[a] + T(num_traits<T>::from_double(po) * exit_occ[h][c][a]));
732 for (std::size_t s = 0; s < E; ++s)
733 for (std::size_t c = 0; c < nc; ++c)
734 flat.insert(flat.end(), next[s][c].begin(), next[s][c].end());
736 if (!prev_flat.empty() && prev_flat.size() == flat.size()) {
738 for (std::size_t a = 0; a < flat.size(); ++a)
739 dmax = std::max(dmax, std::fabs(num_traits<T>::to_double(flat[a]) -
740 num_traits<T>::to_double(prev_flat[a])));
742 if (dmax < o.iter_tol)
break;
748 const T nan = num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN());
749 std::vector<std::vector<T> > hitprob(nc, std::vector<T>(K, nan));
750 std::vector<std::vector<T> > missprob(nc, std::vector<T>(K, nan));
751 for (std::size_t c = 0; c < nc; ++c) {
752 std::vector<T> hitT(K, num_traits<T>::from_int(0)), missT(K, num_traits<T>::from_int(0));
753 for (std::size_t s = 0; s < E; ++s) {
754 if (wmass[s][c].empty())
continue;
757 for (std::size_t j = 0; j < wmass[s][c].size(); ++j) {
758 const double v = num_traits<T>::to_double(wmass[s][c][j]);
759 if (!std::isfinite(v)) ok =
false;
762 if (!ok || !(sw > 0.0))
continue;
763 std::size_t idx = tran[s].size();
764 for (std::size_t q = 0; q < tran[s].size(); ++q)
765 if (tran[s][q].node == nodes[c]) idx = q;
766 if (idx == tran[s].size())
continue;
767 const fluid::FluidCacheqnTranCache<T>& tc = tran[s][idx];
768 const T pe = num_traits<T>::from_double(e.prob_env[s]);
769 for (std::size_t k = 0; k < K && k < tc.arate.size(); ++k) {
770 if (!(num_traits<T>::to_double(tc.arate[k]) > 0.0))
continue;
771 T hbar = num_traits<T>::from_int(0), mbar = num_traits<T>::from_int(0);
772 for (std::size_t j = 0; j < wmass[s][c].size() && j < tc.hitprob_t.cols(); ++j) {
773 hbar = T(hbar + T(tc.hitprob_t(k, j) * wmass[s][c][j]));
774 mbar = T(mbar + T(tc.missprob_t(k, j) * wmass[s][c][j]));
776 const T swT = num_traits<T>::from_double(sw);
777 hitT[k] = T(hitT[k] + T(T(pe * tc.arate[k]) * T(hbar / swT)));
778 missT[k] = T(missT[k] + T(T(pe * tc.arate[k]) * T(mbar / swT)));
781 for (std::size_t k = 0; k < K; ++k) {
782 const T tot = T(hitT[k] + missT[k]);
783 if (num_traits<T>::to_double(tot) > 0.0) {
784 hitprob[c][k] = T(hitT[k] / tot);
785 missprob[c][k] = T(missT[k] / tot);
798 std::vector<T>(), Matrix<T>(), Matrix<T>(), std::vector<T>());
799 for (std::size_t c = 0; c < nc && c < out.
caches.size(); ++c) {
800 out.
caches[c].hitprob = hitprob[c];
801 out.
caches[c].missprob = missprob[c];
808void refuse_cache_stages(
const Environment<T>& e,
const EnvOptions& o) {
809 if (o.stage_solver ==
"fluid")
return;
810 for (std::size_t s = 0; s < e.nstages(); ++s) {
814 if (e.is_lqn(s))
continue;
815 const qn::NetworkStruct<T>& sn = e.stage(s).model;
816 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind)
819 "SolverENV meanfield: stage " + std::to_string(s + 1) +
820 " holds a Cache, whose environment-blended hit and miss ratios come from "
821 "aggregateCacheMeanfield_ and its per-sweep solver_fld_cacheqn_tran RMF "
822 "transient; that transient exists only for a FLUID stage solver, and this "
824 o.stage_solver +
"' stages");
833 meanfield_detail::refuse_cache_stages(e, o);
837 out.
cache = meanfield_detail::aggregate_cache_meanfield(e, o);
853 meanfield_detail::refuse_cache_stages(e, o);
860 out.
cache = meanfield_detail::aggregate_cache_meanfield(*out.
compression.env, o);
What a solver observed about the Cache nodes of a model.
UnsupportedError(const std::string &what)
const std::string & name() const
const EnvStage< T > & stage(std::size_t e) const
Matrix< double > prob_orig
probOrig(h, e)
std::size_t nstages() const
void reject_lqn_stages(const std::string &who, const std::string &why) const
Refuse an environment carrying a LayeredNetwork stage, by name.
const EnvArc< T > & arc(std::size_t e, std::size_t h) const
std::vector< double > prob_env
probEnv
A network plus its refreshed NetworkStruct.
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
Steady-state distribution of a continuous-time Markov chain.
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
A random environment: a port of matlab/src/lang/Environment.m, restricted to what SolverENV reads out...
The exception types the port throws.
The INTEGRATED caching-queueing network under the fluid solver: ports of solver_fld_cacheqn_analyzer....
Cumulative distribution of the inter-arrival time of a MAP.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
std::vector< std::vector< std::size_t > > MacroPartition
A partition of the stage indices 0..E-1 into macro-states, MATLAB's MS.
EnvDecomp< T > env_ctmc_decompose(const Matrix< T > &Q, const MacroPartition &MS, const EnvCompressOptions &opt)
Port of SolverENV.ctmc_decompose: one NCD decomposition, by whichever kernel options....
MacroPartition env_beam_search_partition(const Matrix< T > &Eutil, const EnvCompressOptions &opt)
Port of beamSearchPartition, the large-environment search: repeatedly merge two blocks,...
MacroPartition env_find_best_partition(const Matrix< T > &Eutil, const EnvCompressOptions &opt)
Port of findBestPartition, the small-environment search.
Matrix< T > env_rate_matrix(const Environment< T > &e)
E0, the environment's rate matrix: E0(e,h) = env{e,h}.getRate().
EnvMeanfieldSolution< T > solver_env_meanfield(Environment< T > &e, const EnvOptions &o)
The mean-field solve on the original stages, with no compression.
EnvCompression< T > env_compress(const Environment< T > &e0, const EnvCompressOptions &opt)
Port of applyCompression: pick a partition, decompose, and build the macro-state environment.
void env_apply_macro_probabilities(Environment< T > &e, const EnvCompression< T > &c)
probEnv = pMacro and probOrig = newEmbweight, the two quantities applyCompression overwrites on the e...
std::vector< FluidCacheqnTranCache< T > > solver_fld_cacheqn_tran(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, double t0, double t1, const std::vector< std::vector< T > > &x0cell=std::vector< std::vector< T > >())
Port of solver_fld_cacheqn_tran.m: the transient counterpart of the analyzer above.
NodeType
Node kinds, with the values of MATLAB NodeType.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
std::vector< T > map_cdf(const Map< T > &m, const std::vector< T > &points)
Cumulative distribution of the inter-arrival time at the given points.
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
KmsResult< T > ctmc_kms(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps)
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
TakahashiResult< T > ctmc_takahashi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps, double massTol=1e-14)
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
MultiResult< T > ctmc_multi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const std::vector< std::vector< std::size_t > > &MSS, const T &q)
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
CourtoisResult< T > ctmc_courtois(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const T &q)
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
Conservation laws of a layered queueing network, enumerated from its structure.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
SolverENV: a queueing network in a random environment.
The knobs applyCompression reads out of options.config.
MacroPartition partition
A partition supplied by the caller, which SKIPS the search entirely.
double env_alpha
options.config.env_alpha: the beam search's per-depth merge penalty.
std::size_t beam_above_stages
Stage count above which the reference switches from the pairwise search to the beam search.
std::string da
options.config.da: courtois (the reference's default), kms, takahashi, multi.
std::size_t beam_width
Beam width, the reference's hard-coded B = 3.
std::size_t da_iter
options.config.da_iter: sweeps for the two iterative kernels.
Everything applyCompression computes, plus the compressed environment.
std::vector< T > pmicro
within-macro-state conditional probabilities
std::vector< T > pmacro
pMacro, the macro-state probabilities
bool compressible
eps <= epsMAX: below this the aggregation is meaningful, above it is not.
Matrix< T > macro_rate
computeMacroRate(i,j)
std::vector< T > p
micro stationary vector from the decomposition
Matrix< double > prob_orig
the macro embedding weights newEmbweight
std::shared_ptr< Environment< T > > env
The compressed environment.
What SolverENV.ctmc_decompose returns: [p, eps, epsMax, q].
std::vector< T > p
approximate stationary vector of the environment
A mean-field solve, with the compression that produced it.
solvers::CacheMetrics< T > cache
aggregateCacheMeanfield_: environment-blended hit and miss probabilities per Cache node.
EnvCompression< T > compression
bool compressed
false when the solve ran on the original stages
EnvSolution avg
what SolverEnv reported
static Distrib exp_rate(const T &r)
T q
uniformization rate used
T eps
NCD index: largest ROW sum of B, ||B||_inf (MATLAB and the JAR).
std::vector< T > p
approximate stationary vector, ORIGINAL state ordering
T epsMAX
(1 - max subdominant block eigenvalue modulus) / 2
T epsMAX
maximum admissible NCD index
T eps
NCD index, as ctmc_courtois defines it.
std::vector< T > p
estimate after numSteps sweeps, ORIGINAL ordering
std::vector< T > p
approximate stationary vector, ORIGINAL ordering
T eps
NCD index of the fine level.
T epsMAX
maximum admissible NCD index of the fine level
T eps
NCD index, as ctmc_courtois defines it.
T epsMAX
maximum admissible NCD index
std::vector< T > p
estimate after numSteps sweeps
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches