5#ifndef LINE_SOLVERS_ENV_SOLVER_ENV_MEANFIELD_H
6#define LINE_SOLVERS_ENV_SOLVER_ENV_MEANFIELD_H
105 std::string
da =
"courtois";
135namespace meanfield_detail {
138inline void refuse_inexact(
const std::string&
da) {
140 "SolverENV compression: the '" +
da +
141 "' decomposition needs transcendental arithmetic, because every one of ctmc_courtois, "
142 "ctmc_kms, ctmc_takahashi and ctmc_multi reports epsMAX, a subdominant eigenvalue "
143 "modulus with no rational closed form; rerun with --arith double or real");
147inline void check_partition(
const MacroPartition& MS, std::size_t n) {
148 std::vector<bool> seen(n,
false);
149 std::size_t total = 0;
150 for (
const std::vector<std::size_t>& blk : MS) {
151 if (blk.
empty())
throw InputError(
"SolverENV compression: a macro-state is empty");
152 for (std::size_t s : blk) {
154 throw InputError(
"SolverENV compression: a macro-state names stage " +
155 std::to_string(s + 1) +
", which does not exist");
157 throw InputError(
"SolverENV compression: stage " + std::to_string(s + 1) +
158 " appears in more than one macro-state");
165 "SolverENV compression: the macro-states must cover every stage; the partition "
167 std::to_string(total) +
" of " + std::to_string(n));
173 for (std::size_t i = 0; i < E; ++i) MS[i] = std::vector<std::size_t>{i};
180 out.reserve(ms.size() - 1);
181 for (std::size_t k = 0; k < ms.size(); ++k) {
183 std::vector<std::size_t> m = ms[i];
184 m.insert(m.end(), ms[j].begin(), ms[j].end());
187 out.push_back(ms[k]);
206 meanfield_detail::check_partition(MS, Q.
rows());
209 meanfield_detail::refuse_inexact(
opt.da);
214 for (std::size_t i = 0; i < Q.
rows(); ++i)
215 for (std::size_t j = 0; j < Q.
cols(); ++j) {
216 const T a =
num_abs(T(Q(i, j)));
217 if (a > qmax) qmax = a;
221 if (
opt.da ==
"courtois") {
227 }
else if (
opt.da ==
"kms") {
233 }
else if (
opt.da ==
"takahashi") {
239 }
else if (
opt.da ==
"multi") {
245 for (std::size_t i = 0; i < MS.size(); ++i) MSS[i] = std::vector<std::size_t>{i};
253 "SolverENV compression: unknown decomposition '" +
opt.da +
254 "'; options.config.da is one of courtois, kms, takahashi, multi");
272 const std::size_t E = e.
nstages();
274 for (std::size_t a = 0; a < E; ++a)
275 for (std::size_t b = 0; b < E; ++b)
276 if (e.
arc(a, b).enabled) E0(a, b) = e.
arc(a, b).dist.rate();
293 const std::size_t E = Eutil.
rows();
297 if (std::isnan(best_eps))
return best;
299 for (std::size_t i = 0; i < E; ++i)
300 for (std::size_t j = i + 1; j < E; ++j) {
302 meanfield_detail::merge_blocks(meanfield_detail::singletons(E), i, j);
305 if (!std::isnan(te) && te < best_eps) {
329 const std::size_t E = Eutil.
rows();
330 std::vector<MacroPartition> beam{meanfield_detail::singletons(E)};
334 for (std::size_t depth = 1; depth < E; ++depth) {
335 std::vector<std::pair<double, MacroPartition>> cand;
337 for (std::size_t i = 0; i < ms.size(); ++i)
338 for (std::size_t j = i + 1; j < ms.size(); ++j) {
339 const MacroPartition trial = meanfield_detail::merge_blocks(ms, i, j);
342 if (std::isnan(te) || !(te > 0.0))
continue;
344 opt.env_alpha *
static_cast<double>(depth);
345 cand.push_back(std::make_pair(cost, trial));
346 if (cost < best_cost) {
352 if (cand.empty())
break;
355 std::stable_sort(cand.begin(), cand.end(),
356 [](
const std::pair<double, MacroPartition>& a,
357 const std::pair<double, MacroPartition>& b) { return a.first < b.first; });
359 for (std::size_t i = 0; i <
opt.beam_width && i < cand.size(); ++i)
360 beam.push_back(cand[i].second);
376 std::shared_ptr<Environment<T>>
env;
402 const std::size_t E = e0.
nstages();
410 "SolverENV compression",
411 "a macro-state is built as one stage network carrying the pmicro-weighted average of "
412 "its members' station rates, and a layered model has no such rate table -- only the "
413 "layers SolverLN derives from it");
419 for (std::size_t a = 0; a < E; ++a)
420 for (std::size_t b = 0; b < E; ++b) {
421 if (!e0.
arc(a, b).enabled)
continue;
424 "SolverENV compression: the transition from stage " + std::to_string(a + 1) +
425 " to " + std::to_string(b + 1) +
426 " is not exponential, and the NCD decomposition reads the environment as a "
427 "CTMC through E0 = getRate(); aggregating it would silently replace the "
428 "transition by an exponential of the same mean, so it is refused instead");
435 if (!
opt.partition.empty()) {
436 meanfield_detail::check_partition(
opt.partition, E);
437 c.
MS =
opt.partition;
438 }
else if (E <=
opt.beam_above_stages) {
443 const std::size_t Ec = c.
MS.size();
455 c.
pmacro.assign(Ec, zero);
456 for (std::size_t i = 0; i < Ec; ++i)
457 for (std::size_t s : c.
MS[i]) c.
pmacro[i] += c.
p[s];
459 for (std::size_t i = 0; i < Ec; ++i) {
467 for (std::size_t i = 0; i < Ec; ++i)
468 for (std::size_t j = 0; j < Ec; ++j)
469 for (std::size_t mi : c.
MS[i])
474 for (std::size_t x = 0; x < Ec; ++x) {
476 for (std::size_t h = 0; h < Ec; ++h)
480 if (!(tot > 0.0))
continue;
481 for (std::size_t k = 0; k < Ec; ++k) {
482 if (k == x)
continue;
490 c.
env = std::make_shared<Environment<T>>(e0.
name() +
"-compressed", Ec);
491 for (std::size_t i = 0; i < Ec; ++i) {
492 const std::size_t first = c.
MS[i][0];
494 const std::size_t M =
sn.nstations, K =
sn.nclasses;
495 for (std::size_t s : c.
MS[i])
496 if (e0.
stage(s).model.nstations != M || e0.
stage(s).model.nclasses != K)
498 "SolverENV compression: macro-state " + std::to_string(i + 1) +
499 " merges stages with different stations or classes, whose rates cannot be "
500 "averaged entrywise");
501 for (std::size_t m = 0; m < M; ++m) {
507 for (std::size_t k = 0; k < K; ++k) {
509 for (std::size_t s : c.
MS[i])
510 acc += T(c.
pmicro[s] * e0.
stage(s).model.rates(m, k));
515 c.
env->set_stage(i, e0.
stage(first).name +
"+", e0.
stage(first).type,
sn);
518 for (std::size_t i = 0; i < Ec; ++i) {
519 bool any_out =
false;
520 for (std::size_t j = 0; j < Ec; ++j) {
521 if (i == j)
continue;
528 const std::size_t fi = c.
MS[i][0],
fj = c.
MS[j][0];
529 const bool want =
static_cast<bool>(e0.
arc(fi,
fj).reset);
530 for (std::size_t a : c.
MS[i])
531 for (std::size_t b : c.
MS[j])
532 if (e0.
arc(a, b).enabled &&
static_cast<bool>(e0.
arc(a, b).reset) != want)
534 "SolverENV compression: the arcs folding into the macro transition " +
535 std::to_string(i + 1) +
" -> " + std::to_string(j + 1) +
536 " do not agree on whether a reset policy applies, and one macro arc "
537 "can carry only one; split the partition so that reset policies are "
538 "uniform within it");
540 e0.
arc(fi,
fj).reset);
545 "SolverENV compression: macro-state " + std::to_string(i + 1) +
546 " has no outgoing transition, so the compressed environment is absorbing; the "
547 "partition merged a whole recurrent class into one block");
572 const std::size_t Ec = c.
MS.size();
575 "SolverENV compression: the macro probabilities do not match the environment they "
576 "are being applied to");
605namespace meanfield_detail {
610 std::vector<std::size_t> out;
611 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
640CacheBlendResult<T> aggregate_cache_meanfield(Environment<T>& e,
const EnvOptions& o) {
641 CacheBlendResult<T> out;
642 const std::size_t E = e.nstages();
643 if (E == 0)
return out;
647 if (e.has_lqn_stages())
return out;
649 out.
nodes = cache_nodes_of(sn1);
650 if (out.nodes.empty())
return out;
651 const std::size_t
nc = out.nodes.size(), K = sn1.
nclasses;
656 if (o.stage_solver !=
"fluid") {
663 std::vector<double> tend(E, 0.0);
664 for (std::size_t s = 0; s < E; ++s) {
665 double t1 = o.timespan_end;
666 if (!std::isfinite(t1) || !(t1 > 0.0)) t1 = 20.0 *
mam::map_mean(e.hold_time[s].map());
670 std::vector<std::vector<std::vector<T> > > entry(E, std::vector<std::vector<T> >(nc));
671 std::vector<std::vector<fluid::FluidCacheqnTranCache<T> > > tran(E);
672 std::vector<std::vector<std::vector<T> > > wmass(E, std::vector<std::vector<T> >(nc));
673 std::vector<std::vector<std::vector<T> > > exit_occ(E, std::vector<std::vector<T> >(nc));
674 std::vector<T> prev_flat;
675 const int sweeps = o.iter_max > 0 ? o.iter_max : 1;
677 for (
int sweep = 0; sweep < sweeps; ++sweep) {
678 for (std::size_t s = 0; s < E; ++s) {
679 fluid::FluidOptions fo;
682 for (std::size_t c = 0; c < nc; ++c) {
683 std::size_t idx = tran[s].size();
684 for (std::size_t q = 0; q < tran[s].size(); ++q)
685 if (tran[s][q].node == out.nodes[c]) idx = q;
686 if (idx == tran[s].size())
continue;
687 const fluid::FluidCacheqnTranCache<T>& tc = tran[s][idx];
689 for (std::size_t k = 0; k < tc.arate.size(); ++k)
690 Lam += num_traits<T>::to_double(tc.arate[k]);
691 if (!(Lam > 0.0))
continue;
694 std::vector<double> treal(tc.t.size(), 0.0);
695 for (std::size_t j = 0; j < tc.t.size(); ++j)
696 treal[j] = num_traits<T>::to_double(tc.t[j]) / Lam;
697 const std::vector<double> F =
mam::map_cdf(e.hold_time[s].map(), treal);
698 std::vector<T> w(treal.size(), num_traits<T>::from_int(0));
699 for (std::size_t j = 1; j < treal.size(); ++j)
700 w[j] = num_traits<T>::from_double(F[j] - F[j - 1]);
704 for (std::size_t j = 0; j < w.size(); ++j) sw += num_traits<T>::to_double(w[j]);
705 if (!(sw > 0.0) || tc.xocc.rows() == 0)
continue;
706 std::vector<T> xo(tc.xocc.rows(), num_traits<T>::from_int(0));
707 for (std::size_t a = 0; a < tc.xocc.rows(); ++a) {
708 T acc = num_traits<T>::from_int(0);
709 for (std::size_t j = 0; j < w.size() && j < tc.xocc.cols(); ++j)
710 acc = T(acc + T(tc.xocc(a, j) * w[j]));
711 xo[a] = T(acc / num_traits<T>::from_double(sw));
718 std::vector<std::vector<std::vector<T> > > next(E, std::vector<std::vector<T> >(nc));
719 for (std::size_t s = 0; s < E; ++s)
720 for (std::size_t c = 0; c < nc; ++c) {
722 for (std::size_t h = 0; h < E; ++h) {
723 const double po = e.prob_orig(h, s);
724 if (!(po > 0.0) || exit_occ[h][c].empty())
continue;
725 if (acc.empty()) acc.assign(exit_occ[h][c].size(), num_traits<T>::from_int(0));
726 if (acc.size() != exit_occ[h][c].size())
continue;
727 for (std::size_t a = 0; a < acc.size(); ++a)
728 acc[a] = T(acc[a] + T(num_traits<T>::from_double(po) * exit_occ[h][c][a]));
734 for (std::size_t s = 0; s < E; ++s)
735 for (std::size_t c = 0; c < nc; ++c)
736 flat.insert(flat.end(), next[s][c].begin(), next[s][c].end());
738 if (!prev_flat.empty() && prev_flat.size() == flat.size()) {
740 for (std::size_t a = 0; a < flat.size(); ++a)
741 dmax = std::max(dmax, std::fabs(num_traits<T>::to_double(flat[a]) -
742 num_traits<T>::to_double(prev_flat[a])));
744 if (dmax < o.iter_tol)
break;
750 const T nan = num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN());
751 out.hitprob.assign(nc, std::vector<T>(K, nan));
752 out.missprob.assign(nc, std::vector<T>(K, nan));
753 for (std::size_t c = 0; c < nc; ++c) {
754 std::vector<T> hitT(K, num_traits<T>::from_int(0)), missT(K, num_traits<T>::from_int(0));
755 for (std::size_t s = 0; s < E; ++s) {
756 if (wmass[s][c].empty())
continue;
759 for (std::size_t j = 0; j < wmass[s][c].size(); ++j) {
760 const double v = num_traits<T>::to_double(wmass[s][c][j]);
761 if (!std::isfinite(v)) ok =
false;
764 if (!ok || !(sw > 0.0))
continue;
765 std::size_t idx = tran[s].size();
766 for (std::size_t q = 0; q < tran[s].size(); ++q)
767 if (tran[s][q].node == out.nodes[c]) idx = q;
768 if (idx == tran[s].size())
continue;
769 const fluid::FluidCacheqnTranCache<T>& tc = tran[s][idx];
770 const T pe = num_traits<T>::from_double(e.prob_env[s]);
771 for (std::size_t k = 0; k < K && k < tc.arate.size(); ++k) {
772 if (!(num_traits<T>::to_double(tc.arate[k]) > 0.0))
continue;
773 T hbar = num_traits<T>::from_int(0), mbar = num_traits<T>::from_int(0);
774 for (std::size_t j = 0; j < wmass[s][c].size() && j < tc.hitprob_t.cols(); ++j) {
775 hbar = T(hbar + T(tc.hitprob_t(k, j) * wmass[s][c][j]));
776 mbar = T(mbar + T(tc.missprob_t(k, j) * wmass[s][c][j]));
778 const T swT = num_traits<T>::from_double(sw);
779 hitT[k] = T(hitT[k] + T(T(pe * tc.arate[k]) * T(hbar / swT)));
780 missT[k] = T(missT[k] + T(T(pe * tc.arate[k]) * T(mbar / swT)));
783 for (std::size_t k = 0; k < K; ++k) {
784 const T tot = T(hitT[k] + missT[k]);
785 if (num_traits<T>::to_double(tot) > 0.0) {
786 out.hitprob[c][k] = T(hitT[k] / tot);
787 out.missprob[c][k] = T(missT[k] / tot);
796void refuse_cache_stages(
const Environment<T>& e,
const EnvOptions& o) {
797 if (o.stage_solver ==
"fluid")
return;
798 for (std::size_t s = 0; s < e.nstages(); ++s) {
802 if (e.is_lqn(s))
continue;
803 const qn::NetworkStruct<T>& sn = e.stage(s).model;
804 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind)
807 "SolverENV meanfield: stage " + std::to_string(s + 1) +
808 " holds a Cache, whose environment-blended hit and miss ratios come from "
809 "aggregateCacheMeanfield_ and its per-sweep solver_fld_cacheqn_tran RMF "
810 "transient; that transient exists only for a FLUID stage solver, and this "
812 o.stage_solver +
"' stages");
821 meanfield_detail::refuse_cache_stages(e, o);
825 out.
cache = meanfield_detail::aggregate_cache_meanfield(e, o);
841 meanfield_detail::refuse_cache_stages(e, o);
848 out.
cache = meanfield_detail::aggregate_cache_meanfield(*out.
compression.env, o);
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.
std::vector< NodeDef > nodes
every node, in creation order
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.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
SolverENV: a queueing network in a random environment.
A mean-field solve, with the compression that produced it.
std::vector< std::vector< T > > hitprob
[cache][class], NaN where no flow
std::vector< std::size_t > nodes
0-based Cache node indices of stage 1
std::vector< std::vector< T > > missprob
[cache][class]
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
EnvCompression< T > compression
CacheBlendResult< T > cache
aggregateCacheMeanfield_: environment-blended hit and miss probabilities per Cache node.
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