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