5#ifndef LINE_API_SPN_SPN_MDD_H
6#define LINE_API_SPN_SPN_MDD_H
103 std::function<T(
const std::vector<T>&)>
dep;
171 bool apply_degree,
const T& scale) {
173 for (
int i = 0; i < d; ++i) {
174 if (!(
static_cast<double>(i) >= mde.
enab[l] &&
static_cast<double>(i) < mde.
inhib[l]))
176 const int j = i +
static_cast<int>(net);
177 if (j < 0 || j > d - 1)
continue;
179 if (apply_degree && mde.
enab[l] > 0) {
180 const double deg = std::floor(
static_cast<double>(i) / mde.
enab[l]);
181 val = deg < mde.
srv ? deg : mde.
srv;
183 bld.
add(
static_cast<std::size_t
>(i),
static_cast<std::size_t
>(j),
190inline long spn_gcd_vec(
const std::vector<double>& a,
const std::vector<double>& b) {
192 const std::vector<double>* all[2] = {&a, &b};
193 for (
int i = 0; i < 2; ++i)
194 for (std::size_t k = 0; k < all[i]->size(); ++k) {
195 const double x = (*all[i])[k];
196 if (std::fabs(x - std::rint(x)) > 1e-9)
return 0;
197 long y = std::labs(
static_cast<long>(std::rint(x)));
199 const long t = g % y;
211inline void spn_minimal_support(std::vector<std::vector<double>>& M,
212 std::vector<std::vector<double>>& B, std::size_t L) {
213 const std::size_t n = B.size();
214 std::vector<bool> drop(n,
false);
215 for (std::size_t i = 0; i < n; ++i) {
216 if (drop[i])
continue;
217 for (std::size_t j = 0; j < n; ++j) {
218 if (i == j || drop[j])
continue;
219 bool contained =
true, strict =
false;
220 for (std::size_t l = 0; l < L && contained; ++l) {
221 const bool si = std::fabs(B[i][l]) > 1e-12;
222 const bool sj = std::fabs(B[j][l]) > 1e-12;
223 if (sj && !si) contained =
false;
224 if (si && !sj) strict =
true;
226 if (contained && strict) {
232 std::vector<std::vector<double>> Mk,
Bk;
233 for (std::size_t i = 0; i < n; ++i)
254inline bool spn_place_invariant(
const std::vector<std::vector<double>>& netm, std::size_t L,
255 std::vector<double>& w) {
256 const std::size_t E = netm.size();
257 bool conservative =
true;
258 for (std::size_t e = 0; e < E; ++e) {
260 for (std::size_t l = 0; l < L; ++l) s += netm[e][l];
261 if (std::fabs(s) > 1e-12) {
262 conservative =
false;
270 if (E == 0)
return false;
272 std::vector<std::vector<double>> M(L, std::vector<double>(E, 0.0));
273 std::vector<std::vector<double>> B(L, std::vector<double>(L, 0.0));
274 for (std::size_t l = 0; l < L; ++l) {
275 for (std::size_t e = 0; e < E; ++e) M[l][e] = netm[e][l];
279 for (std::size_t e = 0; e < E; ++e) {
280 std::vector<std::vector<double>> Mn, Bn;
281 for (std::size_t i = 0; i < M.size(); ++i)
282 if (std::fabs(M[i][e]) < 1e-12) {
286 for (std::size_t a = 0; a < M.size(); ++a) {
287 if (!(M[a][e] > 1e-12))
continue;
288 for (std::size_t b = 0; b < M.size(); ++b) {
289 if (!(M[b][e] < -1e-12))
continue;
290 const double ca = -M[b][e], cb = M[a][e];
291 std::vector<double> c(E, 0.0), d(L, 0.0);
292 for (std::size_t k = 0; k < E; ++k) c[k] = ca * M[a][k] + cb * M[b][k];
293 for (std::size_t k = 0; k < L; ++k) d[k] = ca * B[a][k] + cb * B[b][k];
294 const long g = spn_gcd_vec(c, d);
296 for (std::size_t k = 0; k < E; ++k) c[k] /=
static_cast<double>(g);
297 for (std::size_t k = 0; k < L; ++k) d[k] /=
static_cast<double>(g);
303 spn_minimal_support(Mn, Bn, L);
308 if (B.empty())
return false;
309 std::vector<double> s(L, 0.0);
310 for (std::size_t i = 0; i < B.size(); ++i)
311 for (std::size_t l = 0; l < L; ++l) s[l] += B[i][l];
312 double mn = std::numeric_limits<double>::max();
313 for (std::size_t l = 0; l < L; ++l) {
314 if (!(s[l] > 1e-9))
return false;
315 mn = std::min(mn, s[l]);
318 for (std::size_t l = 0; l < L; ++l) w[l] = s[l] / mn;
324void spn_successors(
const std::vector<int>& s,
const std::vector<SpnMode<T>>& md,
325 const std::vector<std::vector<double>>& netm,
326 const std::vector<std::size_t>& phaseof,
const std::vector<int>& domain,
327 std::size_t L, std::vector<std::vector<int>>& out,
328 std::vector<bool>& ever_disabled) {
329 const T zero = num_traits<T>::from_int(0);
330 for (std::size_t e = 0; e < md.size(); ++e) {
331 const SpnMode<T>& mde = md[e];
333 for (std::size_t l = 0; l < L; ++l)
334 if (
static_cast<double>(s[l]) < mde.enab[l] ||
335 static_cast<double>(s[l]) >= mde.inhib[l]) {
340 ever_disabled[e] =
true;
343 if (phaseof[e] == 0) {
344 std::vector<int> t = s;
346 for (std::size_t l = 0; l < L; ++l) {
347 const int nv = s[l] +
static_cast<int>(netm[e][l]);
348 if (nv < 0 || nv > domain[l] - 1) {
354 if (ok) out.push_back(t);
356 const std::size_t q = phaseof[e] - 1;
358 for (std::size_t j = 0; j < mde.nph; ++j)
359 if (
static_cast<int>(j) != ph && mde.D0(ph, j) != zero) {
360 std::vector<int> t = s;
361 t[q] =
static_cast<int>(j);
364 std::vector<int> moved = s;
366 for (std::size_t l = 0; l < L; ++l) {
367 const int nv = s[l] +
static_cast<int>(netm[e][l]);
368 if (nv < 0 || nv > domain[l] - 1) {
375 for (std::size_t j = 0; j < mde.nph; ++j)
376 if (mde.D1(ph, j) != zero) {
377 std::vector<int> t = moved;
378 t[q] =
static_cast<int>(j);
396 const double inf = std::numeric_limits<double>::infinity();
405 "spn_mdd: the net has " + std::to_string(
sn.nclasses) +
406 " classes, and this translation puts ONE LEVEL PER PLACE; a coloured net needs a "
407 "level per (place, class) pair, whose Kronecker descriptor is not the one built "
408 "here. Solve the single-class net, or use SolverCTMC / SolverLDES, which evaluate "
409 "the per-class arcs directly.");
411 std::vector<std::size_t> places, transitions;
412 for (std::size_t i = 1; i <=
sn.nodes.size(); ++i) {
416 if (places.empty() || transitions.empty())
417 throw InputError(
"spn_mdd: the model holds no Place or no Transition node");
418 const std::size_t P = places.size();
419 const std::size_t L = P;
422 std::vector<SpnMode<T>> md;
423 for (std::size_t t = 0; t < transitions.size(); ++t) {
424 const std::size_t ind = transitions[t];
425 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
426 sn.transparam.find(ind);
427 if (it ==
sn.transparam.end())
continue;
429 for (std::size_t m = 0; m < tp.
nmodes; ++m) {
431 throw UnsupportedError(
"spn_mdd: mode " + std::to_string(m + 1) +
" of node " +
432 std::to_string(ind) +
433 " is IMMEDIATE; vanishing states must be eliminated before "
434 "the net has a Kronecker rate descriptor");
436 throw UnsupportedError(
"spn_mdd: mode " + std::to_string(m + 1) +
" of node " +
437 std::to_string(ind) +
438 " has a marking-dependent firing rate; g(marking) is not a "
439 "product of per-level terms");
441 throw InputError(
"spn_mdd: mode " + std::to_string(m + 1) +
" of node " +
442 std::to_string(ind) +
" has no firing process");
446 e.
enab.assign(L, 0.0);
447 e.
inhib.assign(L, inf);
448 e.
fire.assign(L, 0.0);
454 for (std::size_t pp = 0; pp < P; ++pp) {
455 const std::size_t q = places[pp] - 1;
467 if (!options.descriptor && e.
nph > 1)
468 throw UnsupportedError(
"spn_mdd: mode " + std::to_string(m + 1) +
" of node " +
469 std::to_string(ind) +
470 " has a phase-type firing time; the reachable-set-only "
471 "mode carries no phase level, and a product-form marking "
472 "process must be memoryless in the marking alone");
476 const std::size_t E = md.size();
477 for (std::size_t e = 0; options.descriptor && e < E; ++e) {
479 for (std::size_t l = 0; l < L; ++l)
480 if (md[e].enab[l] != 0) ++nz;
481 if (md[e].srv != 1 && nz > 1)
483 " of node " + std::to_string(md[e].trans) +
" has " +
484 std::to_string(md[e].srv) +
" servers and draws from " +
486 " levels; the enabling degree min_l floor(m(l)/enab(l)) is then "
487 "not a product of per-level terms and admits no Kronecker form");
488 if (md[e].srv != 1 && nz == 0)
489 throw InputError(
"spn_mdd: mode " + std::to_string(md[e].mode + 1) +
" of node " +
490 std::to_string(md[e].trans) +
" has " + std::to_string(md[e].srv) +
491 " servers but consumes from no place, so its enabling degree is "
492 "unbounded and its firing rate undefined");
496 std::vector<std::size_t> phaseof(E, 0);
498 for (std::size_t e = 0; options.descriptor && e < E; ++e)
503 const std::size_t K = L + Q;
505 std::vector<std::vector<double>> netm(E, std::vector<double>(L, 0.0));
506 for (std::size_t e = 0; e < E; ++e)
507 for (std::size_t l = 0; l < L; ++l) netm[e][l] = md[e].fire[l] - md[e].enab[l];
510 std::vector<double> init0(L, 0.0);
511 if (!options.init.empty()) {
512 if (options.init.size() != L)
513 throw InputError(
"spn_mdd: options.init must hold one token count per place");
514 init0 = options.init;
516 for (std::size_t r = 0; r <
sn.classes.size(); ++r) {
517 const double njobs =
sn.classes[r].population;
518 if (!std::isfinite(njobs))
520 " is open; an unbounded token population has no finite "
522 const std::size_t ref_node =
sn.station_to_node[
sn.classes[r].refstat - 1];
523 for (std::size_t pp = 0; pp < P; ++pp)
524 if (places[pp] == ref_node) init0[pp] += njobs;
527 std::vector<double> winv;
528 const bool has_inv = detail::spn_place_invariant(netm, L, winv);
531 for (std::size_t l = 0; l < L; ++l) vinv += winv[l] * init0[l];
533 std::vector<double> bound(L, 0.0);
534 if (!options.bound.empty()) {
535 for (std::size_t l = 0; l < L; ++l)
536 bound[l] = options.bound.size() == 1 ? options.bound[0] : options.bound[l];
537 }
else if (has_inv) {
538 for (std::size_t l = 0; l < L; ++l)
539 bound[l] = winv[l] > 0 ? std::floor(vinv / winv[l]) : vinv;
541 throw InputError(
"spn_mdd: the net has no place invariant with positive weights, so the "
542 "marking is not bounded a priori; pass options.bound");
545 std::vector<int> domain(K, 1);
546 for (std::size_t l = 0; l < L; ++l) domain[l] = static_cast<int>(bound[l]) + 1;
547 for (std::size_t e = 0; e < E; ++e)
548 if (phaseof[e] > 0) domain[phaseof[e] - 1] =
static_cast<int>(md[e].nph);
550 std::vector<int> init(K, 0);
551 for (std::size_t l = 0; l < L; ++l) init[l] = static_cast<int>(init0[l]);
552 for (std::size_t e = 0; e < E; ++e)
554 for (std::size_t a = 0; a < md[e].nph; ++a)
555 if (md[e].pie[a] > zero) {
556 init[phaseof[e] - 1] =
static_cast<int>(a);
562 std::vector<bool> ever_disabled(E,
false);
565 std::vector<std::vector<int>> frontier;
566 frontier.push_back(init);
567 std::size_t head = 0;
568 while (head < frontier.size()) {
569 const std::vector<int> s = frontier[head];
571 std::vector<std::vector<int>> succ;
572 detail::spn_successors(s, md, netm, phaseof, domain, L, succ, ever_disabled);
573 for (std::size_t r = 0; r < succ.size(); ++r)
574 if (!diagram.
member(succ[r])) {
576 frontier.push_back(succ[r]);
578 if (head > 1024 && 2 * head > frontier.size()) {
579 frontier.erase(frontier.begin(), frontier.begin() +
static_cast<long>(head));
585 for (std::size_t e = 0; e < E; ++e)
586 if (phaseof[e] > 0 && options.descriptor && ever_disabled[e] &&
587 options.phmemory !=
"resume" &&
588 options.phmemory !=
"RESUME")
590 "spn_mdd: mode " + std::to_string(md[e].mode + 1) +
" of node " +
591 std::to_string(md[e].trans) +
592 " has a phase-type firing time AND is disabled in some reachable marking. LINE "
593 "discards the phase on disabling (preemptive repeat) but that reset is a joint "
594 "condition on the place levels and has no Kronecker form, so this descriptor would "
595 "encode preemptive resume instead and disagree with SolverCTMC. Pass "
596 "phmemory=\"resume\" to accept the resume semantics.");
600 for (std::size_t e = 0; options.descriptor && e < E; ++e) {
602 std::set<std::size_t> gate, touched;
603 for (std::size_t l = 0; l < L; ++l)
604 if (mde.
enab[l] > 0 || std::isfinite(mde.
inhib[l])) {
608 for (std::size_t l = 0; l < L; ++l)
609 if (netm[e][l] != 0) touched.insert(l);
610 const std::vector<std::size_t> touched_sorted(touched.begin(), touched.end());
613 for (std::size_t l = 0; l < L; ++l)
614 if (mde.
enab[l] > 0) {
615 degl =
static_cast<long>(l);
619 if (phaseof[e] == 0) {
620 if (touched_sorted.empty())
continue;
624 for (std::size_t t = 0; t < touched_sorted.size(); ++t) {
625 const std::size_t l = touched_sorted[t];
626 event.lev.push_back(l);
627 event.W.push_back(detail::spn_placemat(l, mde, netm[e][l], domain[l],
628 static_cast<long>(l) == degl, one));
631 const std::size_t l0 = touched_sorted[0];
632 event.W[0] = detail::spn_placemat(l0, mde, netm[e][l0], domain[l0],
633 static_cast<long>(l0) == degl, mde.
D1(0, 0));
634 desc.
events.push_back(event);
636 const std::size_t q = phaseof[e] - 1;
638 std::size_t nnz_off = 0;
639 for (std::size_t a = 0; a < mde.
nph; ++a)
640 for (std::size_t b = 0; b < mde.
nph; ++b)
641 if (a != b && mde.
D0(a, b) != zero) ++nnz_off;
646 for (std::set<std::size_t>::const_iterator g = gate.begin(); g != gate.end();
648 event.lev.push_back(*g);
650 detail::spn_placemat(*g, mde, 0.0, domain[*g],
false, one));
653 for (std::size_t a = 0; a < mde.
nph; ++a)
654 for (std::size_t b = 0; b < mde.
nph; ++b)
655 if (a != b) bld.
add(a, b, mde.
D0(a, b));
656 event.lev.push_back(q);
657 event.W.push_back(bld.
build());
658 desc.
events.push_back(event);
664 for (std::size_t t = 0; t < touched_sorted.size(); ++t) {
665 const std::size_t l = touched_sorted[t];
666 event.lev.push_back(l);
667 event.W.push_back(detail::spn_placemat(l, mde, netm[e][l], domain[l],
668 static_cast<long>(l) == degl, one));
671 for (std::size_t a = 0; a < mde.
nph; ++a)
672 for (std::size_t b = 0; b < mde.
nph; ++b) bld.
add(a, b, mde.
D1(a, b));
673 event.lev.push_back(q);
674 event.W.push_back(bld.
build());
675 desc.
events.push_back(event);
691 for (std::size_t pp = 0; pp < P; ++pp) info.
placenames.push_back(
sn.nodes[places[pp] - 1].name);
693 for (std::size_t l = 0; l < L; ++l) info.
levelkind[l] = 1;
696 for (std::size_t e = 0; e < E; ++e)
698 info.
levelname[phaseof[e] - 1] =
"phase(" +
sn.nodes[md[e].trans - 1].name +
".m" +
699 std::to_string(md[e].mode + 1) +
")";
UnsupportedError(const std::string &what)
The diagram: insert / member / index / enumerate / cardinality.
MddStruct to_struct() const
Export the diagram as plain arrays for downstream algorithms.
void insert(const std::vector< int > &state)
Add a K-tuple of 0-based local values to the set.
void compact()
Reclaim dead nodes left by the append-only build.
bool member(const std::vector< int > &state) const
True iff state is in the set; O(K).
Incremental triplet builder; duplicate entries are accumulated.
Builder & add(std::size_t i, std::size_t j, const T &value)
Accumulate value into entry (i,j).
MddLocalMatrix< T > build() const
A network plus its refreshed NetworkStruct.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Quasi-reduced ordered Multi-valued Decision Diagram.
The rate side of the decision-diagram domain: local matrices, events, the Kronecker descriptor,...
mam::Map< T > dist_to_map(const Distrib< T > &d)
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
std::vector< T > mdd_entry_law(const std::vector< T > &given, const Matrix< T > &D1, std::size_t h, std::size_t i, const std::string &caller)
Entry law of a phase-type station, taken as given or derived from D1.
@ Bk
Birman-Kogan saddle point with bottleneck detection.
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.
A MAP as the pair of matrices (D0, D1).
std::size_t order() const
Kronecker rate descriptor of a structured model, the input of mdd_mcd.
std::vector< int > domain
Local domain per level.
int N
Closed population; the conservation law the level marginals must satisfy.
std::vector< double > invariant_weights
Optional conservation law as weights' * QLen = value, overriding the closed-population test.
double invariant_value
Value of the invariant when invariant_weights is set.
std::vector< MddEvent< T > > events
The events of the descriptor.
std::size_t K
Number of levels, i.e.
One event of the Kronecker rate descriptor.
std::size_t a
Station (or transition node) the event departs from, 0-based.
A local rate matrix W_k^e of the Kronecker descriptor, held row-compressed.
Plain-array export of an MDD, the input contract of mdd_mcd.
The parameters of a Cache node, MATLAB's sn.nodeparam{ind} for a Cache.
static std::vector< T > inhibit_total(const std::vector< Matrix< T > > &a, std::size_t m)
The inhibiting THRESHOLD of one mode per place, class blind.
static std::vector< T > arc_total(const std::vector< Matrix< T > > &a, std::size_t m)
The arcs of one mode summed over classes, for a consumer that is class blind BECAUSE THE NET IS SINGL...
std::vector< lang::TimingStrategy > timing
immediate or timed
std::vector< lang::Distrib< T > > firingproc
firing distribution per mode
std::vector< double > nmodeservers
servers per mode, may be infinite
std::vector< Matrix< T > > firing
firing[m](p,r): class-r tokens mode m moves to/from place p when it fires.
std::vector< Matrix< T > > enabling
enabling[m](p,r): class-r tokens of place p (0-based node) mode m needs.
std::vector< std::function< T(const std::vector< T > &)> > firingdep
Marking-dependent firing-rate multiplier g_m(marking); an empty entry is the unit multiplier.
std::vector< Matrix< T > > inhibiting
inhibiting[m](p,r): class-r tokens of p that BLOCK mode m (Inf = never).
Everything the caller needs alongside the descriptor.
bool descriptor
Whether the Kronecker descriptor was built.
std::vector< SpnMode< T > > modes
std::vector< std::size_t > places
1-based node indices of the places.
std::vector< std::string > levelname
std::vector< std::string > placenames
std::size_t nnodes
Node count of the model, so a firingdep argument can be rebuilt.
std::vector< std::size_t > phaseof
1-based phase level of each mode, 0 when the mode has one phase.
std::vector< bool > ever_disabled
Whether each mode was ever found disabled in a reachable marking.
std::vector< int > levelkind
1 for a place level, 2 for a phase level.
One (transition, mode) pair of the net, in level coordinates.
std::size_t trans
1-based node index of the transition.
std::vector< double > fire
Firing outcome per place level.
std::vector< double > enab
Enabling multiplicity per place level.
std::vector< double > inhib
Inhibition threshold per place level; infinite when absent.
std::function< T(const std::vector< T > &)> dep
Marking-dependent firing-rate multiplier; empty for the unit one.
std::size_t mode
Mode index within the transition, 0-based.
Options of the translation.
std::string phmemory
"exact" (default) or "resume".
bool descriptor
Build the Kronecker rate descriptor (default true).
std::vector< double > bound
Per-place-level token bound; empty infers it from a place invariant.
std::vector< double > init
Initial marking per place level; empty takes it from the reference stations.
Descriptor, diagram and metadata returned together.
mdd::MddDescriptor< T > desc