5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_MDD_ANALYZER_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_MDD_ANALYZER_H
91namespace mdd_analyzer_detail {
104 const std::size_t M =
sn.nstations, R =
sn.nclasses;
106 for (std::size_t i = 0; i < M; ++i) {
107 const std::size_t si =
sn.stateful_of_station(i + 1) - 1;
108 for (std::size_t j = 0; j < M; ++j) {
109 const std::size_t sj =
sn.stateful_of_station(j + 1) - 1;
110 P(i, j) =
sn.rt(si * R, sj * R);
113 for (std::size_t i = 0; i < M; ++i) {
115 for (std::size_t j = 0; j < M; ++j) s += P(i, j);
118 "SolverCTMC(mdd): the station-to-station routing chain is not stochastic at "
120 std::to_string(i + 1) +
121 "; the mdd method needs every completion to move the job to another station");
128 return s == SchedStrategy::PS || s == SchedStrategy::DPS || s == SchedStrategy::GPS ||
129 s == SchedStrategy::INF;
133inline bool mdd_is_nonpreemptive(
SchedStrategy s,
double servers) {
134 return (s == SchedStrategy::FCFS || s == SchedStrategy::LCFS || s == SchedStrategy::SIRO ||
135 s == SchedStrategy::HOL) &&
159CtmcMddSolution<T> spn_route(
const NetworkStruct<T>& sn,
const CtmcOptions& opt,
160 const mdd::MddMcdOptions& mcdopt) {
161 const T zero = num_traits<T>::from_int(0);
162 const std::size_t M = sn.nstations, R = sn.nclasses;
164 spn::SpnOptions mddopt;
166 const mdd::MddMcdResult<T> out =
mdd::mdd_mcd<T>(net.mdds, net.desc, mcdopt);
167 const std::size_t L = net.info.nplacelevels;
169 CtmcMddSolution<T> sol;
174 sol.avg.XN.assign(R, zero);
175 sol.avg.CN.assign(R, zero);
177 sol.avg.PreemptN =
Matrix<T>(M, R, zero);
179 for (std::size_t pp = 0; pp < net.info.places.size(); ++pp) {
180 const std::size_t ist = sn.nodes[net.info.places[pp] - 1].station;
181 if (ist < 1)
continue;
182 sol.avg.QN(ist - 1, 0) = out.QLen[pp];
183 sol.avg.UN(ist - 1, 0) = out.QLen[pp];
188 std::vector<std::vector<T>> pl(L);
189 for (std::size_t l = 0; l < L; ++l) {
190 const std::size_t k = net.mdds.K - 1 - l;
191 pl[l].assign(net.mdds.domain[l], zero);
192 for (std::size_t r = 0; r < out.Mrows[k].size(); ++r)
193 pl[l][out.Mrows[k][r].second] += out.pik[k][r];
195 for (std::size_t v = 0; v < pl[l].size(); ++v) tot += pl[l][v];
197 for (std::size_t v = 0; v < pl[l].size(); ++v) pl[l][v] = T(pl[l][v] / tot);
200 for (std::size_t e = 0; e < net.info.modes.size(); ++e) {
201 const spn::SpnMode<T>& mde = net.info.modes[e];
202 if (mde.nph > 1)
continue;
204 std::vector<std::size_t> lv;
205 for (std::size_t l = 0; l < L; ++l)
206 if (mde.enab[l] > 0) lv.push_back(l);
207 T nsrv = num_traits<T>::from_int(1);
209 double kmax = std::numeric_limits<double>::infinity();
210 for (std::size_t i = 0; i < lv.size(); ++i)
211 kmax = std::min(kmax, std::floor((pl[lv[i]].size() - 1) / mde.enab[lv[i]]));
212 if (std::isfinite(mde.srv)) kmax = std::min(kmax, mde.srv);
214 for (
int k = 1; k <= static_cast<int>(kmax); ++k) {
215 T ge = num_traits<T>::from_int(1);
216 for (std::size_t i = 0; i < lv.size(); ++i) {
217 const std::size_t l = lv[i];
218 const std::size_t thr =
static_cast<std::size_t
>(k * mde.enab[l]);
219 if (thr >= pl[l].size()) {
224 for (std::size_t v = thr; v < pl[l].size(); ++v) s += pl[l][v];
230 const T x = T(mde.D1(0, 0) * nsrv);
231 for (std::size_t l = 0; l < L; ++l) {
232 if (mde.enab[l] <= 0)
continue;
233 const std::size_t ist = sn.nodes[net.info.places[l] - 1].station;
234 if (ist >= 1) sol.avg.TN(ist - 1, 0) = T(sol.avg.TN(ist - 1, 0) + x);
237 for (std::size_t i = 0; i < M; ++i)
238 if (sol.avg.TN(i, 0) > zero)
239 sol.avg.RN(i, 0) = T(sol.avg.QN(i, 0) / sol.avg.TN(i, 0));
241 const std::size_t ref = sn.classes.empty() ? 0 : sn.classes[0].refstat;
242 if (ref >= 1 && ref <= M) sol.avg.XN[0] = sol.avg.TN(ref - 1, 0);
244 for (std::size_t i = 0; i < M; ++i) Nk += sol.avg.QN(i, 0);
245 if (sol.avg.XN[0] > zero && Nk > zero) sol.avg.CN[0] = T(Nk / sol.avg.XN[0]);
247 sol.num_states =
static_cast<long long>(net.info.diagram.cardinality());
248 sol.level_sizes = out.level_sizes;
249 sol.iters = out.iters;
250 sol.paths_per_level = out.paths_per_level;
251 sol.no_aggregation = out.no_aggregation;
252 sol.encoding =
"spn";
283 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
284 if (
sn.nodes[ind].nodetype == NodeType::Place)
return std::string();
291 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
292 if (
sn.nodes[ind].nodetype == NodeType::Fork ||
sn.nodes[ind].nodetype == NodeType::Join)
293 return "SolverCTMC(mdd): the mdd method analyses closed single-class networks and "
294 "stochastic Petri nets; a fork-join model is neither, and its tag "
295 "augmentation adds one auxiliary class per branch";
296 if (
sn.nclasses != 1)
297 return "SolverCTMC(mdd): the mdd method analyses single-class networks; this model has " +
298 std::to_string(
sn.nclasses) +
299 " classes. The Kronecker descriptor would need one level per (station,class)";
300 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
301 if (
sn.nodes[ind].nodetype == NodeType::Source ||
sn.nodes[ind].nodetype == NodeType::Sink)
302 return "SolverCTMC(mdd): the mdd method analyses CLOSED networks; an open stream "
303 "makes the marking unbounded, so the reachable set has no finite decision "
305 const std::vector<double> njobs_gate =
sn.njobs();
306 if (!std::isfinite(njobs_gate[0]) || njobs_gate[0] <= 0.0)
307 return "SolverCTMC(mdd): the mdd method needs a finite positive closed population";
308 return std::string();
335 const std::size_t M =
sn.nstations, R =
sn.nclasses;
348 for (std::size_t ind = 0; ind <
sn.nodes.size(); ++ind)
349 if (
sn.nodes[ind].nodetype == NodeType::Place)
350 return mdd_analyzer_detail::spn_route<T>(
sn,
opt, mcdopt);
352 const std::vector<double> njobs =
sn.njobs();
353 const int N =
static_cast<int>(njobs[0]);
358 std::vector<T> mu(M, zero);
359 std::vector<double> servers(M, 1.0);
360 std::vector<mdd::MddServiceLaw<T>> proc(M);
361 std::vector<SchedStrategy> sched(M, SchedStrategy::FCFS);
363 for (std::size_t i = 0; i < M; ++i) {
364 mu[i] =
sn.rates(i, 0);
365 servers[i] =
sn.stations[i].nservers;
366 sched[i] =
sn.stations[i].sched;
368 if (d.
phases() > 1 && d.
D0.rows() > 0) {
371 proc[i].present =
true;
377 std::string encoding =
"np";
379 bool all_shared =
true;
380 for (std::size_t i = 0; i < M; ++i)
381 if (!mdd_analyzer_detail::mdd_is_shared(sched[i])) all_shared =
false;
388 bool all_np_at_ph =
true;
389 std::size_t first_ph = M, bad = M;
390 for (std::size_t i = 0; i < M; ++i) {
391 if (!proc[i].present)
continue;
392 if (first_ph == M) first_ph = i;
393 if (mdd_analyzer_detail::mdd_is_nonpreemptive(sched[i], servers[i]))
continue;
394 all_np_at_ph =
false;
395 if (bad == M && !mdd_analyzer_detail::mdd_is_shared(sched[i])) bad = i;
398 if (bad == M) bad = first_ph;
400 "SolverCTMC(mdd): station " + std::to_string(bad + 1) +
401 " combines a phase-type service law with a discipline that neither local "
402 "encoding represents: the count-plus-phase encoding is non-preemptive, and "
403 "the per-phase-count encoding covers only shared servers (PS/DPS/GPS/INF). "
404 "Mixing a shared and a non-preemptive phase-type station in one model is "
405 "likewise unsupported");
410 const Matrix<T> P = mdd_analyzer_detail::mdd_station_routing(
sn);
414 if (encoding ==
"ps") {
417 std::vector<std::string> schedname(M);
430 s.
avg.XN.assign(R, zero);
431 s.
avg.CN.assign(R, zero);
432 for (std::size_t i = 0; i < M; ++i) {
434 s.
avg.UN(i, 0) = out.
U[i];
435 s.
avg.TN(i, 0) = out.
X[i];
436 if (out.
X[i] > zero) s.
avg.RN(i, 0) = T(out.
QLen[i] / out.
X[i]);
443 const std::size_t ref =
sn.classes[0].refstat;
445 if (!
sn.visits.empty() &&
sn.visits[0].rows() > 0) {
446 const std::size_t rsf =
sn.stateful_of_station(ref) - 1;
447 if (
sn.visits[0](rsf, 0) > zero) vis =
sn.visits[0](rsf, 0);
449 s.
avg.XN[0] = T(s.
avg.TN(ref - 1, 0) / vis);
UnsupportedError(const std::string &what)
The diagram: insert / member / index / enumerate / cardinality.
long long cardinality() const
|S|, the number of stored states.
MddStruct to_struct() const
Export the diagram as plain arrays for downstream algorithms.
A network plus its refreshed NetworkStruct.
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.
Quasi-reduced ordered Multi-valued Decision Diagram.
Kronecker rate descriptor of a single-class closed queueing network.
Miner-Ciardo-Donatelli approximate stationary analysis.
Kronecker rate descriptor for shared-server stations with phase-type service.
Reachability set generation into a decision diagram.
The rate side of the decision-diagram domain: local matrices, events, the Kronecker descriptor,...
std::string solver_ctmc_mdd_supports(const NetworkStruct< T > &sn)
Can the mdd decision-diagram method be asked for this model?
mva::AvgResult< T > solver_ctmc_avg_table(const NetworkStruct< T > &sn, const CtmcSolution< T > &d, const std::string &method)
Port of @@SolverCTMC/runAnalyzer.m's result assembly: solve, then apply the metric filter @@NetworkSo...
CtmcMddSolution< T > solver_ctmc_mdd_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const mdd::MddMcdOptions &mcdopt=mdd::MddMcdOptions())
Solve with the mdd method.
mva::AvgResult< T > solver_ctmc_mdd_run_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const mdd::MddMcdOptions &mcdopt=mdd::MddMcdOptions())
Solve with mdd and format, so a caller with no use for the diagram has one call.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
const char * sched_to_text(SchedStrategy s)
MddDescriptor< T > mdd_descriptor(const std::vector< T > &mu, const Matrix< T > &P, const std::vector< double > &servers, int N, const std::vector< MddServiceLaw< T > > &proc=std::vector< MddServiceLaw< T > >(), const std::vector< std::string > &sched=std::vector< std::string >())
Build the descriptor.
MDD mdd_reachset(const std::vector< int > &domain, const std::vector< int > &init, const MddNextState &nextfun)
Generate and store the reachability set into a quasi-reduced ordered MDD.
MddDescriptor< T > mdd_ps(const std::vector< T > &mu, const Matrix< T > &P, const std::vector< double > &servers, int N, const std::vector< MddServiceLaw< T > > &proc=std::vector< MddServiceLaw< T > >())
Build the descriptor.
MddMcdResult< T > mdd_mcd(const MddStruct &mdds, const MddDescriptor< T > &desc, const MddMcdOptions &options=MddMcdOptions())
Approximate stationary measures by decision-diagram-guided aggregation.
SpnResult< T > spn_mdd(const qn::NetworkStruct< T > &sn, const SpnOptions &options=SpnOptions())
Build the reachable set and Kronecker descriptor of a stochastic Petri net.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Port of solver_ctmc.m: the infinitesimal generator of a queueing network, assembled from the enumerat...
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Decision-diagram reachable set and Kronecker rate descriptor of a stochastic Petri net,...
The mean performance metrics a stationary vector maps to.
What one mdd solve produces beside the means, i.e.
long long num_states
|S|, counted in the diagram without ever listing a state.
int iters
Coupled fixed-point sweeps performed.
bool no_aggregation
True certifies the answer is exact structurally; see the file header.
std::vector< double > paths_per_level
max |A(p)| per paper level; 1 everywhere means no node is shared.
std::string encoding
Which local encoding was picked, "np" or "ps".
std::vector< std::size_t > level_sizes
|M_k| per paper level; their sum is what the diagram actually holds.
The SolverCTMC knobs this port honours.
Everything one CTMC solve produces.
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
std::size_t phases() const
The order of the representation, MATLAB's sn.phases.
Kronecker rate descriptor of a structured model, the input of mdd_mcd.
MddNextState nextfun
Successor function over local indices.
std::vector< int > domain
Local domain per level.
std::vector< int > init
Initial local index per level.
Knobs of the level iteration in mdd_mcd.
Result of the Miner-Ciardo-Donatelli level aggregation.
std::vector< std::size_t > level_sizes
|M_k| per paper level.
bool no_aggregation
True certifies the result is EXACT with no reference solve needed; false means "not certified by this...
int iters
Fixed-point iterations performed.
std::vector< T > QLen
Mean occupancy per station (or place), in station order.
std::vector< double > paths_per_level
max |A(p)| per paper level: the largest number of distinct root-to-node paths at that level.
std::vector< T > X
Per-station throughput; empty when the descriptor carries no queueing parameters.
std::vector< T > U
Per-station utilization; empty when the descriptor carries no queueing parameters.
The metrics getAvg returns, after filtering.