LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
wf_pattern_updater.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_WF_WF_PATTERN_UPDATER_H
6#define LINE_API_WF_WF_PATTERN_UPDATER_H
7
8/**
9 * @file
10 * @ingroup api_wf
11 * Collapse the detected workflow patterns and convolve their service laws.
12 *
13 * Templated port of the native Python `line_solver/api/wf/pattern_updater.py`,
14 * cross-checked against jar/src/main/java/jline/api/wf/Wf_pattern_updater.java.
15 * There is no MATLAB counterpart: `api/wf` exists in the JAR and in Python only.
16 *
17 * PYTHON IS THE REFERENCE HERE, NOT THE JAR. The JAR's four convolutions are
18 * STUBS -- `convolveSequence`, `convolveParallel` and `convolveBranches` return
19 * `params.get(0)` unchanged and `convolveLoop` returns its argument -- so the
20 * Java class rewrites the link matrix and then reports the FIRST branch's
21 * service law as the law of the collapsed pattern. Its `removeMatrixRows` is
22 * also defective: the loop over the sorted row list `return`s inside its first
23 * iteration, so at most one row is ever removed. Python implements the actual
24 * phase-type algebra and removes every row, and that is what is ported.
25 *
26 * THE FOUR CONVOLUTIONS, on representations (alpha, T) that need not be
27 * honest phase types -- alpha may sum below one, and the deficit 1 - alpha e is
28 * treated as instantaneous completion, which is what makes the formulas below
29 * carry the sub-stochastic entry vectors the detectors produce:
30 *
31 * - SEQUENCE, the convolution of the two durations:
32 * alpha = [a1, (1 - a1 e1) a2], T = [[T1, (-T1 e1) a2], [0, T2]].
33 * - PARALLEL, the MAXIMUM of the two durations, so the state is the pair of
34 * phases until one branch finishes and the surviving branch alone after:
35 * alpha = [kron(a1,a2), (1 - a2 e2) a1, (1 - a1 e1) a2],
36 * T = [[T1 (x) I + I (x) T2, I (x) (-T2 e2), (-T1 e1) (x) I],
37 * [0, T1, 0], [0, 0, T2]].
38 * - LOOP, a geometric number of repetitions with probability p: the exit flow
39 * is fed back into the entry law, T <- T + p (-T e) alpha, which leaves
40 * alpha unchanged. p outside (0,1) is the identity.
41 * - BRANCH, a probabilistic choice: alpha = [p1 a1, p2 a2, ...] over a
42 * block-diagonal T, with the probabilities renormalized (and made uniform
43 * when they sum to zero).
44 *
45 * `find_fork_join_for_parallel` returns "none" in BOTH references, so the
46 * parallel arm of `update_patterns` never fires. That is reproduced rather than
47 * invented: supplying a fork/join search here would collapse patterns neither
48 * reference collapses, and the resulting workflow would not be the one any
49 * other codebase produces. The convolution itself is implemented and reachable
50 * through `convolve_parallel`, which is what a caller with its own fork/join
51 * pairing needs.
52 *
53 * ARITHMETIC: field. Block assembly, Kronecker products, and one division in
54 * the probability renormalization, so it instantiates under Rational.
55 */
56
57#include <algorithm>
58#include <cstddef>
59#include <map>
60#include <set>
61#include <vector>
62
69#include "line/num/number.h"
70#include "line/util/error.h"
71#include "line/util/matrix.h"
72
73namespace line {
74namespace wf {
75
76/** A phase-type-shaped service law: entry vector and transient generator. */
77template <class T>
79 std::vector<T> alpha;
81};
82
83/** The collapsed link matrix and the service law of every surviving node. */
84template <class T>
87 std::map<int, ServiceParameters<T>> serviceParameters;
88};
89
90/** Statistics of one update pass; the Java getUpdateStats map. */
91template <class T>
93 std::size_t originalLinks = 0;
94 std::size_t updatedLinks = 0;
95 long linksReduced = 0;
96 std::size_t serviceNodes = 0;
98};
99
100namespace detail {
101
102/** The unit-mass fallback both references return for an empty parameter list. */
103template <class T>
104ServiceParameters<T> wf_unit_params() {
106 p.alpha.assign(1, num_traits<T>::from_int(1));
108 return p;
109}
110
111/** -T e, the exit rate out of each phase. */
112template <class T>
113std::vector<T> wf_exit_rate(const Matrix<T>& Tm) {
114 const T zero = num_traits<T>::from_int(0);
115 std::vector<T> v(Tm.rows(), zero);
116 for (std::size_t i = 0; i < Tm.rows(); ++i) {
117 T s = zero;
118 for (std::size_t j = 0; j < Tm.cols(); ++j) s += Tm(i, j);
119 v[i] = -s;
120 }
121 return v;
122}
123
124/** 1 - alpha e, the mass the entry law leaves for instantaneous completion. */
125template <class T>
126T wf_exit_prob(const std::vector<T>& alpha) {
127 T s = num_traits<T>::from_int(0);
128 for (std::size_t i = 0; i < alpha.size(); ++i) s += alpha[i];
129 return T(num_traits<T>::from_int(1) - s);
130}
131
132/** Copy `src` into `dst` with its top-left corner at (r0, c0). */
133template <class T>
134void wf_place(Matrix<T>& dst, const Matrix<T>& src, std::size_t r0, std::size_t c0) {
135 for (std::size_t i = 0; i < src.rows(); ++i)
136 for (std::size_t j = 0; j < src.cols(); ++j) dst(r0 + i, c0 + j) = src(i, j);
137}
138
139/** Drop the listed rows; duplicates and out-of-range indices are ignored. */
140template <class T>
141Matrix<T> wf_remove_rows(const Matrix<T>& m, const std::vector<std::size_t>& rows) {
142 if (rows.empty() || m.rows() == 0) return m;
143 std::vector<bool> keep(m.rows(), true);
144 for (std::size_t i = 0; i < rows.size(); ++i)
145 if (rows[i] < m.rows()) keep[rows[i]] = false;
146 std::size_t n = 0;
147 for (std::size_t i = 0; i < m.rows(); ++i)
148 if (keep[i]) ++n;
149 Matrix<T> out(n, m.cols(), num_traits<T>::from_int(0));
150 std::size_t r = 0;
151 for (std::size_t i = 0; i < m.rows(); ++i) {
152 if (!keep[i]) continue;
153 for (std::size_t j = 0; j < m.cols(); ++j) out(r, j) = m(i, j);
154 ++r;
155 }
156 return out;
157}
158
159/** Rewrite every reference to `oldNode` in columns 0 and 1 as `newNode`. */
160template <class T>
161void wf_replace_node(Matrix<T>& m, int oldNode, int newNode) {
162 const T nn = num_traits<T>::from_int(newNode);
163 for (std::size_t i = 0; i < m.rows(); ++i) {
164 if (wf_id(m, i, 0) == oldNode) m(i, 0) = nn;
165 if (wf_id(m, i, 1) == oldNode) m(i, 1) = nn;
166 }
167}
168
169/** Rows whose source or target is one of `nodes`. */
170template <class T>
171std::vector<std::size_t> wf_rows_involving(const Matrix<T>& m, const std::vector<int>& nodes) {
172 const std::set<int> s(nodes.begin(), nodes.end());
173 std::vector<std::size_t> out;
174 for (std::size_t i = 0; i < m.rows(); ++i)
175 if (s.count(wf_id(m, i, 0)) || s.count(wf_id(m, i, 1))) out.push_back(i);
176 return out;
177}
178
179} // namespace detail
180
181/** Convolution of the durations, i.e. the service laws run one after another. */
182template <class T>
184 if (params.empty()) return detail::wf_unit_params<T>();
185 if (params.size() == 1) return params[0];
186 const T zero = num_traits<T>::from_int(0);
187
188 ServiceParameters<T> acc = params[0];
189 for (std::size_t k = 1; k < params.size(); ++k) {
190 const std::vector<T>& a2 = params[k].alpha;
191 const Matrix<T>& T2 = params[k].T_;
192 const std::size_t n1 = acc.alpha.size(), n2 = a2.size();
193 const T ex = detail::wf_exit_prob(acc.alpha);
194 const std::vector<T> er = detail::wf_exit_rate(acc.T_);
195
196 std::vector<T> na(n1 + n2, zero);
197 for (std::size_t i = 0; i < n1; ++i) na[i] = acc.alpha[i];
198 for (std::size_t j = 0; j < n2; ++j) na[n1 + j] = T(ex * a2[j]);
199
200 Matrix<T> nt(n1 + n2, n1 + n2, zero);
201 detail::wf_place(nt, acc.T_, 0, 0);
202 detail::wf_place(nt, T2, n1, n1);
203 for (std::size_t i = 0; i < n1; ++i)
204 for (std::size_t j = 0; j < n2; ++j) nt(i, n1 + j) = T(er[i] * a2[j]);
205
206 acc.alpha = na;
207 acc.T_ = nt;
208 }
209 return acc;
210}
211
212/** Maximum of the durations, i.e. a fork whose join waits for every branch. */
213template <class T>
215 if (params.empty()) return detail::wf_unit_params<T>();
216 if (params.size() == 1) return params[0];
217 const T zero = num_traits<T>::from_int(0);
218
219 ServiceParameters<T> acc = params[0];
220 for (std::size_t k = 1; k < params.size(); ++k) {
221 const std::vector<T>& a2 = params[k].alpha;
222 const Matrix<T>& T2 = params[k].T_;
223 const std::size_t n1 = acc.alpha.size(), n2 = a2.size(), np = n1 * n2;
224 const T e1 = detail::wf_exit_prob(acc.alpha), e2 = detail::wf_exit_prob(a2);
225 const std::vector<T> r1 = detail::wf_exit_rate(acc.T_), r2 = detail::wf_exit_rate(T2);
226
227 std::vector<T> na(np + n1 + n2, zero);
228 for (std::size_t i = 0; i < n1; ++i)
229 for (std::size_t j = 0; j < n2; ++j) na[i * n2 + j] = T(acc.alpha[i] * a2[j]);
230 for (std::size_t i = 0; i < n1; ++i) na[np + i] = T(e2 * acc.alpha[i]);
231 for (std::size_t j = 0; j < n2; ++j) na[np + n1 + j] = T(e1 * a2[j]);
232
233 Matrix<T> nt(np + n1 + n2, np + n1 + n2, zero);
234 // T1 (x) I + I (x) T2 on the both-alive block.
235 for (std::size_t i = 0; i < n1; ++i)
236 for (std::size_t j = 0; j < n2; ++j) {
237 const std::size_t r = i * n2 + j;
238 for (std::size_t ii = 0; ii < n1; ++ii) nt(r, ii * n2 + j) += acc.T_(i, ii);
239 for (std::size_t jj = 0; jj < n2; ++jj) nt(r, i * n2 + jj) += T2(j, jj);
240 }
241 // Branch 2 finishes first -> only branch 1 is left, in phase i.
242 for (std::size_t i = 0; i < n1; ++i)
243 for (std::size_t j = 0; j < n2; ++j) nt(i * n2 + j, np + i) = r2[j];
244 // Branch 1 finishes first -> only branch 2 is left, in phase j.
245 for (std::size_t i = 0; i < n1; ++i)
246 for (std::size_t j = 0; j < n2; ++j) nt(i * n2 + j, np + n1 + j) = r1[i];
247 detail::wf_place(nt, acc.T_, np, np);
248 detail::wf_place(nt, T2, np + n1, np + n1);
249
250 acc.alpha = na;
251 acc.T_ = nt;
252 }
253 return acc;
254}
255
256/**
257 * Geometric repetition: the exit flow re-enters through alpha with probability
258 * `loopProb`. Outside (0,1) the law is returned unchanged, as in the reference.
259 */
260template <class T>
261ServiceParameters<T> convolve_loop(const ServiceParameters<T>& params, const T& loopProb) {
262 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
263 if (!(loopProb > zero) || !(loopProb < one)) return params;
264 const std::vector<T> er = detail::wf_exit_rate(params.T_);
265 ServiceParameters<T> out = params;
266 for (std::size_t i = 0; i < out.T_.rows(); ++i)
267 for (std::size_t j = 0; j < out.T_.cols(); ++j)
268 out.T_(i, j) = T(out.T_(i, j) + loopProb * er[i] * params.alpha[j]);
269 return out;
270}
271
272/** Probabilistic choice among the alternatives, on a block-diagonal generator. */
273template <class T>
275 const std::vector<T>& probsIn) {
276 if (params.empty()) return detail::wf_unit_params<T>();
277 if (params.size() == 1) return params[0];
278 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
279
280 std::vector<T> probs(params.size(), zero);
281 T total = zero;
282 for (std::size_t i = 0; i < params.size(); ++i) {
283 probs[i] = i < probsIn.size() ? probsIn[i] : zero;
284 total += probs[i];
285 }
286 if (total > zero) {
287 for (std::size_t i = 0; i < probs.size(); ++i) probs[i] = T(probs[i] / total);
288 } else {
289 const T u = T(one / num_traits<T>::from_int(static_cast<long>(params.size())));
290 for (std::size_t i = 0; i < probs.size(); ++i) probs[i] = u;
291 }
292
293 std::size_t n = 0;
294 for (std::size_t i = 0; i < params.size(); ++i) n += params[i].alpha.size();
296 out.alpha.assign(n, zero);
297 out.T_ = Matrix<T>(n, n, zero);
298 std::size_t off = 0;
299 for (std::size_t i = 0; i < params.size(); ++i) {
300 for (std::size_t j = 0; j < params[i].alpha.size(); ++j)
301 out.alpha[off + j] = T(probs[i] * params[i].alpha[j]);
302 detail::wf_place(out.T_, params[i].T_, off, off);
303 off += params[i].alpha.size();
304 }
305 return out;
306}
307
308/**
309 * The fork and join bracketing a parallel pattern.
310 *
311 * BOTH references return "none" unconditionally, so the parallel arm of
312 * `update_patterns` never fires. Reproduced deliberately; see the header note.
313 */
314template <class T>
315bool find_fork_join_for_parallel(const Matrix<T>&, const std::vector<int>&, int*, int*) {
316 return false;
317}
318
319/**
320 * Collapse the four pattern families in the reference's order: sequences,
321 * parallels, loops, branches. Each stage re-detects on the matrix the previous
322 * stage produced.
323 */
324template <class T>
326 const std::vector<int>& serviceNodes,
327 const std::vector<int>& forkNodes,
328 const std::vector<int>& joinNodes,
329 const std::vector<int>& routerNodes,
330 const std::map<int, ServiceParameters<T>>& serviceParams) {
331 detail::wf_check(linkMatrix);
332 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
333 Matrix<T> m = linkMatrix;
334 std::map<int, ServiceParameters<T>> params = serviceParams;
335 const std::set<int> serviceSet(serviceNodes.begin(), serviceNodes.end());
336
337 // ---- sequences ------------------------------------------------------
338 const std::vector<std::vector<int>> sequences = detect_sequences(m, serviceNodes);
339 if (!sequences.empty()) {
340 std::vector<std::size_t> drop;
341 for (std::size_t i = 0; i < m.rows(); ++i) {
342 const int a = detail::wf_id(m, i, 0), b = detail::wf_id(m, i, 1);
343 if (!serviceSet.count(a) || !serviceSet.count(b)) continue;
344 for (std::size_t s = 0; s < sequences.size(); ++s)
345 for (std::size_t j = 0; j + 1 < sequences[s].size(); ++j)
346 if (sequences[s][j] == a && sequences[s][j + 1] == b) {
347 drop.push_back(i);
348 j = sequences[s].size(); // break, as the reference does
349 }
350 }
351 m = detail::wf_remove_rows(m, drop);
352 for (std::size_t s = 0; s < sequences.size(); ++s) {
353 const std::vector<int>& seq = sequences[s];
354 if (seq.size() < 2) continue;
355 detail::wf_replace_node(m, seq.back(), seq.front());
356 std::vector<ServiceParameters<T>> sp;
357 for (std::size_t j = 0; j < seq.size(); ++j) {
358 typename std::map<int, ServiceParameters<T>>::const_iterator it = params.find(seq[j]);
359 if (it != params.end()) sp.push_back(it->second);
360 }
361 params[seq.front()] = convolve_sequence(sp);
362 for (std::size_t j = 1; j < seq.size(); ++j) params.erase(seq[j]);
363 }
364 }
365
366 // ---- parallels ------------------------------------------------------
367 const std::vector<std::vector<int>> parallels =
368 detect_parallel(m, serviceNodes, forkNodes, joinNodes);
369 for (std::size_t p = 0; p < parallels.size(); ++p) {
370 const std::vector<int>& par = parallels[p];
371 if (par.size() < 2) continue;
372 int forkNode = -1, joinNode = -1;
373 if (!find_fork_join_for_parallel(m, par, &forkNode, &joinNode)) continue;
374 m = detail::wf_remove_rows(m, detail::wf_rows_involving(m, par));
375 detail::wf_replace_node(m, forkNode, par.front());
376 detail::wf_replace_node(m, joinNode, par.front());
377 std::vector<ServiceParameters<T>> sp;
378 for (std::size_t j = 0; j < par.size(); ++j) {
379 typename std::map<int, ServiceParameters<T>>::const_iterator it = params.find(par[j]);
380 if (it != params.end()) sp.push_back(it->second);
381 }
382 params[par.front()] = convolve_parallel(sp);
383 for (std::size_t j = 1; j < par.size(); ++j) params.erase(par[j]);
384 }
385
386 // ---- loops ----------------------------------------------------------
387 const std::vector<int> loops = detect_loops(m, serviceNodes, routerNodes, joinNodes);
388 const std::set<int> routerSet(routerNodes.begin(), routerNodes.end());
389 for (std::size_t l = 0; l < loops.size(); ++l) {
390 const int loopNode = loops[l];
391 const T loopProb = get_loop_probability(loopNode, m, routerNodes);
392 if (!(num_traits<T>::to_double(loopProb) > lang::GlobalConstants::Zero)) continue;
393
394 std::vector<int> routers;
395 for (std::size_t i = 0; i < m.rows(); ++i) {
396 const int a = detail::wf_id(m, i, 0), b = detail::wf_id(m, i, 1);
397 if (!((a == loopNode && routerSet.count(b)) || (b == loopNode && routerSet.count(a))))
398 continue;
399 if (routerSet.count(a)) routers.push_back(a);
400 if (routerSet.count(b)) routers.push_back(b);
401 }
402 std::sort(routers.begin(), routers.end());
403 routers.erase(std::unique(routers.begin(), routers.end()), routers.end());
404
405 const std::set<int> rs(routers.begin(), routers.end());
406 std::vector<std::size_t> drop;
407 for (std::size_t i = 0; i < m.rows(); ++i) {
408 const int a = detail::wf_id(m, i, 0), b = detail::wf_id(m, i, 1);
409 if ((a == loopNode && rs.count(b)) || (b == loopNode && rs.count(a)))
410 drop.push_back(i);
411 }
412 m = detail::wf_remove_rows(m, drop);
413 for (std::size_t r = 0; r < routers.size(); ++r)
414 detail::wf_replace_node(m, routers[r], loopNode);
415 for (std::size_t i = 0; i < m.rows(); ++i)
416 if (detail::wf_id(m, i, 0) == loopNode) m(i, 2) = one;
417
418 typename std::map<int, ServiceParameters<T>>::iterator it = params.find(loopNode);
419 if (it != params.end()) it->second = convolve_loop(it->second, loopProb);
420 }
421
422 // ---- branches -------------------------------------------------------
423 const std::vector<BranchPattern<T>> branches = detect_branches(m, serviceNodes, joinNodes);
424 for (std::size_t b = 0; b < branches.size(); ++b) {
425 const BranchPattern<T>& br = branches[b];
426 if (br.branchNodes.size() < 2 || br.forkNode < 0) continue;
427 m = detail::wf_remove_rows(m, detail::wf_rows_involving(m, br.branchNodes));
428 detail::wf_replace_node(m, br.forkNode, br.branchNodes.front());
429 if (br.hasJoinNode) detail::wf_replace_node(m, br.joinNode, br.branchNodes.front());
430 std::vector<ServiceParameters<T>> sp;
431 for (std::size_t j = 0; j < br.branchNodes.size(); ++j) {
432 typename std::map<int, ServiceParameters<T>>::const_iterator it =
433 params.find(br.branchNodes[j]);
434 if (it != params.end()) sp.push_back(it->second);
435 }
436 params[br.branchNodes.front()] = convolve_branches(sp, br.probabilities);
437 for (std::size_t j = 1; j < br.branchNodes.size(); ++j) params.erase(br.branchNodes[j]);
438 }
439
441 out.linkMatrix = m;
442 out.serviceParameters = params;
443 (void)zero;
444 return out;
445}
446
447/**
448 * Every node the collapsed matrix still references carries a service law.
449 *
450 * The two references test OPPOSITE implications: Python asks that every
451 * referenced node have a law (ported here), the JAR that every law belong to a
452 * referenced node. Python's is the one that catches the failure mode the
453 * collapse can actually produce -- a node left in the matrix whose law was
454 * erased with its pattern.
455 */
456template <class T>
458 std::set<int> referenced;
459 for (std::size_t i = 0; i < w.linkMatrix.rows(); ++i) {
460 referenced.insert(detail::wf_id(w.linkMatrix, i, 0));
461 referenced.insert(detail::wf_id(w.linkMatrix, i, 1));
462 }
463 for (std::set<int>::const_iterator it = referenced.begin(); it != referenced.end(); ++it)
464 if (w.serviceParameters.find(*it) == w.serviceParameters.end()) return false;
465 return true;
466}
467
468/** How much the collapse shrank the link matrix. */
469template <class T>
472 s.originalLinks = originalMatrix.rows();
473 s.updatedLinks = w.linkMatrix.rows();
474 s.linksReduced = static_cast<long>(s.originalLinks) - static_cast<long>(s.updatedLinks);
475 s.serviceNodes = w.serviceParameters.size();
476 if (s.originalLinks > 0)
478 num_traits<T>::from_int(static_cast<long>(s.originalLinks)));
479 return s;
480}
481
482} // namespace wf
483} // namespace line
484
485#endif // LINE_API_WF_WF_PATTERN_UPDATER_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense matrix and non-owning view.
bool find_fork_join_for_parallel(const Matrix< T > &, const std::vector< int > &, int *, int *)
The fork and join bracketing a parallel pattern.
std::vector< int > detect_loops(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes, const std::vector< int > &routerNodes, const std::vector< int > &joinNodes=std::vector< int >())
UpdatedWorkflow< T > update_patterns(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes, const std::vector< int > &forkNodes, const std::vector< int > &joinNodes, const std::vector< int > &routerNodes, const std::map< int, ServiceParameters< T > > &serviceParams)
Collapse the four pattern families in the reference's order: sequences, parallels,...
ServiceParameters< T > convolve_parallel(const std::vector< ServiceParameters< T > > &params)
Maximum of the durations, i.e.
std::vector< std::vector< int > > detect_sequences(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes)
ServiceParameters< T > convolve_loop(const ServiceParameters< T > &params, const T &loopProb)
Geometric repetition: the exit flow re-enters through alpha with probability loopProb.
std::vector< BranchPattern< T > > detect_branches(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes, const std::vector< int > &joinNodes)
bool validate_updated_workflow(const UpdatedWorkflow< T > &w)
Every node the collapsed matrix still references carries a service law.
ServiceParameters< T > convolve_branches(const std::vector< ServiceParameters< T > > &params, const std::vector< T > &probsIn)
Probabilistic choice among the alternatives, on a block-diagonal generator.
UpdateStats< T > get_update_stats(const Matrix< T > &originalMatrix, const UpdatedWorkflow< T > &w)
How much the collapse shrank the link matrix.
ServiceParameters< T > convolve_sequence(const std::vector< ServiceParameters< T > > &params)
Convolution of the durations, i.e.
T get_loop_probability(int serviceNode, const Matrix< T > &linkMatrix, const std::vector< int > &routerNodes)
Probability on the router-to-service edge that closes the loop, 0 when the node is not on a simple lo...
std::vector< std::vector< int > > detect_parallel(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes, const std::vector< int > &forkNodes, const std::vector< int > &joinNodes)
Number-type abstraction for the templated API port.
static constexpr double Zero
Definition lang_types.h:670
Mirrors the Java BranchPattern.
std::vector< int > branchNodes
bool hasJoinNode
the Java Integer may be null
std::vector< T > probabilities
A phase-type-shaped service law: entry vector and transient generator.
Statistics of one update pass; the Java getUpdateStats map.
The collapsed link matrix and the service law of every surviving node.
std::map< int, ServiceParameters< T > > serviceParameters
Branch (probabilistic choice) pattern detection in a workflow network.
Loop pattern detection in a workflow network.
Parallel (fork-join) pattern detection in a workflow network.
Sequence pattern detection in a workflow network.