LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
spn_conv.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_CONV_H
6#define LINE_API_SPN_SPN_CONV_H
7
8/**
9 * @file
10 * @ingroup api_spn
11 * Convolution algorithm for the normalising constant of an S-invariant
12 * reachable product-form stochastic Petri net.
13 *
14 * J. Coleman, W. Henderson, P. Taylor, "Product form equilibrium distributions
15 * and a convolution algorithm for stochastic Petri nets", Performance
16 * Evaluation 26(3), 1996, 159-180; presented as the point of comparison for
17 * MDD-rec in S. Balsamo, A. Marin, I. Stojic, FGCS 111 (2020), Sec. 5.1.
18 *
19 * With S the minimal-support S-invariant matrix and V = S m0 the load vector,
20 * the reachability set of an S-INVARIANT REACHABLE net is exactly
21 * {m >= 0 : S m = V}, and conditioning on the marking of one place partitions it
22 * (Lemma 5.1). Writing G_j(W) for the mass of the markings supported on the
23 * first j places with S m = W,
24 *
25 * G_0(W) = [W == 0], G_j(W) = sum_i g_j(i) G_{j-1}(W - i S_j),
26 *
27 * and G = G_n(V). On a net whose only invariant is "the tokens are conserved"
28 * this is Buzen's convolution for a closed queueing network, one place per
29 * station.
30 *
31 * NO ILP IS SOLVED. The paper obtains the marking set M_p(P',W) from the
32 * feasibility of an integer program (Prop. 5.2) so that the sum skips the terms
33 * that contribute nothing. Here the sum simply runs over i whose residual
34 * W - i S_j stays non-negative and the recursion returns zero on an infeasible
35 * residual, which gives the same value: the ILP is an optimisation of the
36 * enumeration, not part of the definition. Memoising on (j, W) keeps the walk
37 * over the reachable residuals rather than over all of them.
38 *
39 * S-INVARIANT REACHABILITY IS NOT CHECKED, and cannot be cheaply: no algorithm
40 * is known that decides it without generating the reachability set (FGCS, Sec.
41 * 5.1). On a net that fails it, {m : S m = V} is strictly larger than the
42 * reachable set and this returns a normalising constant over unreachable
43 * markings too, which is why `mdd::mdd_rec` -- which walks the reachable set
44 * itself -- is the general algorithm and this one the special case. Compare the
45 * two on a new net before trusting this one on it.
46 */
47
48#include <cstddef>
49#include <map>
50#include <vector>
51
53#include "line/num/number.h"
54#include "line/util/error.h"
55
56namespace line {
57namespace spn {
58
59namespace detail {
60
61template <class T>
62T spn_conv_rec(std::size_t j, const std::vector<long long>& W,
63 const std::vector<std::vector<long long>>& S,
64 const std::vector<std::vector<T>>& g,
65 std::map<std::pair<std::size_t, std::vector<long long>>, T>& memo) {
66 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
67 if (j == 0) {
68 for (std::size_t r = 0; r < W.size(); ++r)
69 if (W[r] != 0) return zero;
70 return one;
71 }
72 const std::pair<std::size_t, std::vector<long long>> key(j, W);
73 const typename std::map<std::pair<std::size_t, std::vector<long long>>, T>::const_iterator it =
74 memo.find(key);
75 if (it != memo.end()) return it->second;
76
77 const std::size_t p = j - 1;
78 T acc = zero;
79 for (std::size_t i = 0; i < g[p].size(); ++i) {
80 std::vector<long long> rem = W;
81 bool feasible = true;
82 for (std::size_t r = 0; r < W.size() && feasible; ++r) {
83 rem[r] -= static_cast<long long>(i) * S[r][p];
84 if (rem[r] < 0) feasible = false;
85 }
86 if (!feasible) break; // S is non-negative, so larger i only gets worse
87 if (g[p][i] == zero) continue;
88 acc += T(g[p][i] * spn_conv_rec(j - 1, rem, S, g, memo));
89 }
90 memo[key] = acc;
91 return acc;
92}
93
94} // namespace detail
95
96/**
97 * The normalising constant by convolution over the invariant load vector.
98 *
99 * @param S S[i][p], the minimal-support S-invariants, one row per invariant
100 * @param V the load vector S m0, one entry per invariant
101 * @param g g[p][i] is g_p(i), the product-form factor of i tokens in place p;
102 * its length bounds the marking of place p
103 */
104template <class T>
105T spn_conv(const std::vector<std::vector<long long>>& S, const std::vector<long long>& V,
106 const std::vector<std::vector<T>>& g) {
107 if (S.empty()) throw InputError("spn_conv: the net has no S-invariant to convolve over");
108 if (S.size() != V.size())
109 throw InputError("spn_conv: one load-vector entry per invariant is required");
110 const std::size_t n = g.size();
111 for (std::size_t r = 0; r < S.size(); ++r) {
112 if (S[r].size() != n)
113 throw InputError("spn_conv: the invariant matrix and g must agree on the place count");
114 for (std::size_t p = 0; p < n; ++p)
115 if (S[r][p] < 0)
116 throw InputError("spn_conv: an S-invariant has a negative weight, so the residual "
117 "recursion has no monotone bound on the marking");
118 }
119 std::map<std::pair<std::size_t, std::vector<long long>>, T> memo;
120 return detail::spn_conv_rec(n, V, S, g, memo);
121}
122
123/** Convolve straight off the invariant basis `spn_sinvariants` returned. */
124template <class T>
125T spn_conv(const SpnInvariants& inv, const std::vector<std::vector<T>>& g) {
126 return spn_conv(inv.S, inv.V, g);
127}
128
129} // namespace spn
130} // namespace line
131
132#endif // LINE_API_SPN_SPN_CONV_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
T spn_conv(const std::vector< std::vector< long long > > &S, const std::vector< long long > &V, const std::vector< std::vector< T > > &g)
The normalising constant by convolution over the invariant load vector.
Definition spn_conv.h:105
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.
The invariant basis of a net, in place-level coordinates.
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.