LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
sn_pn_firing_rates.m
1%{ @file sn_pn_firing_rates.m
2 % @brief Recovers per-mode transition firing rates of a Petri net from the
3 % Place throughputs
4 %
5 % @author LINE Development Team
6%}
7
8%{
9 % @brief Recovers per-mode transition firing rates from the Place throughputs
10 %
11 % @details
12 % The firing rates of a Petri net are not carried by the network structure,
13 % but they are determined by the Place throughputs together with the net
14 % structure. Writing x for the vector of per-mode firing rates, two families
15 % of equations hold at steady state, for every Place p and class k:
16 %
17 % departure sum over the modes consuming (p,k) of x, weighted by the
18 % input arc multiplicity when TPUTISTOKENS is true and
19 % unweighted when it is false, equals TN(p,k)
20 % balance sum over all modes of x times (produced minus consumed)
21 % equals zero
22 %
23 % The system is solved in least squares. That is deliberate: an exact solver
24 % supplies throughputs that satisfy it exactly and the fit is then the exact
25 % answer, whereas a simulator supplies estimates that satisfy it only up to
26 % sampling error and the least-squares fit is the right estimator there. A
27 % residual test would reject every simulated run.
28 %
29 % @par Syntax:
30 % @code
31 % [x, consumed, produced, placeNodes] = sn_pn_firing_rates(sn, TN, tputIsTokens)
32 % @endcode
33 %
34 % @par Parameters:
35 % <table>
36 % <tr><th>Name<th>Description
37 % <tr><td>sn<td>Network structure
38 % <tr><td>TN<td>Average throughputs at stations
39 % <tr><td>tputIsTokens<td>True when TN counts tokens, false when it counts firing events
40 % </table>
41 %
42 % @par Returns:
43 % <table>
44 % <tr><th>Name<th>Description
45 % <tr><td>x<td>Firing rate per (transition, mode) pair, empty when undetermined
46 % <tr><td>consumed<td>Tokens consumed, indexed (mode, place, class)
47 % <tr><td>produced<td>Tokens produced, indexed (mode, place, class)
48 % <tr><td>placeNodes<td>Node indices of the Places, in the order used above
49 % </table>
50%}
51function [x, consumed, produced, placeNodes] = sn_pn_firing_rates(sn, TN, tputIsTokens)
52
53x = [];
54consumed = [];
55produced = [];
56
57R = sn.nclasses;
58placeNodes = find(sn.nodetype == NodeType.Place);
59transNodes = find(sn.nodetype == NodeType.Transition);
60if isempty(placeNodes) || isempty(transNodes) || isempty(TN)
61 return
62end
63
64% see _kb/04-networkstruct.md (api/sn/*.m derived-field helpers) for rationale
65if any(sn.nodetype == NodeType.Source) || any(sn.nodetype == NodeType.Sink)
66 return
67end
68statefulNodes = find(sn.isstateful);
69for pp = 1:length(placeNodes)
70 sfp = find(statefulNodes == placeNodes(pp), 1);
71 if isempty(sfp)
72 return
73 end
74 for sfj = 1:length(statefulNodes)
75 if sfj == sfp
76 continue
77 end
78 blockOut = sn.rt((sfp-1)*R+(1:R), (sfj-1)*R+(1:R));
79 blockIn = sn.rt((sfj-1)*R+(1:R), (sfp-1)*R+(1:R));
80 if (any(blockOut(:) > 0) || any(blockIn(:) > 0)) ...
81 && sn.nodetype(statefulNodes(sfj)) ~= NodeType.Transition
82 return
83 end
84 end
85end
86
87% Enumerate the (transition, mode) pairs: a mode is what carries a firing
88% rate, and a transition may hold several.
89modeTrans = [];
90modeIdx = [];
91modeTimed = [];
92for tt = 1:length(transNodes)
93 ind = transNodes(tt);
94 param = sn.nodeparam{ind};
95 if isempty(param) || ~isfield(param, 'nmodes')
96 return
97 end
98 for m = 1:param.nmodes
99 modeTrans(end+1) = ind; %#ok<AGROW>
100 modeIdx(end+1) = m; %#ok<AGROW>
101 % An immediate firing takes zero time and is not a timed event, so
102 % the analyzers never count it in TN. Its rate is an unknown to be
103 % recovered from the balance equations, not a measured quantity.
104 timed = true;
105 if isfield(param, 'timing') && numel(param.timing) >= m
106 timed = param.timing(m) ~= TimingStrategy.IMMEDIATE;
107 end
108 modeTimed(end+1) = timed; %#ok<AGROW>
109 end
110end
111modeTimed = logical(modeTimed);
112nModes = length(modeTrans);
113if nModes == 0
114 return
115end
116
117consumed = zeros(nModes, length(placeNodes), R);
118produced = zeros(nModes, length(placeNodes), R);
119for mm = 1:nModes
120 param = sn.nodeparam{modeTrans(mm)};
121 enab = reshape(param.enabling{modeIdx(mm)}, [], R);
122 fire = reshape(param.firing{modeIdx(mm)}, [], R);
123 for pp = 1:length(placeNodes)
124 pind = placeNodes(pp);
125 for k = 1:R
126 consumed(mm, pp, k) = max(0, enab(pind, k));
127 produced(mm, pp, k) = max(0, fire(pind, k));
128 end
129 end
130end
131
132% see _kb/04-networkstruct.md (api/sn/*.m derived-field helpers) for rationale
133nEq = 2 * length(placeNodes) * R;
134A = zeros(nEq, nModes);
135b = zeros(nEq, 1);
136row = 0;
137nMeasured = 0;
138for pp = 1:length(placeNodes)
139 ist = sn.nodeToStation(placeNodes(pp));
140 for k = 1:R
141 if tputIsTokens
142 arow = consumed(:, pp, k)';
143 else
144 arow = double(consumed(:, pp, k)' > 0);
145 end
146 arow(~modeTimed) = 0;
147 if any(arow ~= 0)
148 row = row + 1;
149 A(row, :) = arow;
150 b(row) = TN(ist, k);
151 nMeasured = nMeasured + 1;
152 end
153
154 row = row + 1;
155 A(row, :) = produced(:, pp, k)' - consumed(:, pp, k)';
156 b(row) = 0;
157 end
158end
159A = A(1:row, :);
160b = b(1:row);
161
162% With no measured row the system is homogeneous and pinv returns the zero
163% vector, which would report every Place as idle. Keep what the caller had.
164if nMeasured == 0
165 return
166end
167
168xfit = pinv(A) * b;
169
170% A negative firing rate means the net structure was not read as intended;
171% reporting a rate that cannot occur would be worse than reporting nothing.
172if any(xfit < -1e-6 * max(1, max(abs(xfit))))
173 return
174end
175
176x = xfit;
177end