5#ifndef LINE_API_SPN_SPN_MDD_H
6#define LINE_API_SPN_SPN_MDD_H
107 std::function<T(
const std::vector<T>&)>
dep;
180 bool apply_degree,
const T& scale) {
182 for (
int i = 0; i < d; ++i) {
183 if (!(
static_cast<double>(i) >= mde.
enab[l] &&
static_cast<double>(i) < mde.
inhib[l]))
185 const int j = i +
static_cast<int>(net);
186 if (j < 0 || j > d - 1)
continue;
188 if (apply_degree && mde.
enab[l] > 0) {
189 const double deg = std::floor(
static_cast<double>(i) / mde.
enab[l]);
190 val = deg < mde.
srv ? deg : mde.
srv;
192 bld.
add(
static_cast<std::size_t
>(i),
static_cast<std::size_t
>(j),
199inline long spn_gcd_vec(
const std::vector<double>& a,
const std::vector<double>& b) {
201 const std::vector<double>* all[2] = {&a, &b};
202 for (
int i = 0; i < 2; ++i)
203 for (std::size_t k = 0; k < all[i]->size(); ++k) {
204 const double x = (*all[i])[k];
205 if (std::fabs(x - std::rint(x)) > 1e-9)
return 0;
206 long y = std::labs(
static_cast<long>(std::rint(x)));
208 const long t = g % y;
220inline void spn_minimal_support(std::vector<std::vector<double>>& M,
221 std::vector<std::vector<double>>& B, std::size_t L) {
222 const std::size_t n = B.size();
223 std::vector<bool> drop(n,
false);
224 for (std::size_t i = 0; i < n; ++i) {
225 if (drop[i])
continue;
226 for (std::size_t j = 0; j < n; ++j) {
227 if (i == j || drop[j])
continue;
228 bool contained =
true, strict =
false;
229 for (std::size_t l = 0; l < L && contained; ++l) {
230 const bool si = std::fabs(B[i][l]) > 1e-12;
231 const bool sj = std::fabs(B[j][l]) > 1e-12;
232 if (sj && !si) contained =
false;
233 if (si && !sj) strict =
true;
235 if (contained && strict) {
241 std::vector<std::vector<double>> Mk,
Bk;
242 for (std::size_t i = 0; i < n; ++i)
263inline bool spn_place_invariant(
const std::vector<std::vector<double>>& netm, std::size_t L,
264 std::vector<double>& w) {
265 const std::size_t E = netm.size();
266 bool conservative =
true;
267 for (std::size_t e = 0; e < E; ++e) {
269 for (std::size_t l = 0; l < L; ++l) s += netm[e][l];
270 if (std::fabs(s) > 1e-12) {
271 conservative =
false;
279 if (E == 0)
return false;
281 std::vector<std::vector<double>> M(L, std::vector<double>(E, 0.0));
282 std::vector<std::vector<double>> B(L, std::vector<double>(L, 0.0));
283 for (std::size_t l = 0; l < L; ++l) {
284 for (std::size_t e = 0; e < E; ++e) M[l][e] = netm[e][l];
288 for (std::size_t e = 0; e < E; ++e) {
289 std::vector<std::vector<double>> Mn, Bn;
290 for (std::size_t i = 0; i < M.size(); ++i)
291 if (std::fabs(M[i][e]) < 1e-12) {
295 for (std::size_t a = 0; a < M.size(); ++a) {
296 if (!(M[a][e] > 1e-12))
continue;
297 for (std::size_t b = 0; b < M.size(); ++b) {
298 if (!(M[b][e] < -1e-12))
continue;
299 const double ca = -M[b][e], cb = M[a][e];
300 std::vector<double> c(E, 0.0), d(L, 0.0);
301 for (std::size_t k = 0; k < E; ++k) c[k] = ca * M[a][k] + cb * M[b][k];
302 for (std::size_t k = 0; k < L; ++k) d[k] = ca * B[a][k] + cb * B[b][k];
303 const long g = spn_gcd_vec(c, d);
305 for (std::size_t k = 0; k < E; ++k) c[k] /=
static_cast<double>(g);
306 for (std::size_t k = 0; k < L; ++k) d[k] /=
static_cast<double>(g);
312 spn_minimal_support(Mn, Bn, L);
317 if (B.empty())
return false;
318 std::vector<double> s(L, 0.0);
319 for (std::size_t i = 0; i < B.size(); ++i)
320 for (std::size_t l = 0; l < L; ++l) s[l] += B[i][l];
321 double mn = std::numeric_limits<double>::max();
322 for (std::size_t l = 0; l < L; ++l) {
323 if (!(s[l] > 1e-9))
return false;
324 mn = std::min(mn, s[l]);
327 for (std::size_t l = 0; l < L; ++l) w[l] = s[l] / mn;
333void spn_successors(
const std::vector<int>& s,
const std::vector<SpnMode<T>>& md,
334 const std::vector<std::vector<double>>& netm,
335 const std::vector<std::size_t>& phaseof,
const std::vector<int>& domain,
336 std::size_t L, std::vector<std::vector<int>>& out,
337 std::vector<bool>& ever_disabled) {
338 const T zero = num_traits<T>::from_int(0);
339 for (std::size_t e = 0; e < md.size(); ++e) {
340 const SpnMode<T>& mde = md[e];
342 for (std::size_t l = 0; l < L; ++l)
343 if (
static_cast<double>(s[l]) < mde.enab[l] ||
344 static_cast<double>(s[l]) >= mde.inhib[l]) {
349 ever_disabled[e] =
true;
352 if (phaseof[e] == 0) {
353 std::vector<int> t = s;
355 for (std::size_t l = 0; l < L; ++l) {
356 const int nv = s[l] +
static_cast<int>(netm[e][l]);
357 if (nv < 0 || nv > domain[l] - 1) {
363 if (ok) out.push_back(t);
365 const std::size_t q = phaseof[e] - 1;
367 for (std::size_t j = 0; j < mde.nph; ++j)
368 if (
static_cast<int>(j) != ph && mde.D0(ph, j) != zero) {
369 std::vector<int> t = s;
370 t[q] =
static_cast<int>(j);
373 std::vector<int> moved = s;
375 for (std::size_t l = 0; l < L; ++l) {
376 const int nv = s[l] +
static_cast<int>(netm[e][l]);
377 if (nv < 0 || nv > domain[l] - 1) {
384 for (std::size_t j = 0; j < mde.nph; ++j)
385 if (mde.D1(ph, j) != zero) {
386 std::vector<int> t = moved;
387 t[q] =
static_cast<int>(j);
405 const double inf = std::numeric_limits<double>::infinity();
407 std::vector<std::size_t> places, transitions;
408 for (std::size_t i = 1; i <=
sn.nodes.size(); ++i) {
412 if (places.empty() || transitions.empty())
413 throw InputError(
"spn_mdd: the model holds no Place or no Transition node");
414 const std::size_t P = places.size();
415 const std::size_t R =
sn.nclasses;
416 const std::size_t L = P * R;
419 std::vector<SpnMode<T>> md;
420 for (std::size_t t = 0; t < transitions.size(); ++t) {
421 const std::size_t ind = transitions[t];
422 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
423 sn.transparam.find(ind);
424 if (it ==
sn.transparam.end())
continue;
426 for (std::size_t m = 0; m < tp.
nmodes; ++m) {
429 std::to_string(ind) +
430 " is IMMEDIATE; vanishing states must be eliminated before "
431 "the net has a Kronecker rate descriptor");
434 std::to_string(ind) +
435 " has a marking-dependent firing rate; g(marking) is not a "
436 "product of per-level terms");
438 throw InputError(
"spn_mdd: mode " + std::to_string(m + 1) +
" of node " +
439 std::to_string(ind) +
" has no firing process");
443 e.
enab.assign(L, 0.0);
444 e.
inhib.assign(L, inf);
445 e.
fire.assign(L, 0.0);
451 for (std::size_t pp = 0; pp < P; ++pp) {
452 const std::size_t q = places[pp] - 1;
453 for (std::size_t k = 0; k < R; ++k) {
454 const std::size_t l = pp * R + k;
455 if (en && q < en->rows() && k < en->cols())
457 if (ih && q < ih->rows() && k < ih->cols())
459 if (fi && q < fi->rows() && k < fi->cols())
470 if (!options.descriptor && e.
nph > 1)
472 std::to_string(ind) +
473 " has a phase-type firing time; the reachable-set-only "
474 "mode carries no phase level, and a product-form marking "
475 "process must be memoryless in the marking alone");
479 const std::size_t E = md.size();
480 for (std::size_t e = 0; options.descriptor && e < E; ++e) {
482 for (std::size_t l = 0; l < L; ++l)
483 if (md[e].enab[l] != 0) ++nz;
484 if (md[e].srv != 1 && nz > 1)
486 " of node " + std::to_string(md[e].trans) +
" has " +
487 std::to_string(md[e].srv) +
" servers and draws from " +
489 " levels; the enabling degree min_l floor(m(l)/enab(l)) is then "
490 "not a product of per-level terms and admits no Kronecker form");
491 if (md[e].srv != 1 && nz == 0)
492 throw InputError(
"spn_mdd: mode " + std::to_string(md[e].mode + 1) +
" of node " +
493 std::to_string(md[e].trans) +
" has " + std::to_string(md[e].srv) +
494 " servers but consumes from no place, so its enabling degree is "
495 "unbounded and its firing rate undefined");
499 std::vector<std::size_t> phaseof(E, 0);
501 for (std::size_t e = 0; options.descriptor && e < E; ++e)
506 const std::size_t K = L + Q;
508 std::vector<std::vector<double>> netm(E, std::vector<double>(L, 0.0));
509 for (std::size_t e = 0; e < E; ++e)
510 for (std::size_t l = 0; l < L; ++l) netm[e][l] = md[e].fire[l] - md[e].enab[l];
513 std::vector<double> init0(L, 0.0);
514 if (!options.init.empty()) {
515 if (options.init.size() != L)
516 throw InputError(
"spn_mdd: options.init must hold one token count per place level");
517 init0 = options.init;
523 for (std::size_t pp = 0; pp < P; ++pp) {
524 const typename std::map<std::size_t, std::vector<T>>::const_iterator it =
525 sn.initmarking.find(places[pp]);
526 if (it ==
sn.initmarking.end())
continue;
528 for (std::size_t k = 0; k < R && k < it->second.size(); ++k)
532 for (std::size_t l = 0; l < L && allzero; ++l) allzero = init0[l] == 0.0;
533 if (!anyset || allzero) {
534 std::fill(init0.begin(), init0.end(), 0.0);
535 for (std::size_t k = 0; k < R; ++k) {
536 const double njobs =
sn.classes[k].population;
537 if (!std::isfinite(njobs))
539 " is open; an unbounded token population has no "
540 "finite place level");
541 const std::size_t ref_node =
sn.station_to_node[
sn.classes[k].refstat - 1];
542 for (std::size_t pp = 0; pp < P; ++pp)
543 if (places[pp] == ref_node) init0[pp * R + k] += njobs;
547 std::vector<double> winv;
548 const bool has_inv = detail::spn_place_invariant(netm, L, winv);
551 for (std::size_t l = 0; l < L; ++l) vinv += winv[l] * init0[l];
553 std::vector<double> bound(L, 0.0);
554 if (!options.bound.empty()) {
555 for (std::size_t l = 0; l < L; ++l)
556 bound[l] = options.bound.size() == 1 ? options.bound[0] : options.bound[l];
557 }
else if (has_inv) {
558 for (std::size_t l = 0; l < L; ++l)
559 bound[l] = winv[l] > 0 ? std::floor(vinv / winv[l]) : vinv;
562 "marking is not bounded a priori; pass options.bound");
565 std::vector<int> domain(K, 1);
566 for (std::size_t l = 0; l < L; ++l) domain[l] = static_cast<int>(bound[l]) + 1;
567 for (std::size_t e = 0; e < E; ++e)
568 if (phaseof[e] > 0) domain[phaseof[e] - 1] =
static_cast<int>(md[e].nph);
570 std::vector<int> init(K, 0);
571 for (std::size_t l = 0; l < L; ++l) init[l] = static_cast<int>(init0[l]);
572 for (std::size_t e = 0; e < E; ++e)
574 for (std::size_t a = 0; a < md[e].nph; ++a)
575 if (md[e].pie[a] > zero) {
576 init[phaseof[e] - 1] =
static_cast<int>(a);
582 std::vector<bool> ever_disabled(E,
false);
585 std::vector<std::vector<int>> frontier;
586 frontier.push_back(init);
587 std::size_t head = 0;
588 while (head < frontier.size()) {
589 const std::vector<int> s = frontier[head];
591 std::vector<std::vector<int>> succ;
592 detail::spn_successors(s, md, netm, phaseof, domain, L, succ, ever_disabled);
593 for (std::size_t r = 0; r < succ.size(); ++r)
594 if (!diagram.
member(succ[r])) {
596 frontier.push_back(succ[r]);
598 if (head > 1024 && 2 * head > frontier.size()) {
599 frontier.erase(frontier.begin(), frontier.begin() +
static_cast<long>(head));
605 for (std::size_t e = 0; e < E; ++e)
606 if (phaseof[e] > 0 && options.descriptor && ever_disabled[e] &&
607 options.phmemory !=
"resume" &&
608 options.phmemory !=
"RESUME")
610 "spn_mdd: mode " + std::to_string(md[e].mode + 1) +
" of node " +
611 std::to_string(md[e].trans) +
612 " has a phase-type firing time AND is disabled in some reachable marking. LINE "
613 "discards the phase on disabling (preemptive repeat) but that reset is a joint "
614 "condition on the place levels and has no Kronecker form, so this descriptor would "
615 "encode preemptive resume instead and disagree with SolverCTMC. Pass "
616 "phmemory=\"resume\" to accept the resume semantics.");
620 for (std::size_t e = 0; options.descriptor && e < E; ++e) {
622 std::set<std::size_t> gate, touched;
623 for (std::size_t l = 0; l < L; ++l)
624 if (mde.
enab[l] > 0 || std::isfinite(mde.
inhib[l])) {
628 for (std::size_t l = 0; l < L; ++l)
629 if (netm[e][l] != 0) touched.insert(l);
630 const std::vector<std::size_t> touched_sorted(touched.begin(), touched.end());
633 for (std::size_t l = 0; l < L; ++l)
634 if (mde.
enab[l] > 0) {
635 degl =
static_cast<long>(l);
639 if (phaseof[e] == 0) {
640 if (touched_sorted.empty())
continue;
644 for (std::size_t t = 0; t < touched_sorted.size(); ++t) {
645 const std::size_t l = touched_sorted[t];
646 event.lev.push_back(l);
647 event.W.push_back(detail::spn_placemat(l, mde, netm[e][l], domain[l],
648 static_cast<long>(l) == degl, one));
651 const std::size_t l0 = touched_sorted[0];
652 event.W[0] = detail::spn_placemat(l0, mde, netm[e][l0], domain[l0],
653 static_cast<long>(l0) == degl, mde.
D1(0, 0));
654 desc.events.push_back(event);
656 const std::size_t q = phaseof[e] - 1;
658 std::size_t nnz_off = 0;
659 for (std::size_t a = 0; a < mde.
nph; ++a)
660 for (std::size_t b = 0; b < mde.
nph; ++b)
661 if (a != b && mde.
D0(a, b) != zero) ++nnz_off;
666 for (std::set<std::size_t>::const_iterator g = gate.begin(); g != gate.end();
668 event.lev.push_back(*g);
670 detail::spn_placemat(*g, mde, 0.0, domain[*g],
false, one));
673 for (std::size_t a = 0; a < mde.
nph; ++a)
674 for (std::size_t b = 0; b < mde.
nph; ++b)
675 if (a != b) bld.
add(a, b, mde.
D0(a, b));
676 event.lev.push_back(q);
677 event.W.push_back(bld.
build());
678 desc.events.push_back(event);
684 for (std::size_t t = 0; t < touched_sorted.size(); ++t) {
685 const std::size_t l = touched_sorted[t];
686 event.lev.push_back(l);
687 event.W.push_back(detail::spn_placemat(l, mde, netm[e][l], domain[l],
688 static_cast<long>(l) == degl, one));
691 for (std::size_t a = 0; a < mde.
nph; ++a)
692 for (std::size_t b = 0; b < mde.
nph; ++b) bld.
add(a, b, mde.
D1(a, b));
693 event.lev.push_back(q);
694 event.W.push_back(bld.
build());
695 desc.events.push_back(event);
701 desc.domain = domain;
703 desc.invariant_weights.assign(K, 0.0);
704 for (std::size_t l = 0; l < L; ++l) desc.invariant_weights[l] = winv[l];
705 desc.invariant_value = vinv;
711 for (std::size_t pp = 0; pp < P; ++pp) info.
placenames.push_back(
sn.nodes[places[pp] - 1].name);
712 for (std::size_t k = 0; k < R; ++k) info.
classnames.push_back(
sn.classes[k].name);
715 for (std::size_t l = 0; l < L; ++l) info.
levelkind[l] = 1;
717 for (std::size_t pp = 0; pp < P; ++pp)
718 for (std::size_t k = 0; k < R; ++k)
720 for (std::size_t e = 0; e < E; ++e)
722 info.
levelname[phaseof[e] - 1] =
"phase(" +
sn.nodes[md[e].trans - 1].name +
".m" +
723 std::to_string(md[e].mode + 1) +
")";
733 if (options.verbose) {
734 std::printf(
"\nSPN -> MDD: %zu places x %zu classes = %zu place levels, %zu phase levels\n",
736 std::string bstr =
"[";
737 for (std::size_t l = 0; l < L; ++l)
738 bstr += (l > 0 ?
" " :
"") + std::to_string(
static_cast<long long>(bound[l]));
740 std::printf(
" modes = %zu, |S| = %lld, bounds = %s\n", E, diagram.
cardinality(),
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.
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)
The (D0,D1) pair that reaches sn.proc.
@ 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.
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.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
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::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 Transition node: MATLAB sn.nodeparam{ind} for an SPN transition,...
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::size_t nclasses
R, the stride of the place-major place levels (level l = pp * R + k).
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::string > classnames
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).
bool verbose
Print the level and state counts, as the reference's options.verbose does.
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 (P * R, place-major); empty reads the model.
Descriptor, diagram and metadata returned together.
mdd::MddDescriptor< T > desc