LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
wf_parallel_detector.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_PARALLEL_DETECTOR_H
6#define LINE_API_WF_WF_PARALLEL_DETECTOR_H
7
8/**
9 * @file
10 * @ingroup api_wf
11 * Parallel (fork-join) pattern detection in a workflow network.
12 *
13 * Templated port of jar/src/main/java/jline/api/wf/Wf_parallel_detector.java
14 * (no MATLAB counterpart, so the JAR is the reference). A fork f and a join j
15 * form a pair when a breadth-first path count from f, forbidden to pass
16 * through any other fork or join, reaches j along more than one path; the
17 * parallel branches of that pair are then the service nodes that are both
18 * reachable from f without passing through j and able to reach j without
19 * passing through f. A pair contributes a pattern only when it has at least
20 * two such service nodes.
21 *
22 * detect_parallel, validate_parallel_pattern and get_parallel_stats are the
23 * three public methods of the Java class, kept 1:1.
24 *
25 * Pure graph traversal; the only arithmetic is the mean branch count, one
26 * division. Finite field computation, instantiates at exact arithmetic, no
27 * transcendental gate.
28 */
29
30#include <cstddef>
31#include <deque>
32#include <map>
33#include <set>
34#include <vector>
35
37#include "line/num/number.h"
38#include "line/util/error.h"
39#include "line/util/matrix.h"
40
41namespace line {
42namespace wf {
43
44/** Mirrors the Java getParallelStats map. */
45template <class T>
47 std::size_t numPatterns = 0;
48 std::size_t totalParallelNodes = 0;
50 std::size_t maxParallelism = 0;
51};
52
53namespace detail {
54
55/**
56 * More than one fork-to-join path avoiding every other fork and join. The path
57 * count is accumulated in breadth-first order, as in the reference; it is a
58 * lower bound on the true path count, not the exact one, but the test is only
59 * "more than one".
60 */
61inline bool wf_valid_fork_join_pair(int fork, int join, const std::map<int, std::vector<int>>& adj,
62 const std::set<int>& forkSet, const std::set<int>& joinSet) {
63 std::deque<int> queue;
64 std::set<int> visited;
65 std::map<int, long> pathCount;
66 queue.push_back(fork);
67 pathCount[fork] = 1;
68
69 while (!queue.empty()) {
70 const int current = queue.front();
71 queue.pop_front();
72 if (!visited.insert(current).second) continue;
73 std::map<int, std::vector<int>>::const_iterator it = adj.find(current);
74 if (it == adj.end()) continue;
75 for (std::size_t k = 0; k < it->second.size(); ++k) {
76 const int nb = it->second[k];
77 if (nb == join) {
78 pathCount[join] += pathCount[current];
79 } else if (!visited.count(nb) && !forkSet.count(nb) && !joinSet.count(nb)) {
80 queue.push_back(nb);
81 pathCount[nb] += pathCount[current];
82 }
83 }
84 }
85 std::map<int, long>::const_iterator jt = pathCount.find(join);
86 return jt != pathCount.end() && jt->second > 1;
87}
88
89/** Nodes reachable from startNode without entering endNode. */
90template <class T>
91std::set<int> wf_reachable(const Matrix<T>& linkMatrix, int startNode, int endNode) {
92 std::set<int> reachable;
93 std::set<int> visited;
94 std::deque<int> queue;
95 queue.push_back(startNode);
96 while (!queue.empty()) {
97 const int current = queue.front();
98 queue.pop_front();
99 if (visited.count(current) || current == endNode) continue;
100 visited.insert(current);
101 for (std::size_t i = 0; i < linkMatrix.rows(); ++i) {
102 const int s = wf_id(linkMatrix, i, 0);
103 const int e = wf_id(linkMatrix, i, 1);
104 if (s == current && e != endNode) {
105 reachable.insert(e);
106 queue.push_back(e);
107 }
108 }
109 }
110 return reachable;
111}
112
113/** Nodes that reach targetNode without passing through startNode. */
114template <class T>
115std::set<int> wf_can_reach(const Matrix<T>& linkMatrix, int targetNode, int startNode) {
116 const std::map<int, std::vector<int>> radj = wf_reverse_adjacency(linkMatrix);
117 std::set<int> canReach;
118 std::set<int> visited;
119 std::deque<int> queue;
120 queue.push_back(targetNode);
121 while (!queue.empty()) {
122 const int current = queue.front();
123 queue.pop_front();
124 if (visited.count(current) || current == startNode) continue;
125 visited.insert(current);
126 std::map<int, std::vector<int>>::const_iterator it = radj.find(current);
127 if (it == radj.end()) continue;
128 for (std::size_t k = 0; k < it->second.size(); ++k) {
129 const int pred = it->second[k];
130 if (pred != startNode) {
131 canReach.insert(pred);
132 queue.push_back(pred);
133 }
134 }
135 }
136 return canReach;
137}
138
139} // namespace detail
140
141/**
142 * @param linkMatrix (nedges x 3) edge list
143 * @param serviceNodes ids of the service nodes
144 * @param forkNodes ids of the fork nodes
145 * @param joinNodes ids of the join nodes
146 * @return one list of parallel service nodes per detected fork-join pair
147 */
148template <class T>
149std::vector<std::vector<int>> detect_parallel(const Matrix<T>& linkMatrix,
150 const std::vector<int>& serviceNodes,
151 const std::vector<int>& forkNodes,
152 const std::vector<int>& joinNodes) {
153 detail::wf_check(linkMatrix);
154 const std::set<int> forkSet(forkNodes.begin(), forkNodes.end());
155 const std::set<int> joinSet(joinNodes.begin(), joinNodes.end());
156 const std::set<int> serviceSet(serviceNodes.begin(), serviceNodes.end());
157 const std::map<int, std::vector<int>> adj = detail::wf_adjacency(linkMatrix);
158
159 std::vector<std::vector<int>> patterns;
160 for (std::size_t a = 0; a < forkNodes.size(); ++a) {
161 for (std::size_t b = 0; b < joinNodes.size(); ++b) {
162 const int fork = forkNodes[a];
163 const int join = joinNodes[b];
164 if (!detail::wf_valid_fork_join_pair(fork, join, adj, forkSet, joinSet)) continue;
165
166 const std::set<int> fromFork = detail::wf_reachable(linkMatrix, fork, join);
167 const std::set<int> toJoin = detail::wf_can_reach(linkMatrix, join, fork);
168 std::vector<int> parallelServices;
169 for (std::set<int>::const_iterator it = fromFork.begin(); it != fromFork.end(); ++it)
170 if (toJoin.count(*it) && serviceSet.count(*it)) parallelServices.push_back(*it);
171 if (parallelServices.size() > 1) patterns.push_back(parallelServices);
172 }
173 }
174 return patterns;
175}
176
177/**
178 * A pattern is valid when its nodes have exactly one common fork predecessor
179 * and exactly one common join successor.
180 */
181template <class T>
182bool validate_parallel_pattern(const std::vector<int>& pattern, const Matrix<T>& linkMatrix,
183 const std::vector<int>& forkNodes,
184 const std::vector<int>& joinNodes) {
185 detail::wf_check(linkMatrix);
186 if (pattern.size() < 2) return false;
187 const std::set<int> forkSet(forkNodes.begin(), forkNodes.end());
188 const std::set<int> joinSet(joinNodes.begin(), joinNodes.end());
189
190 std::set<int> sources, targets;
191 for (std::size_t p = 0; p < pattern.size(); ++p) {
192 for (std::size_t i = 0; i < linkMatrix.rows(); ++i) {
193 const int s = detail::wf_id(linkMatrix, i, 0);
194 const int e = detail::wf_id(linkMatrix, i, 1);
195 if (e == pattern[p] && forkSet.count(s)) sources.insert(s);
196 if (s == pattern[p] && joinSet.count(e)) targets.insert(e);
197 }
198 }
199 return sources.size() == 1 && targets.size() == 1;
200}
201
202/** Count, total, mean and maximum degree of parallelism. */
203template <class T>
204ParallelStats<T> get_parallel_stats(const std::vector<std::vector<int>>& patterns) {
205 ParallelStats<T> stats;
206 stats.numPatterns = patterns.size();
207 std::size_t total = 0;
208 std::size_t mx = 0;
209 for (std::size_t i = 0; i < patterns.size(); ++i) {
210 total += patterns[i].size();
211 if (patterns[i].size() > mx) mx = patterns[i].size();
212 }
213 stats.totalParallelNodes = total;
214 stats.maxParallelism = mx;
215 if (!patterns.empty())
216 stats.avgParallelism = num_traits<T>::from_int(static_cast<long>(total)) /
217 num_traits<T>::from_int(static_cast<long>(patterns.size()));
218 return stats;
219}
220
221} // namespace wf
222} // namespace line
223
224#endif // LINE_API_WF_WF_PARALLEL_DETECTOR_H
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
bool validate_parallel_pattern(const std::vector< int > &pattern, const Matrix< T > &linkMatrix, const std::vector< int > &forkNodes, const std::vector< int > &joinNodes)
A pattern is valid when its nodes have exactly one common fork predecessor and exactly one common joi...
ParallelStats< T > get_parallel_stats(const std::vector< std::vector< int > > &patterns)
Count, total, mean and maximum degree of parallelism.
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.
Mirrors the Java getParallelStats map.