214 "spn_pf: y is the exponential of a least-squares solution of the log-complex-balance "
215 "equations, so the derivation needs a transcendental field and is unavailable under "
216 "exact arithmetic. Supply the g_l directly to mdd_rec / spn_metrics, which stay "
221 mddopt.
bound = options.bound;
222 mddopt.
verbose = options.verbose;
227 const std::vector<SpnMode<T>>& md = info.
modes;
228 const std::size_t E = md.size();
229 if (E == 0)
throw InputError(
"spn_pf: the net has no timed mode");
232 for (std::size_t pp = 0; pp < info.
places.size(); ++pp) {
233 const std::size_t nd = info.
places[pp];
234 const std::size_t ist =
sn.nodes[nd - 1].station;
235 if (ist >= 1 && ist <=
sn.stations.size() &&
239 " is a QUEUEING place: its embedded service is state that the marking does "
240 "not carry, so the net is not the token-container Petri net this product form "
245 std::vector<double> lambda(E, 0.0);
246 std::vector<std::vector<double>> Iv(E), Ov(E);
247 for (std::size_t e = 0; e < E; ++e) {
251 bool consumes =
false;
252 for (std::size_t l = 0; l < L; ++l) {
253 if (std::isfinite(md[e].inhib[l]))
255 "spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
256 std::to_string(md[e].trans) +
257 " has an inhibitor arc. An inhibitor zeroes the firing rate on markings "
258 "that still satisfy m >= I(t), so the rate is not lambda*psi(m-I)/psi(m) "
259 "on any psi and the net has no product form of this kind");
260 if (md[e].enab[l] > 0) consumes =
true;
262 if (md[e].srv != 1 && !consumes)
263 throw InputError(
"spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
264 std::to_string(md[e].trans) +
265 " has several servers but consumes from no place, so its "
266 "enabling degree is unbounded and its firing rate undefined");
267 if (!(lambda[e] > 0))
268 throw InputError(
"spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
269 std::to_string(md[e].trans) +
270 " has a non-positive firing rate");
275 bool okgeo =
true, okma =
true;
276 for (std::size_t s = 0; s < states.size(); ++s) {
277 std::vector<double> m(L, 0.0);
278 for (std::size_t l = 0; l < L; ++l) m[l] = states[s][l];
279 for (std::size_t e = 0; e < E; ++e) {
281 for (std::size_t l = 0; l < L && enabled; ++l)
282 if (m[l] < md[e].enab[l]) enabled =
false;
283 if (!enabled)
continue;
284 double actual = lambda[e] * detail::spn_pf_servers<T>(m, md[e], L);
288 const std::size_t KC = info.
nclasses;
290 for (std::size_t pp = 0; pp < info.
places.size(); ++pp)
291 for (std::size_t k = 0; k < KC; ++k)
295 const double ma = lambda[e] * detail::spn_pf_massaction(m, md[e].enab, L);
296 okgeo = okgeo && detail::spn_pf_close(actual, lambda[e], options.tol);
297 okma = okma && detail::spn_pf_close(actual, ma, options.tol);
300 "spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
301 std::to_string(md[e].trans) +
" fires at rate " + std::to_string(actual) +
302 " in a reachable marking, which is neither its rate constant "
303 "(single-server, psi = 1) nor its mass-action rate " +
305 " (psi = prod 1/m!). LINE's rate law on this mode is "
306 "lambda*min(enabling degree, servers)*g(m), and no psi puts that in the "
307 "form lambda*psi(m-I)/psi(m)");
310 const std::string kind = okgeo ?
"geometric" :
"massaction";
313 std::vector<std::vector<double>> C;
314 std::map<std::string, std::size_t> seen;
315 std::vector<std::size_t> src(E), dst(E);
316 for (std::size_t e = 0; e < E; ++e) src[e] = detail::spn_pf_complex(Iv[e], C, seen);
317 for (std::size_t e = 0; e < E; ++e) dst[e] = detail::spn_pf_complex(Ov[e], C, seen);
318 const std::size_t c = C.size();
322 for (std::size_t i = 0; i < c; ++i)
323 for (std::size_t j = 0; j < c; ++j) A(i, j) = 0.0;
324 for (std::size_t e = 0; e < E; ++e) {
325 if (src[e] == dst[e])
continue;
326 A(dst[e], src[e]) += lambda[e];
327 A(src[e], src[e]) -= lambda[e];
331 std::vector<int> lclass(c, -1);
332 std::size_t nlink = 0;
333 for (std::size_t v = 0; v < c; ++v) {
334 if (lclass[v] >= 0)
continue;
335 std::vector<std::size_t> stack(1, v);
336 lclass[v] =
static_cast<int>(nlink);
337 while (!stack.empty()) {
338 const std::size_t u = stack.back();
340 for (std::size_t e = 0; e < E; ++e) {
342 if (src[e] == u) w = dst[e];
343 else if (dst[e] == u) w = src[e];
344 if (w < c && lclass[w] < 0) {
345 lclass[w] =
static_cast<int>(nlink);
353 for (std::size_t b = 0; b < nlink && wr; ++b) {
355 for (std::size_t v = 0; v < c && v0 == c; ++v)
356 if (lclass[v] ==
static_cast<int>(b)) v0 = v;
357 const std::vector<bool> fwd = detail::spn_pf_reach(v0, src, dst, c);
358 const std::vector<bool> bwd = detail::spn_pf_reach(v0, dst, src, c);
359 for (std::size_t v = 0; v < c; ++v)
360 if (lclass[v] ==
static_cast<int>(b) && (!fwd[v] || !bwd[v])) wr =
false;
363 for (std::size_t e = 0; e < E; ++e)
364 for (std::size_t l = 0; l < L; ++l) netm(e, l) = Ov[e][l] - Iv[e][l];
367 const std::size_t srank =
rref(netmc, 1e-9).size();
368 const int deficiency =
static_cast<int>(c) -
static_cast<int>(nlink) -
369 static_cast<int>(srank);
372 std::vector<double> kappa(c, 0.0);
373 for (std::size_t b = 0; b < nlink; ++b) {
374 std::vector<std::size_t> idx;
375 for (std::size_t v = 0; v < c; ++v)
376 if (lclass[v] ==
static_cast<int>(b)) idx.push_back(v);
377 if (idx.size() == 1) {
384 for (std::size_t i = 0; i < idx.size(); ++i)
385 for (std::size_t j = 0; j < idx.size(); ++j) Qb(i, j) = A(idx[j], idx[i]);
388 for (std::size_t i = 0; i < idx.size(); ++i) mx = std::max(mx, pb[i]);
389 for (std::size_t i = 0; i < idx.size(); ++i) {
392 "spn_pf: linkage class " + std::to_string(b + 1) +
" of the complex graph "
393 "carries no flow through complex " + std::to_string(idx[i] + 1) +
394 ", so the net admits no positive complex-balanced point. A weakly "
395 "reversible net has a strictly positive balance flow on every linkage "
396 "class; this one is " + detail::spn_pf_wrtext(wr));
397 kappa[idx[i]] = pb[i] / mx;
402 std::vector<std::vector<double>> rows;
403 std::vector<double> rhs;
404 for (std::size_t b = 0; b < nlink; ++b) {
406 for (std::size_t v = 0; v < c; ++v) {
407 if (lclass[v] !=
static_cast<int>(b))
continue;
412 std::vector<double> row(L, 0.0);
413 for (std::size_t l = 0; l < L; ++l) row[l] = C[v][l] - C[v0][l];
415 rhs.push_back(std::log(kappa[v]) - std::log(kappa[v0]));
418 std::vector<double> x(L, 0.0);
421 const std::size_t nr = rows.size();
423 for (std::size_t i = 0; i < nr; ++i)
424 for (std::size_t j = 0; j < nr; ++j) {
426 for (std::size_t l = 0; l < L; ++l) s += rows[i][l] * rows[j][l];
430 for (std::size_t l = 0; l < L; ++l) {
432 for (std::size_t i = 0; i < nr; ++i) s += rows[i][l] * w.
x[i];
435 double res = 0.0, scale = 1.0;
436 for (std::size_t i = 0; i < nr; ++i) {
438 for (std::size_t l = 0; l < L; ++l) s += rows[i][l] * x[l];
439 res = std::max(res, std::fabs(s - rhs[i]));
440 scale = std::max(scale, std::fabs(rhs[i]));
442 if (res > options.tol * scale)
444 "spn_pf: the complex-balance equations are inconsistent (residual " +
445 std::to_string(res) +
"): this net has no product form of the tested kind at "
446 "these rates. Its deficiency is " + std::to_string(deficiency) +
" and it is " +
447 detail::spn_pf_wrtext(wr) +
"; the Deficiency Zero Theorem guarantees a "
448 "solution only at deficiency 0 with weak reversibility");
450 std::vector<double> yd(L, 1.0);
451 for (std::size_t l = 0; l < L; ++l) yd[l] = std::exp(x[l]);
454 std::vector<double> psi(c, 1.0);
455 for (std::size_t v = 0; v < c; ++v) {
457 for (std::size_t l = 0; l < L; ++l) p *= std::pow(yd[l], C[v][l]);
460 double resb = 0.0, scaleb = std::numeric_limits<double>::min();
461 for (std::size_t v = 0; v < c; ++v) {
462 double s = 0.0, sa = 0.0;
463 for (std::size_t u = 0; u < c; ++u) {
464 s += A(v, u) * psi[u];
465 sa += std::fabs(A(v, u)) * psi[u];
467 resb = std::max(resb, std::fabs(s));
468 scaleb = std::max(scaleb, sa);
470 if (resb > options.tol * scaleb)
472 "spn_pf: complex balance fails at the computed point (relative residual " +
473 std::to_string(resb / scaleb) +
"), so the product form would not be stationary");
479 for (std::size_t l = 0; l < L; ++l) {
481 const int d =
spn.mdds.domain[l];
484 for (
int k = 0; k < d; ++k) {
487 pw = T(pw * out.y[l]);
489 out.g[l][k] = kind ==
"massaction" ? T(pw / fact) : pw;
494 out.deficiency = deficiency;
497 out.weakly_reversible = wr;
498 out.residual = resb / scaleb;
500 if (options.verbose) {
501 std::printf(
"\nSPN product form: %s, %zu complexes, %zu linkage classes, rank %zu, "
502 "deficiency %d, %s\n",
503 kind.c_str(), c, nlink, srank, deficiency, detail::spn_pf_wrtext(wr).c_str());
504 std::string ystr =
"[";
505 for (std::size_t l = 0; l < L; ++l) {
507 std::snprintf(buf,
sizeof(buf),
"%s%.6g", l > 0 ?
" " :
"", yd[l]);
511 std::printf(
" y = %s (complex-balance residual %.2e)\n", ystr.c_str(), resb / scaleb);