LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ctmc_mdd_analyzer.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_MDD_ANALYZER_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_MDD_ANALYZER_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The `mdd` method of SolverCTMC: stationary analysis of a closed single-class
12 * network whose state space is held in a decision diagram and solved by level
13 * aggregation.
14 *
15 * Port of matlab/src/solvers/CTMC/solver_ctmc_mdd_analyzer.m, after
16 * A.S. Miner, G. Ciardo, S. Donatelli, "Using the exact state space of a Markov
17 * model to compute approximate stationary measures", SIGMETRICS 2000.
18 *
19 * IT NEVER FORMS THE |S|-STATE GENERATOR, which is the whole point and also why
20 * it sits beside `solver_ctmc_analyzer` rather than inside it: the reachable set
21 * lives in an MDD and K coupled level-CTMCs are iterated to a fixed point, so
22 * the memory cost is O(sum_k |M_k|) rather than O(|S|). The saving grows with
23 * the number of stations and is NEGATIVE at K = 3, where the diagram compresses
24 * nothing.
25 *
26 * EXACTNESS. The single approximation is Pr{i_k | alpha} = Pr{i_k | p}. It is
27 * EXACT on product-form networks (paper Sec. 5), which covers exponential
28 * service under any work-conserving discipline and general service at PS or IS
29 * stations (BCMP types 2 and 3). It is an approximation otherwise, notably
30 * phase-type service at FCFS or LCFS, where errors of a fraction of a percent on
31 * the mean queue lengths have been observed. `no_aggregation` additionally
32 * certifies exactness STRUCTURALLY: when no diagram node is shared, conditioning
33 * on the node equals conditioning on the whole path and the approximation is an
34 * identity. False means "not certified", never "approximate" -- a product-form
35 * model is exact however much its diagram shares.
36 *
37 * WHICH ENCODING. Exponential service is discipline-insensitive for the
38 * queue-length law, so the compact count encoding of `mdd_descriptor` serves any
39 * work-conserving station. Phase-type service is not: `mdd_descriptor`'s
40 * count-plus-one-phase local state is NON-preemptive, while a shared server has
41 * every job present in service holding its own phase and needs the per-phase
42 * counts of `mdd_ps`. A model that mixes the two cases is refused rather than
43 * modelled under whichever encoding happens to be picked.
44 */
45
46#include <cmath>
47#include <cstddef>
48#include <string>
49#include <vector>
50
51#include "line/api/mdd/mdd.h"
54#include "line/api/mdd/mdd_ps.h"
60#include "line/num/number.h"
63#include "line/util/error.h"
64#include "line/util/matrix.h"
65
66namespace line {
67namespace ctmc {
68
69// NetworkStruct, NodeType and SchedStrategy are already in scope from
70// solver_ctmc.h; re-declaring them here would be a redundant using.
71
72/** What one `mdd` solve produces beside the means, i.e. the reference's INFO. */
73template <class T>
76 /** |S|, counted in the diagram without ever listing a state. */
77 long long num_states = 0;
78 /** |M_k| per paper level; their sum is what the diagram actually holds. */
79 std::vector<std::size_t> level_sizes;
80 /** Coupled fixed-point sweeps performed. */
81 int iters = 0;
82 /** max |A(p)| per paper level; 1 everywhere means no node is shared. */
83 std::vector<double> paths_per_level;
84 /** True certifies the answer is exact structurally; see the file header. */
85 bool no_aggregation = false;
86 /** Which local encoding was picked, "np" or "ps". */
87 std::string encoding;
88 std::string actualmethod = "mdd";
89};
90
91namespace mdd_analyzer_detail {
92
93/**
94 * Station-to-station routing of the single class, read off `sn.rt`.
95 *
96 * `rt` is indexed by STATEFUL node and class-major, so the row of station i is
97 * `(stateful_of_station(i) - 1) * R + 0` with R = 1 here. A row that does not
98 * sum to one means a completion can leave the network or stay put, neither of
99 * which the closed descriptor represents.
100 */
101template <class T>
102Matrix<T> mdd_station_routing(const NetworkStruct<T>& sn) {
103 const T zero = num_traits<T>::from_int(0);
104 const std::size_t M = sn.nstations, R = sn.nclasses;
105 Matrix<T> P(M, M, zero);
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);
111 }
112 }
113 for (std::size_t i = 0; i < M; ++i) {
114 T s = zero;
115 for (std::size_t j = 0; j < M; ++j) s += P(i, j);
116 if (std::fabs(num_traits<T>::to_double(s) - 1.0) > 1e-8)
117 throw InputError(
118 "SolverCTMC(mdd): the station-to-station routing chain is not stochastic at "
119 "station " +
120 std::to_string(i + 1) +
121 "; the mdd method needs every completion to move the job to another station");
122 }
123 return P;
124}
125
126/** Disciplines under which every job present is in service, each with its own phase. */
127inline bool mdd_is_shared(SchedStrategy s) {
128 return s == SchedStrategy::PS || s == SchedStrategy::DPS || s == SchedStrategy::GPS ||
129 s == SchedStrategy::INF;
130}
131
132/** Disciplines the count-plus-one-phase local state represents, single server only. */
133inline bool mdd_is_nonpreemptive(SchedStrategy s, double servers) {
134 return (s == SchedStrategy::FCFS || s == SchedStrategy::LCFS || s == SchedStrategy::SIRO ||
135 s == SchedStrategy::HOL) &&
136 servers == 1.0;
137}
138
139
140/**
141 * The Petri-net route of the `mdd` method: `spn_mdd` supplies the reachable set
142 * and the Kronecker descriptor, `mdd_mcd` aggregates, and the measures are read
143 * back per place.
144 *
145 * The approximation is the same Eq. 5 as for a queueing network, and it is exact
146 * on a product-form net, which `solver_nc_spn_analyzer` solves exactly and far
147 * more cheaply -- the aggregation earns its place on the nets that have NO
148 * product form.
149 *
150 * A net carries no per-station service rate, so `mdd_mcd` returns only the level
151 * marginals. The token throughput is assembled here from the mode rates and
152 * those marginals, under the SAME independence across levels that the
153 * aggregation already assumes: it is the method's own approximation applied once
154 * more, not a second one layered on top. TN counts FIRING EVENTS, which is what
155 * the explicit CTMC path reports and what `sn_pn_avg_rates` converts to a token
156 * rate afterwards.
157 */
158template <class T>
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;
163
164 spn::SpnOptions mddopt;
165 const spn::SpnResult<T> net = spn::spn_mdd<T>(sn, 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;
168
169 CtmcMddSolution<T> sol;
170 sol.avg.QN = Matrix<T>(M, R, zero);
171 sol.avg.UN = Matrix<T>(M, R, zero);
172 sol.avg.RN = Matrix<T>(M, R, zero);
173 sol.avg.TN = Matrix<T>(M, R, zero);
174 sol.avg.XN.assign(R, zero);
175 sol.avg.CN.assign(R, zero);
176 sol.avg.StartN = Matrix<T>(M, R, zero);
177 sol.avg.PreemptN = Matrix<T>(M, R, zero);
178
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]; // a Place is an INF station: U = Q
184 }
185
186 // P(level l = v) from the converged level chains; mdd_mcd works in the
187 // paper's orientation, paper level k = level K+1-l.
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];
194 T tot = zero;
195 for (std::size_t v = 0; v < pl[l].size(); ++v) tot += pl[l][v];
196 if (tot > zero)
197 for (std::size_t v = 0; v < pl[l].size(); ++v) pl[l][v] = T(pl[l][v] / tot);
198 }
199
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; // no single rate; read the phase level
203 // E[min(enabling degree, servers)] under independence across the inputs
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);
208 if (!lv.empty()) {
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);
213 nsrv = zero;
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()) {
220 ge = zero;
221 break;
222 }
223 T s = zero;
224 for (std::size_t v = thr; v < pl[l].size(); ++v) s += pl[l][v];
225 ge = T(ge * s);
226 }
227 nsrv += ge; // E[min] = sum_k P(min >= k)
228 }
229 }
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);
235 }
236 }
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));
240
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);
243 T Nk = zero;
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]);
246
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";
253 return sol;
254}
255
256} // namespace mdd_analyzer_detail
257
258/**
259 * Can the `mdd` decision-diagram method be asked for this model?
260 *
261 * The model-shape gate asked as a predicate rather than thrown.
262 * `solver_ctmc_mdd_analyzer` refuses with it before it builds anything, and a
263 * REPORT reaches the same call, so a pair the report offers is a pair the
264 * method runs. One predicate with two callers is what stops the two from
265 * disagreeing about which models the method serves.
266 *
267 * A STOCHASTIC PETRI NET IS EXEMPT: a Place model is read through
268 * `spn_route`, which builds the reachable set and the Kronecker descriptor from
269 * the marking rather than from the (station,class) encoding, so neither the
270 * single-class rule nor the closed-population rule applies to it.
271 *
272 * The deeper refusals the analyzer still raises -- a station-to-station chain
273 * that is not stochastic, and a phase-type law at a discipline neither local
274 * encoding represents -- are not restated here: they are decided from
275 * quantities the analyzer computes on its way through, not from the model
276 * shape, so a caller cannot be told about them without doing the work.
277 *
278 * @param sn the refreshed struct of the model
279 * @return an empty string when the method may run, else the refusal
280 */
281template <class T>
283 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind)
284 if (sn.nodes[ind].nodetype == NodeType::Place) return std::string();
285 // A FORK-JOIN MODEL IS NEITHER of the two shapes this method serves. The
286 // tag augmentation a fork needs adds one auxiliary class per branch, so the
287 // struct that reaches the analyzer is never single-class however the model
288 // was written, and the level decomposition has no meaning for a firing that
289 // does not conserve the per-chain population. Also stated in
290 // `qn::ctmc_feature_set("mdd")`, which drops the Fork/Join names.
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 "
304 "diagram";
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();
309}
310
311/**
312 * Solve with the `mdd` method.
313 *
314 * @param sn the refreshed struct of a CLOSED single-class network
315 * @param opt the SolverCTMC knobs; only `method` is read, the state-space ones
316 * having no meaning for a solve that enumerates nothing
317 * @param mcdopt the level-iteration knobs, an INNER numerical solve whose
318 * tolerance must stay far tighter than any solver-level `iter_tol`
319 */
320template <class T>
322 const mdd::MddMcdOptions& mcdopt =
324 // EVERY STEP HERE IS A FIELD OPERATION, including the level solve.
325 // `mcd_solve_stat` picks its backend by the arithmetic: Householder QR in
326 // floating point, where the reflector norms are square roots and the point
327 // is to avoid squaring the condition number, and `line::lstsq` under exact
328 // arithmetic, where there is no condition number to square. So `--method
329 // mdd` runs under `--arith exact` like `--method default`, and returns the
330 // exact rational fixed point of the level system. The one thing exactness
331 // does NOT buy is agreement with the enumerated chain: the level
332 // aggregation is an approximation away from product form whatever the
333 // arithmetic, and `no_aggregation` is what certifies otherwise.
334 const T zero = num_traits<T>::from_int(0);
335 const std::size_t M = sn.nstations, R = sn.nclasses;
336
337 // The model-shape rules live in `solver_ctmc_mdd_supports`, which a report
338 // asks too: the analyzer must refuse exactly what the report refuses, and
339 // one predicate with two callers is what keeps the two from drifting apart.
340 // All three come back as one exception type now; the population case used to
341 // be an InputError, and no catch site anywhere distinguishes the two.
342 const std::string shape_reason = solver_ctmc_mdd_supports(sn);
343 if (!shape_reason.empty()) throw UnsupportedError(shape_reason);
344
345 // A net holding Places takes the SPN translation instead of mdd_descriptor:
346 // the levels are places, the measures come back per place, and the same Eq. 5
347 // approximation applies. See mdd_analyzer_detail::spn_route.
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);
351
352 const std::vector<double> njobs = sn.njobs();
353 const int N = static_cast<int>(njobs[0]);
354
355 // ---- service laws: the rate by LINE convention, the (D0,D1) pair when the
356 // station is phase-type. A one-phase entry stays exponential so that the
357 // descriptor takes its compact index = population encoding.
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);
362 bool any_ph = false;
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;
367 const lang::Distrib<T>& d = sn.service[i][0];
368 if (d.phases() > 1 && d.D0.rows() > 0) {
369 proc[i].D0 = d.D0;
370 proc[i].D1 = d.D1;
371 proc[i].present = true;
372 any_ph = true;
373 }
374 }
375
376 // ---- which local encoding represents these disciplines exactly
377 std::string encoding = "np";
378 if (any_ph) {
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;
382 if (all_shared) {
383 encoding = "ps";
384 } else {
385 // Every PHASE-TYPE station must be non-preemptive for the compact
386 // encoding; a shared-server one among them is the mixed case, which
387 // no single encoding covers.
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;
396 }
397 if (!all_np_at_ph) {
398 if (bad == M) bad = first_ph;
399 throw UnsupportedError(
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");
406 }
407 }
408 }
409
410 const Matrix<T> P = mdd_analyzer_detail::mdd_station_routing(sn);
411
412 // ---- descriptor, reachable set, level aggregation
414 if (encoding == "ps") {
415 desc = mdd::mdd_ps(mu, P, servers, N, proc);
416 } else {
417 std::vector<std::string> schedname(M);
418 for (std::size_t i = 0; i < M; ++i) schedname[i] = lang::sched_to_text(sched[i]);
419 desc = mdd::mdd_descriptor(mu, P, servers, N, proc, schedname);
420 }
421 const mdd::MDD diagram = mdd::mdd_reachset(desc.domain, desc.init, desc.nextfun);
422 const mdd::MddMcdResult<T> out = mdd::mdd_mcd(diagram.to_struct(), desc, mcdopt);
423
424 // ---- pack the analyzer contract
426 s.avg.QN = Matrix<T>(M, R, zero);
427 s.avg.UN = Matrix<T>(M, R, zero);
428 s.avg.RN = Matrix<T>(M, R, zero);
429 s.avg.TN = Matrix<T>(M, R, zero);
430 s.avg.XN.assign(R, zero);
431 s.avg.CN.assign(R, zero);
432 for (std::size_t i = 0; i < M; ++i) {
433 s.avg.QN(i, 0) = out.QLen[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]); // Little's law
437 }
438
439 // System throughput at the reference station, per unit visit. `visits` is
440 // indexed by STATEFUL node, so the station index has to be converted; a
441 // model with a Router has more nodes than stations and reading the station
442 // index straight into that array would charge the wrong row.
443 const std::size_t ref = sn.classes[0].refstat;
444 T vis = num_traits<T>::from_int(1);
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);
448 }
449 s.avg.XN[0] = T(s.avg.TN(ref - 1, 0) / vis);
450 if (s.avg.XN[0] > zero) s.avg.CN[0] = T(num_traits<T>::from_int(N) / s.avg.XN[0]);
451
452 s.num_states = diagram.cardinality();
453 s.level_sizes = out.level_sizes;
454 s.iters = out.iters;
457 s.encoding = encoding;
458 s.actualmethod = opt.method.empty() ? std::string("mdd") : opt.method;
459 return s;
460}
461
462/** Solve with `mdd` and format, so a caller with no use for the diagram has one call. */
463template <class T>
472
473} // namespace ctmc
474} // namespace line
475
476#endif // LINE_SOLVERS_CTMC_SOLVER_CTMC_MDD_ANALYZER_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
The diagram: insert / member / index / enumerate / cardinality.
Definition mdd.h:97
long long cardinality() const
|S|, the number of stored states.
Definition mdd.h:145
MddStruct to_struct() const
Export the diagram as plain arrays for downstream algorithms.
Definition mdd.h:181
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.
Definition lang_types.h:181
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:230
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.
Definition mdd_ps.h:242
MddMcdResult< T > mdd_mcd(const MddStruct &mdds, const MddDescriptor< T > &desc, const MddMcdOptions &options=MddMcdOptions())
Approximate stationary measures by decision-diagram-guided aggregation.
Definition mdd_mcd.h:384
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.
Definition spn_mdd.h:394
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.
Definition lang_types.h:759
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.
Definition mdd_types.h:165
MddNextState nextfun
Successor function over local indices.
Definition mdd_types.h:191
std::vector< int > domain
Local domain per level.
Definition mdd_types.h:171
std::vector< int > init
Initial local index per level.
Definition mdd_types.h:189
Knobs of the level iteration in mdd_mcd.
Definition mdd_types.h:212
Result of the Miner-Ciardo-Donatelli level aggregation.
Definition mdd_types.h:229
std::vector< std::size_t > level_sizes
|M_k| per paper level.
Definition mdd_types.h:241
bool no_aggregation
True certifies the result is EXACT with no reference solve needed; false means "not certified by this...
Definition mdd_types.h:255
int iters
Fixed-point iterations performed.
Definition mdd_types.h:243
std::vector< T > QLen
Mean occupancy per station (or place), in station order.
Definition mdd_types.h:231
std::vector< double > paths_per_level
max |A(p)| per paper level: the largest number of distinct root-to-node paths at that level.
Definition mdd_types.h:249
std::vector< T > X
Per-station throughput; empty when the descriptor carries no queueing parameters.
Definition mdd_types.h:233
std::vector< T > U
Per-station utilization; empty when the descriptor carries no queueing parameters.
Definition mdd_types.h:235
The metrics getAvg returns, after filtering.