212 "spn_pf: y is the exponential of a least-squares solution of the log-complex-balance "
213 "equations, so the derivation needs a transcendental field and is unavailable under "
214 "exact arithmetic. Supply the g_l directly to mdd_rec / spn_metrics, which stay "
219 mddopt.
bound = options.bound;
224 const std::vector<SpnMode<T>>& md = info.
modes;
225 const std::size_t E = md.size();
226 if (E == 0)
throw InputError(
"spn_pf: the net has no timed mode");
229 for (std::size_t pp = 0; pp < info.
places.size(); ++pp) {
230 const std::size_t nd = info.
places[pp];
231 const std::size_t ist =
sn.nodes[nd - 1].station;
232 if (ist >= 1 && ist <=
sn.stations.size() &&
236 " is a QUEUEING place: its embedded service is state that the marking does "
237 "not carry, so the net is not the token-container Petri net this product form "
242 std::vector<double> lambda(E, 0.0);
243 std::vector<std::vector<double>> Iv(E), Ov(E);
244 for (std::size_t e = 0; e < E; ++e) {
248 bool consumes =
false;
249 for (std::size_t l = 0; l < L; ++l) {
250 if (std::isfinite(md[e].inhib[l]))
252 "spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
253 std::to_string(md[e].trans) +
254 " has an inhibitor arc. An inhibitor zeroes the firing rate on markings "
255 "that still satisfy m >= I(t), so the rate is not lambda*psi(m-I)/psi(m) "
256 "on any psi and the net has no product form of this kind");
257 if (md[e].enab[l] > 0) consumes =
true;
259 if (md[e].srv != 1 && !consumes)
260 throw InputError(
"spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
261 std::to_string(md[e].trans) +
262 " has several servers but consumes from no place, so its "
263 "enabling degree is unbounded and its firing rate undefined");
264 if (!(lambda[e] > 0))
265 throw InputError(
"spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
266 std::to_string(md[e].trans) +
267 " has a non-positive firing rate");
272 bool okgeo =
true, okma =
true;
273 for (std::size_t s = 0; s < states.size(); ++s) {
274 std::vector<double> m(L, 0.0);
275 for (std::size_t l = 0; l < L; ++l) m[l] = states[s][l];
276 for (std::size_t e = 0; e < E; ++e) {
278 for (std::size_t l = 0; l < L && enabled; ++l)
279 if (m[l] < md[e].enab[l]) enabled =
false;
280 if (!enabled)
continue;
281 double actual = lambda[e] * detail::spn_pf_servers<T>(m, md[e], L);
286 for (std::size_t pp = 0; pp < info.
places.size(); ++pp)
290 const double ma = lambda[e] * detail::spn_pf_massaction(m, md[e].enab, L);
291 okgeo = okgeo && detail::spn_pf_close(actual, lambda[e], options.tol);
292 okma = okma && detail::spn_pf_close(actual, ma, options.tol);
295 "spn_pf: mode " + std::to_string(md[e].mode + 1) +
" of node " +
296 std::to_string(md[e].trans) +
" fires at rate " + std::to_string(actual) +
297 " in a reachable marking, which is neither its rate constant "
298 "(single-server, psi = 1) nor its mass-action rate " +
300 " (psi = prod 1/m!). LINE's rate law on this mode is "
301 "lambda*min(enabling degree, servers)*g(m), and no psi puts that in the "
302 "form lambda*psi(m-I)/psi(m)");
305 const std::string kind = okgeo ?
"geometric" :
"massaction";
308 std::vector<std::vector<double>> C;
309 std::map<std::string, std::size_t> seen;
310 std::vector<std::size_t> src(E), dst(E);
311 for (std::size_t e = 0; e < E; ++e) src[e] = detail::spn_pf_complex(Iv[e], C, seen);
312 for (std::size_t e = 0; e < E; ++e) dst[e] = detail::spn_pf_complex(Ov[e], C, seen);
313 const std::size_t c = C.size();
317 for (std::size_t i = 0; i < c; ++i)
318 for (std::size_t j = 0; j < c; ++j) A(i, j) = 0.0;
319 for (std::size_t e = 0; e < E; ++e) {
320 if (src[e] == dst[e])
continue;
321 A(dst[e], src[e]) += lambda[e];
322 A(src[e], src[e]) -= lambda[e];
326 std::vector<int> lclass(c, -1);
327 std::size_t nlink = 0;
328 for (std::size_t v = 0; v < c; ++v) {
329 if (lclass[v] >= 0)
continue;
330 std::vector<std::size_t> stack(1, v);
331 lclass[v] =
static_cast<int>(nlink);
332 while (!stack.empty()) {
333 const std::size_t u = stack.back();
335 for (std::size_t e = 0; e < E; ++e) {
337 if (src[e] == u) w = dst[e];
338 else if (dst[e] == u) w = src[e];
339 if (w < c && lclass[w] < 0) {
340 lclass[w] =
static_cast<int>(nlink);
348 for (std::size_t b = 0; b < nlink && wr; ++b) {
350 for (std::size_t v = 0; v < c && v0 == c; ++v)
351 if (lclass[v] ==
static_cast<int>(b)) v0 = v;
352 const std::vector<bool> fwd = detail::spn_pf_reach(v0, src, dst, c);
353 const std::vector<bool> bwd = detail::spn_pf_reach(v0, dst, src, c);
354 for (std::size_t v = 0; v < c; ++v)
355 if (lclass[v] ==
static_cast<int>(b) && (!fwd[v] || !bwd[v])) wr =
false;
358 for (std::size_t e = 0; e < E; ++e)
359 for (std::size_t l = 0; l < L; ++l) netm(e, l) = Ov[e][l] - Iv[e][l];
361 const std::size_t srank =
rref(netmc, line::detail::lstsq_tolerance(netm)).size();
362 const int deficiency =
static_cast<int>(c) -
static_cast<int>(nlink) -
363 static_cast<int>(srank);
366 std::vector<double> kappa(c, 0.0);
367 for (std::size_t b = 0; b < nlink; ++b) {
368 std::vector<std::size_t> idx;
369 for (std::size_t v = 0; v < c; ++v)
370 if (lclass[v] ==
static_cast<int>(b)) idx.push_back(v);
371 if (idx.size() == 1) {
378 for (std::size_t i = 0; i < idx.size(); ++i)
379 for (std::size_t j = 0; j < idx.size(); ++j) Qb(i, j) = A(idx[j], idx[i]);
382 for (std::size_t i = 0; i < idx.size(); ++i) mx = std::max(mx, pb[i]);
383 for (std::size_t i = 0; i < idx.size(); ++i) {
386 "spn_pf: linkage class " + std::to_string(b + 1) +
" of the complex graph "
387 "carries no flow through complex " + std::to_string(idx[i] + 1) +
388 ", so the net admits no positive complex-balanced point. A weakly "
389 "reversible net has a strictly positive balance flow on every linkage "
390 "class; this one is " + detail::spn_pf_wrtext(wr));
391 kappa[idx[i]] = pb[i] / mx;
396 std::vector<std::vector<double>> rows;
397 std::vector<double> rhs;
398 for (std::size_t b = 0; b < nlink; ++b) {
400 for (std::size_t v = 0; v < c; ++v) {
401 if (lclass[v] !=
static_cast<int>(b))
continue;
406 std::vector<double> row(L, 0.0);
407 for (std::size_t l = 0; l < L; ++l) row[l] = C[v][l] - C[v0][l];
409 rhs.push_back(std::log(kappa[v]) - std::log(kappa[v0]));
412 std::vector<double> x(L, 0.0);
415 const std::size_t nr = rows.size();
417 for (std::size_t i = 0; i < nr; ++i)
418 for (std::size_t j = 0; j < nr; ++j) {
420 for (std::size_t l = 0; l < L; ++l) s += rows[i][l] * rows[j][l];
424 for (std::size_t l = 0; l < L; ++l) {
426 for (std::size_t i = 0; i < nr; ++i) s += rows[i][l] * w.
x[i];
429 double res = 0.0, scale = 1.0;
430 for (std::size_t i = 0; i < nr; ++i) {
432 for (std::size_t l = 0; l < L; ++l) s += rows[i][l] * x[l];
433 res = std::max(res, std::fabs(s - rhs[i]));
434 scale = std::max(scale, std::fabs(rhs[i]));
436 if (res > options.tol * scale)
438 "spn_pf: the complex-balance equations are inconsistent (residual " +
439 std::to_string(res) +
"): this net has no product form of the tested kind at "
440 "these rates. Its deficiency is " + std::to_string(deficiency) +
" and it is " +
441 detail::spn_pf_wrtext(wr) +
"; the Deficiency Zero Theorem guarantees a "
442 "solution only at deficiency 0 with weak reversibility");
444 std::vector<double> yd(L, 1.0);
445 for (std::size_t l = 0; l < L; ++l) yd[l] = std::exp(x[l]);
448 std::vector<double> psi(c, 1.0);
449 for (std::size_t v = 0; v < c; ++v) {
451 for (std::size_t l = 0; l < L; ++l) p *= std::pow(yd[l], C[v][l]);
454 double resb = 0.0, scaleb = std::numeric_limits<double>::min();
455 for (std::size_t v = 0; v < c; ++v) {
456 double s = 0.0, sa = 0.0;
457 for (std::size_t u = 0; u < c; ++u) {
458 s += A(v, u) * psi[u];
459 sa += std::fabs(A(v, u)) * psi[u];
461 resb = std::max(resb, std::fabs(s));
462 scaleb = std::max(scaleb, sa);
464 if (resb > options.tol * scaleb)
466 "spn_pf: complex balance fails at the computed point (relative residual " +
467 std::to_string(resb / scaleb) +
"), so the product form would not be stationary");
473 for (std::size_t l = 0; l < L; ++l) {
475 const int d =
spn.mdds.domain[l];
478 for (
int k = 0; k < d; ++k) {
481 pw = T(pw * out.
y[l]);
483 out.
g[l][k] = kind ==
"massaction" ? T(pw / fact) : pw;