LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
spn_net.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_API_SPN_SPN_NET_H
6#define LINE_API_SPN_SPN_NET_H
7
8/**
9 * @file
10 * @ingroup api_spn
11 * Shared net model of the Petri-net property functions (spn_tinvariants,
12 * spn_siphons, spn_traps, spn_structprops, spn_reachgraph, spn_reachprops,
13 * spn_properties): the level-by-event arc matrices, the initial marking,
14 * Farkas' algorithm, the siphon enumeration and the GSPN firing rule. The
15 * MATLAB twin is matlab/src/api/spn/private/.
16 *
17 * Levels are the (place, class) pairs of `spn_sinvariants`, place-major
18 * (pp * R + k, 0-based); events are the (transition, mode) pairs in node
19 * order. Per event, pre = max(0, enabling), post = max(0, firing) and the
20 * inhibitor threshold is inhibiting (infinite: no arc).
21 */
22
23#include <algorithm>
24#include <cmath>
25#include <cstddef>
26#include <limits>
27#include <map>
28#include <string>
29#include <vector>
30
34#include "line/num/number.h"
35#include "line/util/error.h"
36#include "line/util/matrix.h"
37
38namespace line {
39namespace spn {
40namespace detail {
41
42/** Arc matrices of a net over place levels and events. */
43struct SpnNet {
44 /** 1-based node indices of the places, in level order. */
45 std::vector<std::size_t> places;
46 std::size_t R = 1;
47 std::size_t n = 0;
48 std::size_t E = 0;
49 /** 1-based node index and 0-based mode index of each event. */
50 std::vector<std::size_t> transitions, modes;
51 /** pre[l][e], post[l][e], C = post - pre, inh[l][e] (infinite: no arc). */
52 std::vector<std::vector<long long>> pre, post, C;
53 std::vector<std::vector<double>> inh;
54 std::vector<bool> immediate;
55 std::vector<double> prio, weight;
56 /** Features the structural checks ignore: inhibitor, immediate, priority. */
57 std::vector<std::string> ignored;
58
59 bool monotone() const { return ignored.empty(); }
60
61 std::vector<bool> enabled(const std::vector<long long>& m) const {
62 std::vector<bool> en(E, true);
63 for (std::size_t e = 0; e < E; ++e)
64 for (std::size_t l = 0; l < n && en[e]; ++l)
65 if (pre[l][e] > m[l] || inh[l][e] <= static_cast<double>(m[l])) en[e] = false;
66 return en;
67 }
68
69 /**
70 * Events that may fire in m: the enabled immediate events of the highest
71 * priority when any immediate event is enabled, else all enabled events.
72 */
73 std::vector<bool> fireable(const std::vector<long long>& m) const {
74 std::vector<bool> en = enabled(m);
75 bool anyimm = false;
76 double best = -std::numeric_limits<double>::infinity();
77 for (std::size_t e = 0; e < E; ++e)
78 if (en[e] && immediate[e]) {
79 anyimm = true;
80 best = std::max(best, prio[e]);
81 }
82 if (!anyimm) return en;
83 std::vector<bool> f(E, false);
84 for (std::size_t e = 0; e < E; ++e) f[e] = en[e] && immediate[e] && prio[e] == best;
85 return f;
86 }
87};
88
89inline long long spn_net_integer(double x, const char* what, const std::string& caller) {
90 const double r = std::floor(x + 0.5);
91 if (std::fabs(x - r) > 1e-9)
92 throw InputError(ErrorKind{"Model"}, caller + ": " + what +
93 " is not integral; a fractional arc multiplicity "
94 "has no Petri-net meaning");
95 return static_cast<long long>(r);
96}
97
98template <class T>
99SpnNet spn_build_net(const qn::NetworkStruct<T>& sn, const std::string& caller) {
100 std::vector<std::size_t> places, trans;
101 for (std::size_t i = 1; i <= sn.nodes.size(); ++i) {
102 if (sn.nodes[i - 1].nodetype == lang::NodeType::Place) places.push_back(i);
103 else if (sn.nodes[i - 1].nodetype == lang::NodeType::Transition) trans.push_back(i);
104 }
105 if (places.empty() || trans.empty())
106 throw InputError(ErrorKind{"Model"}, caller + ": the model holds no Place or no Transition node");
107 const std::size_t R = sn.nclasses;
108 for (std::size_t k = 0; k < R && k < sn.classes.size(); ++k)
109 if (!std::isfinite(sn.classes[k].population))
110 throw UnsupportedError(ErrorKind{"OpenClass"},
111 caller + ": class " + std::to_string(k + 1) +
112 " is open: its arrivals enter a place through a Source "
113 "routing rather than a transition, so the incidence "
114 "matrix does not describe the token game");
115 SpnNet net;
116 net.places = places;
117 net.R = R;
118 const std::size_t P = places.size();
119 net.n = P * R;
120 const std::size_t n = net.n;
121 net.pre.assign(n, std::vector<long long>());
122 net.post.assign(n, std::vector<long long>());
123 net.inh.assign(n, std::vector<double>());
124 const double inf = std::numeric_limits<double>::infinity();
125 for (std::size_t t = 0; t < trans.size(); ++t) {
126 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
127 sn.transparam.find(trans[t]);
128 if (it == sn.transparam.end()) continue;
129 const qn::TransitionParam<T>& tp = it->second;
130 for (std::size_t m = 0; m < tp.nmodes; ++m) {
131 const Matrix<T>* en = m < tp.enabling.size() ? &tp.enabling[m] : nullptr;
132 const Matrix<T>* ih = m < tp.inhibiting.size() ? &tp.inhibiting[m] : nullptr;
133 const Matrix<T>* fi = m < tp.firing.size() ? &tp.firing[m] : nullptr;
134 for (std::size_t pp = 0; pp < P; ++pp) {
135 const std::size_t q = places[pp] - 1;
136 for (std::size_t k = 0; k < R; ++k) {
137 const std::size_t l = pp * R + k;
138 double a = 0, b = 0, h = inf;
139 if (en && q < en->rows() && k < en->cols())
140 a = std::max(0.0, num_traits<T>::to_double((*en)(q, k)));
141 if (fi && q < fi->rows() && k < fi->cols())
142 b = std::max(0.0, num_traits<T>::to_double((*fi)(q, k)));
143 if (ih && q < ih->rows() && k < ih->cols())
144 h = num_traits<T>::to_double((*ih)(q, k));
145 net.pre[l].push_back(spn_net_integer(a, "an enabling arc", caller));
146 net.post[l].push_back(spn_net_integer(b, "a firing arc", caller));
147 net.inh[l].push_back(h);
148 }
149 }
150 net.transitions.push_back(trans[t]);
151 net.modes.push_back(m);
152 net.immediate.push_back(m < tp.timing.size() &&
153 tp.timing[m] == lang::TimingStrategy::IMMEDIATE);
154 net.prio.push_back(m < tp.firingprio.size() ? tp.firingprio[m] : 1.0);
155 net.weight.push_back(m < tp.fireweight.size()
156 ? num_traits<T>::to_double(tp.fireweight[m])
157 : 1.0);
158 }
159 }
160 net.E = net.transitions.size();
161 if (net.E == 0) throw InputError(ErrorKind{"Model"}, caller + ": the net has no firing mode");
162 net.C.assign(n, std::vector<long long>(net.E, 0));
163 bool anyinh = false;
164 for (std::size_t l = 0; l < n; ++l)
165 for (std::size_t e = 0; e < net.E; ++e) {
166 net.C[l][e] = net.post[l][e] - net.pre[l][e];
167 anyinh = anyinh || std::isfinite(net.inh[l][e]);
168 }
169 bool anyimm = false, anytimed = false;
170 std::vector<double> ip;
171 for (std::size_t e = 0; e < net.E; ++e) {
172 if (net.immediate[e]) {
173 anyimm = true;
174 if (std::find(ip.begin(), ip.end(), net.prio[e]) == ip.end()) ip.push_back(net.prio[e]);
175 } else {
176 anytimed = true;
177 }
178 }
179 if (anyinh) net.ignored.push_back("inhibitor");
180 if (anyimm && anytimed) net.ignored.push_back("immediate");
181 if (ip.size() > 1) net.ignored.push_back("priority");
182 return net;
183}
184
185inline long long spn_marking_integer(double x, const std::string& caller) {
186 const double r = std::floor(x + 0.5);
187 if (std::fabs(x - r) > 1e-9 || r < 0)
188 throw InputError(ErrorKind{"Argument"},
189 caller + ": an initial marking must be a non-negative integer");
190 return static_cast<long long>(r);
191}
192
193/** init if given, else the declared Place state if any, else the reference station populations. */
194template <class T>
195std::vector<long long> spn_initmarking(const qn::NetworkStruct<T>& sn, const SpnNet& net,
196 const std::vector<double>& init, const std::string& caller) {
197 const std::size_t n = net.n, R = net.R, P = net.places.size();
198 std::vector<long long> m0(n, 0);
199 if (!init.empty()) {
200 if (init.size() != n)
201 throw InputError(ErrorKind{"Argument"},
202 caller + ": init must hold one token count per place level");
203 for (std::size_t i = 0; i < n; ++i) m0[i] = spn_marking_integer(init[i], caller);
204 return m0;
205 }
206 bool anyset = false;
207 std::vector<double> dec(n, 0.0);
208 for (std::size_t pp = 0; pp < P; ++pp) {
209 const typename std::map<std::size_t, std::vector<T>>::const_iterator it =
210 sn.initmarking.find(net.places[pp]);
211 if (it == sn.initmarking.end()) continue;
212 for (std::size_t k = 0; k < R && k < it->second.size(); ++k) {
213 dec[pp * R + k] = num_traits<T>::to_double(it->second[k]);
214 anyset = anyset || dec[pp * R + k] > 0;
215 }
216 }
217 if (anyset) {
218 for (std::size_t i = 0; i < n; ++i) m0[i] = spn_marking_integer(dec[i], caller);
219 return m0;
220 }
221 for (std::size_t k = 0; k < R; ++k) {
222 const std::size_t rs = sn.classes[k].refstat;
223 if (rs < 1 || rs > sn.station_to_node.size()) continue;
224 const std::size_t ref_node = sn.station_to_node[rs - 1];
225 for (std::size_t pp = 0; pp < P; ++pp)
226 if (net.places[pp] == ref_node)
227 m0[pp * R + k] += spn_marking_integer(sn.classes[k].population, caller);
228 }
229 return m0;
230}
231
232/**
233 * Minimal-support non-negative integer rows x with x' A = 0 (Farkas on
234 * [A | I], gcd-normalised, support-minimality filter at every step).
235 */
236inline std::vector<std::vector<long long>> spn_farkas(const std::vector<std::vector<long long>>& A,
237 std::size_t nc) {
238 const std::size_t nr = A.size();
239 const std::size_t w = nc + nr;
240 std::vector<std::vector<long long>> rows(nr, std::vector<long long>(w, 0));
241 for (std::size_t p = 0; p < nr; ++p) {
242 for (std::size_t c = 0; c < nc; ++c) rows[p][c] = A[p][c];
243 rows[p][nc + p] = 1;
244 }
245 for (std::size_t c = 0; c < nc; ++c) {
246 std::vector<std::vector<long long>> nxt;
247 for (std::size_t r = 0; r < rows.size(); ++r)
248 if (rows[r][c] == 0) nxt.push_back(rows[r]);
249 for (std::size_t a = 0; a < rows.size(); ++a) {
250 if (rows[a][c] <= 0) continue;
251 for (std::size_t b = 0; b < rows.size(); ++b) {
252 if (rows[b][c] >= 0) continue;
253 const long long pa = rows[a][c], nb = -rows[b][c];
254 const long long d = spn_gcd(pa, nb);
255 std::vector<long long> combo(w, 0);
256 long long g = 0;
257 for (std::size_t k = 0; k < w; ++k) {
258 combo[k] = (nb / d) * rows[a][k] + (pa / d) * rows[b][k];
259 g = spn_gcd(g, combo[k]);
260 }
261 if (g > 1)
262 for (std::size_t k = 0; k < w; ++k) combo[k] /= g;
263 bool nz = false;
264 for (std::size_t k = 0; k < nr; ++k) nz = nz || combo[nc + k] != 0;
265 if (nz) nxt.push_back(combo);
266 }
267 }
268 std::vector<bool> keep(nxt.size(), true);
269 for (std::size_t r = 0; r < nxt.size(); ++r)
270 for (std::size_t s = 0; s < nxt.size(); ++s) {
271 if (s == r || !keep[r]) continue;
272 if (!spn_support_subset(nxt[s], nxt[r], nc, nr)) continue;
273 if (!spn_support_equal(nxt[s], nxt[r], nc, nr) || s < r) keep[r] = false;
274 }
275 rows.clear();
276 for (std::size_t r = 0; r < nxt.size(); ++r)
277 if (keep[r]) rows.push_back(nxt[r]);
278 }
279 std::vector<std::vector<long long>> X;
280 for (std::size_t r = 0; r < rows.size(); ++r) {
281 std::vector<long long> x(rows[r].begin() + static_cast<std::ptrdiff_t>(nc), rows[r].end());
282 bool nonneg = true;
283 for (std::size_t k = 0; k < nr; ++k) nonneg = nonneg && x[k] >= 0;
284 if (nonneg) X.push_back(x);
285 }
286 return X;
287}
288
289/** Largest siphon inside the level set X (drop every level fed by an event taking nothing from X). */
290inline std::vector<bool> spn_maxsiphon(const std::vector<std::vector<long long>>& pre,
291 const std::vector<std::vector<long long>>& post,
292 std::vector<bool> X) {
293 const std::size_t n = pre.size();
294 const std::size_t E = n == 0 ? 0 : pre[0].size();
295 for (;;) {
296 bool any = false;
297 for (std::size_t l = 0; l < n; ++l) any = any || X[l];
298 if (!any) return X;
299 std::vector<bool> fr(E, true);
300 for (std::size_t e = 0; e < E; ++e)
301 for (std::size_t l = 0; l < n && fr[e]; ++l)
302 if (X[l] && pre[l][e] > 0) fr[e] = false;
303 bool drop = false;
304 for (std::size_t l = 0; l < n; ++l) {
305 if (!X[l]) continue;
306 for (std::size_t e = 0; e < E; ++e)
307 if (fr[e] && post[l][e] > 0) {
308 X[l] = false;
309 drop = true;
310 break;
311 }
312 }
313 if (!drop) return X;
314 }
315}
316
317inline bool spn_isminimal(const std::vector<std::vector<long long>>& pre,
318 const std::vector<std::vector<long long>>& post,
319 const std::vector<bool>& S) {
320 for (std::size_t p = 0; p < S.size(); ++p) {
321 if (!S[p]) continue;
322 std::vector<bool> X = S;
323 X[p] = false;
324 const std::vector<bool> Y = spn_maxsiphon(pre, post, X);
325 for (std::size_t l = 0; l < Y.size(); ++l)
326 if (Y[l]) return false;
327 }
328 return true;
329}
330
331/**
332 * All minimal siphons, by the branch and bound documented in spn_siphons.h,
333 * sorted by size, then lexicographically by their level indices.
334 */
335inline std::vector<std::vector<bool>> spn_minsiphons(const std::vector<std::vector<long long>>& pre,
336 const std::vector<std::vector<long long>>& post) {
337 const std::size_t n = pre.size();
338 std::vector<std::vector<bool>> found;
339 std::vector<std::vector<bool>> stI(1, std::vector<bool>(n, false));
340 std::vector<std::vector<bool>> stO(1, std::vector<bool>(n, false));
341 while (!stI.empty()) {
342 const std::vector<bool> I = stI.back();
343 const std::vector<bool> O = stO.back();
344 stI.pop_back();
345 stO.pop_back();
346 std::vector<bool> notO(n);
347 for (std::size_t l = 0; l < n; ++l) notO[l] = !O[l];
348 const std::vector<bool> S = spn_maxsiphon(pre, post, notO);
349 bool anyS = false, miss = false;
350 for (std::size_t l = 0; l < n; ++l) {
351 anyS = anyS || S[l];
352 miss = miss || (I[l] && !S[l]);
353 }
354 if (!anyS || miss) continue;
355 bool inside = false;
356 for (std::size_t f = 0; f < found.size() && !inside; ++f) {
357 bool sub = true, nz = false;
358 for (std::size_t l = 0; l < n; ++l) {
359 nz = nz || found[f][l];
360 if (found[f][l] && !I[l]) sub = false;
361 }
362 inside = sub && nz;
363 }
364 if (inside) continue;
365 if (spn_isminimal(pre, post, S)) {
366 found.push_back(S);
367 continue;
368 }
369 std::size_t q = n;
370 for (std::size_t l = 0; l < n; ++l)
371 if (S[l] && !I[l]) {
372 q = l;
373 break;
374 }
375 if (q == n) continue;
376 std::vector<bool> Oq = O, Iq = I;
377 Oq[q] = true;
378 Iq[q] = true;
379 stI.push_back(Iq);
380 stO.push_back(O);
381 stI.push_back(I);
382 stO.push_back(Oq);
383 }
384 std::stable_sort(found.begin(), found.end(),
385 [](const std::vector<bool>& a, const std::vector<bool>& b) {
386 std::vector<std::size_t> ia, ib;
387 for (std::size_t l = 0; l < a.size(); ++l) {
388 if (a[l]) ia.push_back(l);
389 if (b[l]) ib.push_back(l);
390 }
391 if (ia.size() != ib.size()) return ia.size() < ib.size();
392 return ia < ib;
393 });
394 return found;
395}
396
397/** Rows of logical sets as 0/1 integers. */
398inline std::vector<std::vector<int>> spn_sets_to_int(const std::vector<std::vector<bool>>& L) {
399 std::vector<std::vector<int>> out(L.size());
400 for (std::size_t i = 0; i < L.size(); ++i) {
401 out[i].resize(L[i].size());
402 for (std::size_t l = 0; l < L[i].size(); ++l) out[i][l] = L[i][l] ? 1 : 0;
403 }
404 return out;
405}
406
407/** The transpose of an nr x nc integer matrix. */
408inline std::vector<std::vector<long long>> spn_transpose(const std::vector<std::vector<long long>>& A,
409 std::size_t nc) {
410 std::vector<std::vector<long long>> B(nc, std::vector<long long>(A.size(), 0));
411 for (std::size_t i = 0; i < A.size(); ++i)
412 for (std::size_t j = 0; j < nc; ++j) B[j][i] = A[i][j];
413 return B;
414}
415
416} // namespace detail
417} // namespace spn
418} // namespace line
419
420#endif // LINE_API_SPN_SPN_NET_H
InputError(const std::string &what)
Definition error.h:105
std::size_t size() const
Definition matrix.h:91
bool empty() const
Definition matrix.h:92
UnsupportedError(const std::string &what)
Definition error.h:119
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.
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
Definition lang_types.h:378
bool nz(const Val &v)
Matrix< double > trans(const Matrix< double > &A)
Definition mg1.h:932
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Minimal-support S-invariants (P-invariants) of a stochastic Petri net, and the load vector V = S m0.