LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
spn_lpbnd.h
Go to the documentation of this file.
1#pragma once
2/**
3 * @file spn_lpbnd.h
4 * @ingroup api_spn
5 * @brief Linear-programming bounds on the mean marking and the throughputs of a
6 * stochastic timed Petri net.
7 *
8 * The stationary chain is relaxed to a MOMENT POLYTOPE: the uniformized
9 * evolution equation is written for E[X_p], E[X_p^2] and E[X_p1 X_p2], which
10 * gives linear equalities among the mean marking x, the enabling probabilities
11 * q and the products y(p,t) = E[X_p e_t]; behavioural and probabilistic
12 * inequalities are added on top; and every reported measure is then obtained by
13 * minimising and maximising its linear form over that polytope. Any stationary
14 * point of the true chain satisfies every row, so the two optima BRACKET the
15 * exact value whatever the polytope leaves out.
16 *
17 * This is the Petri-net sibling of the QRF bounds in SolverBA: same technique,
18 * a different index space, and a LINEAR objective, so there is no stationary
19 * point to escape from and the answer is a property of the model alone.
20 *
21 * VARIABLES, over place levels l and modes e: x(l) the mean tokens, q(e) the
22 * probability that mode e is enabled, th(e) its throughput, u(e) the
23 * state-equation firing counts, and y(l,e) = E[X_l e_e] on the Markovian side
24 * only. u is EXISTENTIAL and is not reported: E[X] is a convex combination of
25 * reachable markings, each of which is m0 + C h for some nonnegative integer h,
26 * so the mean satisfies m0 + C u for some nonnegative real u.
27 *
28 * ONE LEVEL PER PLACE, NOT PER (PLACE, CLASS), and a multiclass net is REFUSED.
29 * The other three codebases carry (nnodes x nclasses) arc matrices and so give
30 * a multiclass net P*R place levels. This port reads
31 * `NetworkStruct::transparam`, whose arcs are per (mode, NODE) with no class
32 * dimension, the same restriction `spn_mdd` states and enforces. A coloured net
33 * is a different model, not an approximation of this one, so it is refused
34 * rather than collapsed. `spn_sinvariants` is class-summed here for the same
35 * reason, which makes the invariant family (8) below the coarser one -- still a
36 * genuine invariant, just not the finest.
37 *
38 * THE INITIAL MARKING COMES FROM THE REFERENCE STATION of each closed class, or
39 * from `SpnLpOptions::init`: unlike the object-graph codebases, a NetworkStruct
40 * carries no per-place state to read instead. A net whose tokens do not all
41 * start at the reference station must pass `init`.
42 *
43 * THE TOKEN COUNTS CREATED BY A FIRING ARE DETERMINISTIC IN LINE, which removes
44 * a whole branch of the reference: it allows sigma_(t,p)(n) to be random and
45 * splits the covariance family into an independent case (its eq. 7) and a
46 * selective one (its eq. 8). A firing outcome is an integer weight, so
47 * E[sigma^2] = sigma^2 and E[sigma_p1 sigma_p2] = sigma_p1 sigma_p2 hold
48 * exactly and eq. (7) is the correct form. Eq. (8) has no LINE model behind it
49 * and is deliberately absent.
50 *
51 * LIVENESS IS OFF BY DEFAULT, AND THAT IS DELIBERATE. The reference's two
52 * liveness rows (sum_t q_t >= 1 and x_p <= sum_t y_(p,t)) hold only on a live
53 * net, and liveness is not something this function can cheaply certify -- an
54 * inhibitor arc alone is enough to deadlock a net that looks well formed. A
55 * bound that silently assumed it would be wrong rather than loose on exactly
56 * the models where a bound is most wanted, so the rows are opt-in.
57 *
58 * WHAT THE ROWS ARE WORTH, MEASURED. They are the whole of the lower side. On
59 * the reference's own Table 2 (its Fig. 2b production line, five rate vectors)
60 * `assumelive` reproduces its published l.b. column to four decimals -- 1.1653
61 * against 1.165, 1.8288 against 1.829, 1.5814 against 1.581, 1.3592 against
62 * 1.359, 1.3497 against 1.350 -- while without them the Markovian lower bound
63 * collapses onto the OPERATIONAL one on four of the five. The upper side needs
64 * neither row and matches the published u.b.2 either way.
65 *
66 * Reference: Z. Liu, "Performance Analysis of Stochastic Timed Petri Nets Using
67 * Linear Programming Approach", IEEE Trans. Software Engineering 24(11), 1998,
68 * 1014-1030. The constraint families are its Table 1, p. 1022; the bracket
69 * statement is its Theorem 3, p. 1021.
70 *
71 * MATLAB twin: `spn_lpbnd.m`. JAR twin: `Spn_lpbnd.java`. Python twin:
72 * `api/spn/lpbnd.py`.
73 */
74
75#include <cmath>
76#include <cstddef>
77#include <limits>
78#include <string>
79#include <vector>
80
86#include "line/util/error.h"
87#include "line/util/lp_highs.h"
88#include "line/util/simplex.h"
89
90namespace line {
91namespace spn {
92
93/** Options of the relaxation. */
95 /**
96 * true (the default) uses the second-moment, covariance and Little's law
97 * families, which need exponential firing times; false drops them and the
98 * whole y block, leaving the operational bound, which needs only a mean
99 * firing time and so admits any phase-type law.
100 */
101 bool markovian = true;
102 /** true adds the two liveness rows, valid only on a live net. */
103 bool assumelive = false;
104 /** Initial tokens per place level; empty takes the reference stations. */
105 std::vector<double> init;
106 /** Slack added to the inequality sides. */
107 double tol = 0.0;
108};
109
110/** One (transition, mode) pair over place levels. */
111struct SpnLpMode {
112 std::size_t trans = 0; ///< 1-based node index of the transition
113 std::size_t mode = 0; ///< mode index within the transition, 0-based
114 std::vector<double> enab;
115 std::vector<double> inhib;
116 std::vector<double> fire;
117 double rate = 0.0;
118};
119
120/** The brackets; each vector pair holds the minimum then the maximum. */
122 std::vector<std::size_t> places; ///< 1-based node indices, in level order
123 std::vector<std::string> levelname;
124 std::vector<SpnLpMode> modes;
125 std::vector<double> tokens_lo, tokens_hi;
126 std::vector<double> place_tput_lo, place_tput_hi;
127 std::vector<double> mode_tput_lo, mode_tput_hi;
128 std::vector<double> mode_util_lo, mode_util_hi;
129 std::vector<double> bound; ///< a priori per-level cap, inf where none
130 std::size_t nplacelevels = 0;
131 bool markovian = true;
132 std::size_t nvars = 0;
133 std::size_t nrows = 0;
134};
135
136namespace detail {
137
138/** One LP. An infeasible or unbounded program answers NaN, as MATLAB does. */
139inline double spn_lp_opt(lp::LpModel<double>& m, const std::vector<double>& c, bool minimize) {
140 for (std::size_t j = 0; j < m.num_vars(); ++j) m.set_cost(j, c[j]);
141 m.set_maximize(!minimize);
143 if (!sol.ok() || !std::isfinite(sol.objective)) return std::numeric_limits<double>::quiet_NaN();
144 return sol.objective;
145}
146
147} // namespace detail
148
149/**
150 * Bracket the mean tokens and the throughputs of a stochastic Petri net.
151 *
152 * @param sn a NetworkStruct holding Places and Transitions, single class
153 * @param options the relaxation options
154 * @return the brackets, per place level and per mode
155 */
156template <class T>
158 const SpnLpOptions& options = SpnLpOptions()) {
159 const double inf = std::numeric_limits<double>::infinity();
160
161 // ONE LEVEL PER PLACE, so a coloured net is a different model. Same refusal
162 // spn_mdd makes, and for the same reason.
163 if (sn.nclasses > 1)
164 throw UnsupportedError(
165 "spn_lpbnd: the net has " + std::to_string(sn.nclasses) +
166 " classes, and this translation puts ONE LEVEL PER PLACE; a coloured net needs a "
167 "level per (place, class) pair, which the moment relaxation would then be written "
168 "over. Solve the single-class net, or use the MATLAB, JAR or python twin, which "
169 "carry the class dimension.");
170
171 std::vector<std::size_t> places, transitions;
172 for (std::size_t i = 1; i <= sn.nodes.size(); ++i) {
173 if (sn.nodes[i - 1].nodetype == lang::NodeType::Place) places.push_back(i);
174 else if (sn.nodes[i - 1].nodetype == lang::NodeType::Transition) transitions.push_back(i);
175 }
176 if (places.empty() || transitions.empty())
177 throw InputError("spn_lpbnd: the model holds no Place or no Transition node");
178 const std::size_t P = places.size();
179 const std::size_t L = P;
180
181 // ---- the (transition, mode) table. spn_mdd builds the same one, but only
182 // as a step of reachable-set construction, which is the cost this bound
183 // exists to avoid.
184 std::vector<SpnLpMode> md;
185 for (std::size_t t = 0; t < transitions.size(); ++t) {
186 const std::size_t ind = transitions[t];
187 const typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
188 sn.transparam.find(ind);
189 if (it == sn.transparam.end()) continue;
190 const qn::TransitionParam<T>& tp = it->second;
191 for (std::size_t m = 0; m < tp.nmodes; ++m) {
192 if (m < tp.timing.size() && tp.timing[m] == lang::TimingStrategy::IMMEDIATE)
193 throw UnsupportedError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
194 std::to_string(ind) +
195 " is IMMEDIATE; the moment relaxation is written for a net "
196 "whose transitions all have finite rates, so vanishing "
197 "states must be eliminated first");
198 if (m < tp.firingdep.size() && tp.firingdep[m])
199 throw UnsupportedError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
200 std::to_string(ind) +
201 " has a marking-dependent firing rate; the uniformization "
202 "step needs one rate per mode");
203 const double srv = m < tp.nmodeservers.size() ? tp.nmodeservers[m] : 1.0;
204 if (srv != 1.0)
205 throw UnsupportedError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
206 std::to_string(ind) + " has " + std::to_string(srv) +
207 " servers; the relaxation is derived under single-server "
208 "semantics, where the firing rate is mu*q. Its "
209 "infinite-server form needs the K-fold transition "
210 "expansion of the reference's Section 7, which is not "
211 "implemented");
212 if (m >= tp.firingproc.size() || tp.firingproc[m].disabled)
213 throw InputError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
214 std::to_string(ind) + " has no firing process");
215 SpnLpMode e;
216 e.trans = ind;
217 e.mode = m;
218 e.enab.assign(L, 0.0);
219 e.inhib.assign(L, inf);
220 e.fire.assign(L, 0.0);
221 // The net is single class here (refused above), so the class-summed
222 // arc IS the arc; `arc_total` is what says that out loud.
223 const std::vector<T> en_t = qn::TransitionParam<T>::arc_total(tp.enabling, m);
224 const std::vector<T> ih_t = qn::TransitionParam<T>::inhibit_total(tp.inhibiting, m);
225 const std::vector<T> fi_t = qn::TransitionParam<T>::arc_total(tp.firing, m);
226 for (std::size_t pp = 0; pp < P; ++pp) {
227 const std::size_t q = places[pp] - 1; // arcs are indexed by node
228 if (q < en_t.size()) e.enab[pp] = std::max(0.0, num_traits<T>::to_double(en_t[q]));
229 if (q < ih_t.size()) e.inhib[pp] = num_traits<T>::to_double(ih_t[q]);
230 if (q < fi_t.size()) e.fire[pp] = std::max(0.0, num_traits<T>::to_double(fi_t[q]));
231 }
232 // A PHASE-TYPE FIRING LAW IS WHERE THE TWO VARIANTS PART. The mean
233 // of a (D0,D1) pair is all the operational bound needs; the
234 // Markovian one needs the marking alone to be the state, which a
235 // multi-phase mode breaks.
236 const mam::Map<T> proc = lang::dist_to_map(tp.firingproc[m]);
237 const std::size_t nph = proc.order();
238 if (options.markovian && nph > 1)
239 throw UnsupportedError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
240 std::to_string(ind) +
241 " has a phase-type firing time; the relaxation is written "
242 "over the marking alone, and a phase-type mode needs the "
243 "state-machine expansion of the reference's Section 7, "
244 "which is not implemented. Use the operational bound, "
245 "which needs only the mean");
246 const double mean = num_traits<T>::to_double(mam::map_mean(proc));
247 e.rate = nph == 1 ? num_traits<T>::to_double(proc.D1(0, 0)) : 1.0 / mean;
248 if (!(e.rate > 0) || !std::isfinite(e.rate))
249 throw InputError("spn_lpbnd: mode " + std::to_string(m + 1) + " of node " +
250 std::to_string(ind) + " has mean firing rate " +
251 std::to_string(e.rate) + "; a bound needs a finite positive one");
252 md.push_back(e);
253 }
254 }
255 if (md.empty()) throw InputError("spn_lpbnd: the net has no firing mode");
256 const std::size_t E = md.size();
257
258 std::vector<double> mu(E, 0.0);
259 std::vector<std::vector<double>> net(E, std::vector<double>(L, 0.0));
260 for (std::size_t e = 0; e < E; ++e) {
261 mu[e] = md[e].rate;
262 for (std::size_t l = 0; l < L; ++l) net[e][l] = md[e].fire[l] - md[e].enab[l];
263 }
264
265 // Per-level a priori bounds and the conserved sums, both off the same
266 // minimal-support P-invariant basis. The reference writes its "cycle
267 // population" family for UNWEIGHTED cycles; spn_sinvariants returns the
268 // weighted invariants S m = V, which are equally linear and strictly
269 // tighter, so those are what is emitted.
270 const SpnInvariants inv = spn_sinvariants(sn, options.init);
271 const std::vector<std::vector<long long>>& S = inv.S;
272 const std::vector<long long>& V = inv.V;
273 const std::vector<long long>& m0 = inv.m0;
274 std::vector<double> B(L, inf);
275 for (std::size_t i = 0; i < S.size(); ++i)
276 for (std::size_t l = 0; l < L; ++l)
277 if (S[i][l] > 0)
278 B[l] = std::min(B[l], std::floor(static_cast<double>(V[i]) /
279 static_cast<double>(S[i][l])));
280
281 // ---- variable layout
282 const std::size_t ix = 0;
283 const std::size_t iq = L;
284 const std::size_t ith = L + E;
285 const std::size_t iu = L + 2 * E;
286 const std::size_t iy = L + 3 * E;
287 const std::size_t nv = options.markovian ? L + 3 * E + L * E : L + 3 * E;
288
289 lp::LpModel<double> lpm(nv);
290 for (std::size_t e = 0; e < E; ++e) lpm.set_bounds(iq + e, 0.0, 1.0);
291 for (std::size_t l = 0; l < L; ++l) {
292 if (std::isfinite(B[l])) {
293 lpm.set_bounds(ix + l, 0.0, B[l]);
294 if (options.markovian)
295 for (std::size_t e = 0; e < E; ++e) lpm.set_bounds(iy + l * E + e, 0.0, B[l]);
296 }
297 }
298
299 const double tol = options.tol;
300
301 // ---- (1) throughput: th_e = mu_e q_e
302 for (std::size_t e = 0; e < E; ++e) {
303 lpm.row_clear();
304 lpm.row_add(ith + e, 1.0);
305 lpm.row_add(iq + e, -mu[e]);
306 lpm.emit_eq(0.0);
307 }
308
309 // ---- (2) flow balance: tokens are created at a level at the rate they are
310 // consumed there. Holds for any stable net, Markovian or not.
311 for (std::size_t l = 0; l < L; ++l) {
312 lpm.row_clear();
313 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iq + e, mu[e] * net[e][l]);
314 lpm.emit_eq(0.0);
315 }
316
317 // ---- (3)+(4) second moment and population covariance, from the
318 // stationarity of E[X_l1 X_l2] under the uniformized chain. Table 1 writes
319 // the q side as four sums over set intersections; since the memberships are
320 // exactly "sigma > 0" and "pi > 0", those collapse to
321 // -(sigma_1 - pi_1)(sigma_2 - pi_2) = -net_1 net_2
322 // per mode, which also makes the l1 == l2 case reduce to the second-moment
323 // family with no separate derivation.
324 if (options.markovian) {
325 for (std::size_t l1 = 0; l1 < L; ++l1)
326 for (std::size_t l2 = l1; l2 < L; ++l2) {
327 lpm.row_clear();
328 for (std::size_t e = 0; e < E; ++e) {
329 // at l1 == l2 the two y terms address the same column and
330 // row_add SUMS them, which is the factor of two the
331 // reference's (6) carries
332 lpm.row_add(iy + l1 * E + e, mu[e] * net[e][l2]);
333 lpm.row_add(iy + l2 * E + e, mu[e] * net[e][l1]);
334 lpm.row_add(iq + e, mu[e] * net[e][l1] * net[e][l2]);
335 }
336 lpm.emit_eq(0.0);
337 }
338 }
339
340 // ---- (5) liveness, only when the caller vouches for it
341 if (options.assumelive) {
342 lpm.row_clear();
343 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iq + e, 1.0);
344 lpm.emit_ge(1.0 - tol);
345 if (options.markovian)
346 for (std::size_t l = 0; l < L; ++l) {
347 lpm.row_clear();
348 lpm.row_add(ix + l, 1.0);
349 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iy + l * E + e, -1.0);
350 lpm.emit_le(tol);
351 }
352 }
353
354 // ---- (6) conflicting transitions: a mode that consumes no more and is
355 // inhibited no sooner is enabled whenever the other is
356 for (std::size_t e1 = 0; e1 < E; ++e1)
357 for (std::size_t e2 = 0; e2 < E; ++e2) {
358 if (e1 == e2) continue;
359 bool dominated = true;
360 for (std::size_t l = 0; l < L && dominated; ++l)
361 if (!(md[e1].enab[l] <= md[e2].enab[l] && md[e1].inhib[l] >= md[e2].inhib[l]))
362 dominated = false;
363 if (dominated) {
364 lpm.row_clear();
365 lpm.row_add(iq + e1, 1.0);
366 lpm.row_add(iq + e2, -1.0);
367 lpm.emit_ge(-tol);
368 }
369 }
370
371 // ---- (7) boundedness, per level; and (8) cycle population, as the
372 // weighted invariant equalities and their y companions
373 if (options.markovian)
374 for (std::size_t l = 0; l < L; ++l) {
375 if (!std::isfinite(B[l])) continue;
376 for (std::size_t e = 0; e < E; ++e) {
377 lpm.row_clear();
378 lpm.row_add(iy + l * E + e, 1.0);
379 lpm.row_add(iq + e, -B[l]);
380 lpm.emit_le(tol);
381 lpm.row_clear();
382 lpm.row_add(ix + l, 1.0);
383 lpm.row_add(iy + l * E + e, -1.0);
384 lpm.row_add(iq + e, B[l]);
385 lpm.emit_le(B[l] + tol);
386 if (B[l] > 0) {
387 lpm.row_clear();
388 lpm.row_add(ix + l, 1.0 - 1.0 / B[l]);
389 lpm.row_add(iy + l * E + e, -1.0);
390 lpm.row_add(iq + e, 1.0);
391 lpm.emit_ge(-tol);
392 }
393 }
394 }
395 for (std::size_t i = 0; i < S.size(); ++i) {
396 lpm.row_clear();
397 for (std::size_t l = 0; l < L; ++l) lpm.row_add(ix + l, static_cast<double>(S[i][l]));
398 lpm.emit_eq(static_cast<double>(V[i]));
399 if (options.markovian)
400 for (std::size_t e = 0; e < E; ++e) {
401 lpm.row_clear();
402 for (std::size_t l = 0; l < L; ++l)
403 lpm.row_add(iy + l * E + e, static_cast<double>(S[i][l]));
404 lpm.row_add(iq + e, -static_cast<double>(V[i]));
405 lpm.emit_eq(0.0);
406 }
407 }
408
409 // ---- (9) reachable marking: the mean lies in the state-equation cone
410 for (std::size_t l = 0; l < L; ++l) {
411 lpm.row_clear();
412 lpm.row_add(ix + l, 1.0);
413 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iu + e, -net[e][l]);
414 lpm.emit_eq(static_cast<double>(m0[l]));
415 }
416
417 // ---- (10) sample-path comparisons
418 if (options.markovian) {
419 double mutot = 0;
420 for (std::size_t e = 0; e < E; ++e) mutot += mu[e];
421 for (std::size_t l = 0; l < L; ++l) {
422 for (std::size_t e = 0; e < E; ++e) {
423 lpm.row_clear();
424 lpm.row_add(iy + l * E + e, 1.0);
425 lpm.row_add(ix + l, -1.0);
426 lpm.emit_le(tol);
427 if (md[e].enab[l] > 0) {
428 lpm.row_clear();
429 lpm.row_add(iy + l * E + e, 1.0);
430 lpm.row_add(iq + e, -md[e].enab[l]);
431 lpm.emit_ge(-tol);
432 }
433 if (std::isfinite(md[e].inhib[l])) {
434 lpm.row_clear();
435 lpm.row_add(iy + l * E + e, 1.0);
436 lpm.row_add(iq + e, -(md[e].inhib[l] - 1.0));
437 lpm.emit_le(tol);
438 }
439 }
440 lpm.row_clear();
441 lpm.row_add(ix + l, mutot);
442 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iy + l * E + e, -mu[e]);
443 lpm.emit_ge(-tol);
444 }
445 for (std::size_t e = 0; e < E; ++e) {
446 std::size_t ent = 0;
447 std::size_t nent = 0;
448 bool inhibited = false;
449 for (std::size_t l = 0; l < L; ++l) {
450 if (md[e].enab[l] > 0) {
451 ent = l;
452 ++nent;
453 }
454 if (std::isfinite(md[e].inhib[l])) inhibited = true;
455 }
456 if (nent == 1 && !inhibited) {
457 lpm.row_clear();
458 lpm.row_add(ix + ent, 1.0);
459 lpm.row_add(iy + ent * E + e, -1.0);
460 lpm.emit_le(md[e].enab[ent] - 1.0 + tol);
461 }
462 }
463 }
464
465 // ---- (11) enabling bounds, from Chernoff's inequality on the marking
466 for (std::size_t e = 0; e < E; ++e) {
467 std::vector<std::size_t> ent, inh;
468 for (std::size_t l = 0; l < L; ++l) {
469 if (md[e].enab[l] > 0) ent.push_back(l);
470 if (std::isfinite(md[e].inhib[l])) inh.push_back(l);
471 }
472 const std::size_t d = ent.size() + inh.size();
473 if (d == 0) continue;
474 bool entBounded = !ent.empty();
475 for (std::size_t j = 0; j < ent.size(); ++j)
476 if (!std::isfinite(B[ent[j]])) entBounded = false;
477 if (entBounded) {
478 lpm.row_clear();
479 lpm.row_add(iq + e, 1.0);
480 double rhs = 1.0;
481 bool ok = true;
482 for (std::size_t j = 0; j < ent.size() && ok; ++j) {
483 const std::size_t l = ent[j];
484 const double den = B[l] - md[e].enab[l] + 1.0;
485 if (den <= 0) {
486 ok = false;
487 break;
488 }
489 lpm.row_add(ix + l, -1.0 / den);
490 rhs -= B[l] / den;
491 }
492 if (ok) {
493 for (std::size_t j = 0; j < inh.size(); ++j)
494 lpm.row_add(ix + inh[j], 1.0 / md[e].inhib[inh[j]]);
495 lpm.emit_ge(rhs - tol);
496 } else {
497 lpm.row_clear();
498 }
499 }
500 // Upper side. Every term of the sum over input levels carries a "min"
501 // operator, and Table 1's convention is that either operand may be
502 // taken; each choice is a valid row and the whole set is the tightest
503 // linear relaxation, so all of them are emitted while the count stays
504 // small.
505 bool ok = true;
506 for (std::size_t j = 0; j < inh.size(); ++j) {
507 const std::size_t l = inh[j];
508 if (!std::isfinite(B[l]) || B[l] - md[e].inhib[l] + 1.0 <= 0) ok = false;
509 }
510 if (!ok) continue;
511 const std::size_t nc = ent.size();
512 std::vector<unsigned long> combos;
513 if (nc <= 4) {
514 for (unsigned long c = 0; c < (1UL << nc); ++c) combos.push_back(c);
515 } else {
516 combos.push_back(0);
517 combos.push_back((1UL << nc) - 1);
518 }
519 for (std::size_t ci = 0; ci < combos.size(); ++ci) {
520 const unsigned long c = combos[ci];
521 lpm.row_clear();
522 lpm.row_add(iq + e, static_cast<double>(d));
523 double rhs = 0.0;
524 for (std::size_t j = 0; j < inh.size(); ++j) {
525 const std::size_t l = inh[j];
526 const double den = B[l] - md[e].inhib[l] + 1.0;
527 rhs += B[l] / den;
528 lpm.row_add(ix + l, 1.0 / den);
529 }
530 for (std::size_t j = 0; j < nc; ++j) {
531 const std::size_t l = ent[j];
532 if (((c >> j) & 1UL) == 0) {
533 lpm.row_add(ix + l, -1.0 / md[e].enab[l]);
534 } else {
535 rhs += 1.0;
536 }
537 }
538 lpm.emit_le(rhs + tol);
539 }
540 }
541
542 // ---- (12) Little's law at each level: the mean sojourn time of a token is
543 // at least the mean minimum firing time of the modes that can remove it
544 if (options.markovian)
545 for (std::size_t l = 0; l < L; ++l) {
546 double out = 0;
547 for (std::size_t e = 0; e < E; ++e)
548 if (md[e].enab[l] > 0) out += mu[e];
549 if (out <= 0) continue;
550 lpm.row_clear();
551 lpm.row_add(ix + l, out);
552 for (std::size_t e = 0; e < E; ++e) lpm.row_add(iq + e, -mu[e] * md[e].fire[l]);
553 lpm.emit_ge(-tol);
554 }
555
556 SpnLpBounds out;
557 out.tokens_lo.assign(L, 0.0);
558 out.tokens_hi.assign(L, 0.0);
559 out.place_tput_lo.assign(L, 0.0);
560 out.place_tput_hi.assign(L, 0.0);
561 std::vector<double> c(nv, 0.0);
562 for (std::size_t l = 0; l < L; ++l) {
563 std::fill(c.begin(), c.end(), 0.0);
564 c[ix + l] = 1.0;
565 out.tokens_lo[l] = detail::spn_lp_opt(lpm, c, true);
566 out.tokens_hi[l] = detail::spn_lp_opt(lpm, c, false);
567 std::fill(c.begin(), c.end(), 0.0);
568 bool any = false;
569 for (std::size_t e = 0; e < E; ++e)
570 if (md[e].enab[l] > 0) {
571 c[iq + e] = mu[e] * md[e].enab[l];
572 any = true;
573 }
574 out.place_tput_lo[l] = any ? detail::spn_lp_opt(lpm, c, true) : 0.0;
575 out.place_tput_hi[l] = any ? detail::spn_lp_opt(lpm, c, false) : 0.0;
576 }
577 out.mode_tput_lo.assign(E, 0.0);
578 out.mode_tput_hi.assign(E, 0.0);
579 out.mode_util_lo.assign(E, 0.0);
580 out.mode_util_hi.assign(E, 0.0);
581 for (std::size_t e = 0; e < E; ++e) {
582 std::fill(c.begin(), c.end(), 0.0);
583 c[ith + e] = 1.0;
584 out.mode_tput_lo[e] = detail::spn_lp_opt(lpm, c, true);
585 out.mode_tput_hi[e] = detail::spn_lp_opt(lpm, c, false);
586 std::fill(c.begin(), c.end(), 0.0);
587 c[iq + e] = 1.0;
588 out.mode_util_lo[e] = detail::spn_lp_opt(lpm, c, true);
589 out.mode_util_hi[e] = detail::spn_lp_opt(lpm, c, false);
590 }
591
592 out.places = places;
593 out.levelname.resize(L);
594 for (std::size_t pp = 0; pp < P; ++pp) out.levelname[pp] = sn.nodes[places[pp] - 1].name;
595 out.modes = md;
596 out.bound = B;
597 out.nplacelevels = L;
598 out.markovian = options.markovian;
599 out.nvars = nv;
600 out.nrows = lpm.num_rows();
601 return out;
602}
603
604} // namespace spn
605} // namespace line
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
void emit_eq(const T &rhs)
Definition simplex.h:217
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
std::size_t num_rows() const
Definition simplex.h:125
void emit_le(const T &rhs)
Definition simplex.h:216
void emit_ge(const T &rhs)
Definition simplex.h:218
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
std::size_t num_vars() const
Definition simplex.h:124
void row_clear()
Discard whatever the row accumulator holds.
Definition simplex.h:179
void set_bounds(std::size_t j, const T &lo, const T &hi)
Definition simplex.h:139
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
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.
A sparse LP backend for line::lp::LpModel, on HiGHS (MIT).
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
mam::Map< T > dist_to_map(const Distrib< T > &d)
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
Definition lang_types.h:363
LpSolution< T > lp_solve(const LpModel< T > &model, std::size_t dense_max_cols=512)
Solve, choosing the backend by arithmetic and size.
Definition lp_highs.h:176
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
SpnInvariants spn_sinvariants(const qn::NetworkStruct< T > &sn, const std::vector< double > &init=std::vector< double >())
Minimal-support S-invariants and the load vector of a net.
SpnLpBounds spn_lpbnd(const qn::NetworkStruct< T > &sn, const SpnLpOptions &options=SpnLpOptions())
Bracket the mean tokens and the throughputs of a stochastic Petri net.
Definition spn_lpbnd.h:157
A queueing network and its refreshed NetworkStruct.
Templated primal simplex with Bland's rule.
Minimal-support S-invariants (P-invariants) of a stochastic Petri net, and the load vector V = S m0.
T objective
c'x, in the sense requested (max or min)
Definition simplex.h:97
bool ok() const
Definition simplex.h:99
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
std::size_t order() const
Definition map_moment.h:57
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).
The invariant basis of a net, in place-level coordinates.
std::vector< long long > m0
The initial marking the load vector was taken against.
std::vector< std::vector< long long > > S
S[i][p]: weight of place p in minimal-support invariant i.
std::vector< long long > V
V = S m0, the load vector.
The brackets; each vector pair holds the minimum then the maximum.
Definition spn_lpbnd.h:121
std::vector< std::string > levelname
Definition spn_lpbnd.h:123
std::vector< double > mode_util_hi
Definition spn_lpbnd.h:128
std::vector< double > mode_util_lo
Definition spn_lpbnd.h:128
std::vector< double > place_tput_hi
Definition spn_lpbnd.h:126
std::vector< double > mode_tput_lo
Definition spn_lpbnd.h:127
std::vector< double > tokens_lo
Definition spn_lpbnd.h:125
std::vector< std::size_t > places
1-based node indices, in level order
Definition spn_lpbnd.h:122
std::vector< double > tokens_hi
Definition spn_lpbnd.h:125
std::vector< double > place_tput_lo
Definition spn_lpbnd.h:126
std::size_t nplacelevels
Definition spn_lpbnd.h:130
std::vector< double > mode_tput_hi
Definition spn_lpbnd.h:127
std::vector< SpnLpMode > modes
Definition spn_lpbnd.h:124
std::vector< double > bound
a priori per-level cap, inf where none
Definition spn_lpbnd.h:129
One (transition, mode) pair over place levels.
Definition spn_lpbnd.h:111
std::vector< double > fire
Definition spn_lpbnd.h:116
std::vector< double > inhib
Definition spn_lpbnd.h:115
std::vector< double > enab
Definition spn_lpbnd.h:114
std::size_t trans
1-based node index of the transition
Definition spn_lpbnd.h:112
std::size_t mode
mode index within the transition, 0-based
Definition spn_lpbnd.h:113
Options of the relaxation.
Definition spn_lpbnd.h:94
bool assumelive
true adds the two liveness rows, valid only on a live net.
Definition spn_lpbnd.h:103
bool markovian
true (the default) uses the second-moment, covariance and Little's law families, which need exponenti...
Definition spn_lpbnd.h:101
std::vector< double > init
Initial tokens per place level; empty takes the reference stations.
Definition spn_lpbnd.h:105
double tol
Slack added to the inequality sides.
Definition spn_lpbnd.h:107