LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
wf_analyzer.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_ANALYZER_H
6#define LINE_API_WF_WF_ANALYZER_H
7
8/**
9 * @file
10 * @ingroup api_wf
11 * The workflow analyzer: detect every pattern, collapse them, report the two
12 * complexities and the recommendations that follow.
13 *
14 * Templated port of the native Python `line_solver/api/wf/analyzer.py`,
15 * cross-checked against jar/src/main/java/jline/api/wf/Wf_analyzer.java. There
16 * is no MATLAB counterpart; `api/wf` exists in the JAR and in Python only, and
17 * Python is the reference for the same reason it is in `wf_pattern_updater.h`
18 * (the JAR's convolutions are stubs, so its "optimized" workflow carries the
19 * first branch's service law).
20 *
21 * THE NETWORK CONVERSION IS THE ONE PART THAT CANNOT BE COPIED. Both references
22 * walk their own object model -- Python asks `type(node).__name__` and reads
23 * `getLinkedRoutingMatrix`, the JAR walks `jline.lang.Network` -- and neither
24 * shape exists here. `wf_from_struct` does the same job from a `NetworkStruct`:
25 * the link matrix is the class-aggregated `rtnodes` above the zero tolerance,
26 * and the node classification follows `NodeType`, Queue and Delay being service,
27 * Fork and Join their own kinds, Router and ClassSwitch control. The service
28 * laws come from `sn.service` rather than from Python's placeholder unit
29 * exponential, which is strictly more information and changes no structure.
30 *
31 * `analyze_workflow` on a representation the caller built by hand is the entry
32 * both references really exercise, and it is byte-for-byte their algorithm.
33 *
34 * ARITHMETIC: field, plus whatever the branch entropy needs -- the diversity
35 * report is gated inside wf_branch_detector.h, not here.
36 */
37
38#include <cstddef>
39#include <map>
40#include <set>
41#include <string>
42#include <utility>
43#include <vector>
44
54#include "line/num/number.h"
55#include "line/util/error.h"
56#include "line/util/matrix.h"
57
58namespace line {
59namespace wf {
60
61/** The workflow in matrix form: the reference's WorkflowRepresentation. */
62template <class T>
66 std::map<int, ServiceParameters<T>> serviceParameters;
67};
68
69/** Everything the four detectors found. */
70template <class T>
72 std::vector<std::vector<int>> sequences;
73 std::vector<std::vector<int>> parallels;
74 std::vector<int> loops;
75 std::vector<BranchPattern<T>> branches;
76};
77
78/** The reference's complexity map, for either the original or the collapsed graph. */
79template <class T>
81 std::size_t totalNodes = 0;
82 std::size_t totalLinks = 0;
83 std::size_t serviceNodes = 0;
84 std::size_t controlNodes = 0;
85 std::size_t connectedNodes = 0;
87};
88
89/** The statistics block of WorkflowAnalysis. */
90template <class T>
100
101/** What analyze_workflow returns. */
102template <class T>
109
110/** Run the four detectors on one representation. */
111template <class T>
120
121namespace detail {
122
123/** Distinct node ids the link matrix mentions, and the total degree of each. */
124template <class T>
125void wf_degrees(const Matrix<T>& m, std::set<int>* nodes, std::map<int, std::size_t>* deg) {
126 for (std::size_t i = 0; i < m.rows(); ++i) {
127 const int a = wf_id(m, i, 0), b = wf_id(m, i, 1);
128 nodes->insert(a);
129 nodes->insert(b);
130 if (deg != 0) {
131 (*deg)[a] += 1;
132 (*deg)[b] += 1;
133 }
134 }
135}
136
137} // namespace detail
138
139/** Complexity of the workflow as declared. */
140template <class T>
143 c.serviceNodes = w.serviceNodes.size();
144 c.controlNodes = w.forkNodes.size() + w.joinNodes.size() + w.routerNodes.size();
146 c.totalLinks = w.linkMatrix.rows();
147
148 std::set<int> nodes;
149 std::map<int, std::size_t> deg;
150 detail::wf_degrees(w.linkMatrix, &nodes, &deg);
151 c.connectedNodes = nodes.size();
152 if (!deg.empty()) {
154 for (std::map<int, std::size_t>::const_iterator it = deg.begin(); it != deg.end(); ++it)
155 s += num_traits<T>::from_int(static_cast<long>(it->second));
156 c.avgDegree = T(s / num_traits<T>::from_int(static_cast<long>(deg.size())));
157 }
158 return c;
159}
160
161/**
162 * Complexity of the collapsed workflow.
163 *
164 * The reference reports only three of the six fields here, so the rest stay at
165 * their defaults rather than being invented: after the collapse there is no
166 * service/control split left to report, the node kinds having been merged.
167 */
168template <class T>
171 c.totalNodes = w.serviceParameters.size();
172 c.totalLinks = w.linkMatrix.rows();
173 std::set<int> nodes;
174 detail::wf_degrees(w.linkMatrix, &nodes, static_cast<std::map<int, std::size_t>*>(0));
175 c.connectedNodes = nodes.size();
176 return c;
177}
178
179/** Detect, collapse, and report. */
180template <class T>
197
198/**
199 * The reference's recommendation strings, in its order.
200 *
201 * They are user-facing text, so they are reproduced verbatim rather than
202 * paraphrased: a caller that greps them would otherwise see different output
203 * from the same analysis in two codebases.
204 */
205template <class T>
206std::vector<std::string> get_optimization_recommendations(const WorkflowAnalysis<T>& a) {
207 std::vector<std::string> out;
209 if (!p.sequences.empty())
210 out.push_back("Found " + std::to_string(p.sequences.size()) +
211 " sequence patterns that can be simplified");
212 if (!p.parallels.empty())
213 out.push_back("Found " + std::to_string(p.parallels.size()) +
214 " parallel patterns for potential optimization");
215 if (!p.loops.empty())
216 out.push_back("Found " + std::to_string(p.loops.size()) +
217 " loop patterns - consider loop unrolling for performance");
218 if (!p.branches.empty()) {
219 out.push_back("Found " + std::to_string(p.branches.size()) +
220 " branch patterns - analyze probability distributions");
221 std::size_t high = 0;
222 for (std::size_t i = 0; i < p.branches.size(); ++i)
224 ++high;
225 if (high > 0)
226 out.push_back(std::to_string(high) +
227 " branches have high entropy - consider load balancing");
228 }
229 const double ratio = num_traits<T>::to_double(a.statistics.updateStats.reductionRatio);
230 if (ratio > 0.1)
231 out.push_back("Workflow complexity reduced by " +
232 std::to_string(static_cast<int>(ratio * 100)) +
233 "% through pattern optimization");
234 return out;
235}
236
237/** The collapsed workflow is consistent and every detected pattern validates. */
238template <class T>
240 if (!validate_updated_workflow(a.optimizedWorkflow)) return false;
241 const Matrix<T>& L = a.originalWorkflow.linkMatrix;
242 for (std::size_t i = 0; i < a.detectedPatterns.sequences.size(); ++i)
243 if (!validate_sequence(a.detectedPatterns.sequences[i], L)) return false;
244 for (std::size_t i = 0; i < a.detectedPatterns.parallels.size(); ++i)
245 if (!validate_parallel_pattern(a.detectedPatterns.parallels[i], L,
246 a.originalWorkflow.forkNodes,
247 a.originalWorkflow.joinNodes))
248 return false;
249 for (std::size_t i = 0; i < a.detectedPatterns.loops.size(); ++i)
250 if (!validate_loop_pattern(a.detectedPatterns.loops[i], L, a.originalWorkflow.routerNodes))
251 return false;
252 for (std::size_t i = 0; i < a.detectedPatterns.branches.size(); ++i)
253 if (!validate_branch_pattern(a.detectedPatterns.branches[i], L)) return false;
254 return true;
255}
256
257/**
258 * Build a workflow representation from a NetworkStruct.
259 *
260 * The link matrix is the class-aggregated node routing `sn.rtnodes`, one row per
261 * (source, target) pair carrying positive probability, with the probability
262 * summed over the class pairs and normalized per source. Node ids are 0-based,
263 * matching the reference's node indices.
264 */
265template <class T>
267 const T zero = num_traits<T>::from_int(0);
268 const double tol = lang::GlobalConstants::Zero;
269 const std::size_t I = sn.nodes.size(), K = sn.nclasses;
270
272 for (std::size_t ind = 0; ind < I; ++ind) {
273 switch (sn.nodes[ind].nodetype) {
274 case qn::NodeType::Queue:
275 case qn::NodeType::Delay:
276 w.serviceNodes.push_back(static_cast<int>(ind));
277 break;
278 case qn::NodeType::Fork:
279 w.forkNodes.push_back(static_cast<int>(ind));
280 break;
281 case qn::NodeType::Join:
282 w.joinNodes.push_back(static_cast<int>(ind));
283 break;
284 case qn::NodeType::Router:
285 case qn::NodeType::ClassSwitch:
286 w.routerNodes.push_back(static_cast<int>(ind));
287 break;
288 default:
289 break;
290 }
291 }
292
293 std::vector<std::vector<T>> agg(I, std::vector<T>(I, zero));
294 if (sn.rtnodes.rows() == I * K && sn.rtnodes.cols() == I * K) {
295 for (std::size_t i = 0; i < I; ++i)
296 for (std::size_t j = 0; j < I; ++j)
297 for (std::size_t r = 0; r < K; ++r)
298 for (std::size_t s = 0; s < K; ++s)
299 agg[i][j] += sn.rtnodes(i * K + r, j * K + s);
300 }
301 std::vector<std::pair<std::pair<int, int>, T>> links;
302 for (std::size_t i = 0; i < I; ++i) {
303 T rowsum = zero;
304 for (std::size_t j = 0; j < I; ++j) rowsum += agg[i][j];
305 if (!(num_traits<T>::to_double(rowsum) > tol)) continue;
306 for (std::size_t j = 0; j < I; ++j) {
307 if (!(num_traits<T>::to_double(agg[i][j]) > tol)) continue;
308 links.push_back(std::make_pair(std::make_pair(static_cast<int>(i), static_cast<int>(j)),
309 T(agg[i][j] / rowsum)));
310 }
311 }
312 w.linkMatrix = Matrix<T>(links.size(), 3, zero);
313 for (std::size_t k = 0; k < links.size(); ++k) {
314 w.linkMatrix(k, 0) = num_traits<T>::from_int(links[k].first.first);
315 w.linkMatrix(k, 1) = num_traits<T>::from_int(links[k].first.second);
316 w.linkMatrix(k, 2) = links[k].second;
317 }
318
319 // Service laws from sn.service, one per service node, taking the first
320 // enabled class. The references install a placeholder unit exponential
321 // here; reading the declared law instead is strictly more information and
322 // leaves the structure untouched.
323 for (std::size_t k = 0; k < w.serviceNodes.size(); ++k) {
324 const std::size_t ind = static_cast<std::size_t>(w.serviceNodes[k]);
325 const std::size_t ist = sn.nodes[ind].station;
327 p.alpha.assign(1, num_traits<T>::from_int(1));
328 p.T_ = Matrix<T>(1, 1, num_traits<T>::from_int(-1));
329 if (ist >= 1 && ist <= sn.nstations) {
330 for (std::size_t r = 0; r < K; ++r) {
331 if (sn.disabled[ist - 1][r] || sn.service[ist - 1][r].disabled) continue;
332 const mam::Map<T> m = lang::dist_to_map(sn.service[ist - 1][r]);
333 p.alpha = mam::map_pie(m);
334 p.T_ = m.D0;
335 break;
336 }
337 }
339 }
340 return w;
341}
342
343} // namespace wf
344} // namespace line
345
346#endif // LINE_API_WF_WF_ANALYZER_H
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
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.
mam::Map< T > dist_to_map(const Distrib< T > &d)
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
WorkflowAnalysis< T > analyze_workflow(const WorkflowRepresentation< T > &w)
Detect, collapse, and report.
bool validate_analysis(const WorkflowAnalysis< T > &a)
The collapsed workflow is consistent and every detected pattern validates.
std::vector< std::string > get_optimization_recommendations(const WorkflowAnalysis< T > &a)
The reference's recommendation strings, in its order.
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 >())
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...
bool validate_sequence(const std::vector< int > &sequence, const Matrix< T > &linkMatrix)
Every consecutive pair of the chain must be an edge of the workflow.
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,...
WorkflowComplexity< T > workflow_complexity(const WorkflowRepresentation< T > &w)
Complexity of the workflow as declared.
BranchStats< T > get_branch_stats(const std::vector< BranchPattern< T > > &patterns)
Count, total, mean/max/min alternatives, and the mean entropy and balance.
BranchDiversity< T > calculate_branch_diversity(const BranchPattern< T > &pattern)
Shannon entropy of the branch probabilities, the same entropy normalized by log(n),...
std::vector< std::vector< int > > detect_sequences(const Matrix< T > &linkMatrix, const std::vector< int > &serviceNodes)
SequenceStats< T > get_sequence_stats(const std::vector< std::vector< int > > &sequences)
Count, total, mean, maximum and minimum chain length.
bool validate_loop_pattern(int loopNode, const Matrix< T > &linkMatrix, const std::vector< int > &routerNodes)
True when the node still has the service -> router -> service structure.
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.
UpdateStats< T > get_update_stats(const Matrix< T > &originalMatrix, const UpdatedWorkflow< T > &w)
How much the collapse shrank the link matrix.
bool validate_branch_pattern(const BranchPattern< T > &pattern, const Matrix< T > &linkMatrix)
Probabilities sum to one within 1e-2 and every alternative is a fork successor.
WorkflowComplexity< T > optimized_complexity(const UpdatedWorkflow< T > &w)
Complexity of the collapsed workflow.
DetectedPatterns< T > detect_all_patterns(const WorkflowRepresentation< T > &w)
Run the four detectors on one representation.
WorkflowRepresentation< T > wf_from_struct(const qn::NetworkStruct< T > &sn)
Build a workflow representation from a NetworkStruct.
ParallelStats< T > get_parallel_stats(const std::vector< std::vector< int > > &patterns)
Count, total, mean and maximum degree of parallelism.
LoopStats< T > get_loop_stats(const std::vector< int > &loopNodes, const Matrix< T > &linkMatrix, const std::vector< int > &routerNodes)
Count and moments of the loop probabilities and iteration counts.
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)
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
static constexpr double Zero
Definition lang_types.h:670
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D0
Definition map_moment.h:54
Mirrors the Java getBranchStats map.
Everything the four detectors found.
Definition wf_analyzer.h:71
std::vector< int > loops
Definition wf_analyzer.h:74
std::vector< std::vector< int > > sequences
Definition wf_analyzer.h:72
std::vector< BranchPattern< T > > branches
Definition wf_analyzer.h:75
std::vector< std::vector< int > > parallels
Definition wf_analyzer.h:73
Mirrors the Java getLoopStats map.
Mirrors the Java getParallelStats map.
Mirrors the Java getSequenceStats map.
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
What analyze_workflow returns.
WorkflowStatistics< T > statistics
DetectedPatterns< T > detectedPatterns
WorkflowRepresentation< T > originalWorkflow
UpdatedWorkflow< T > optimizedWorkflow
The reference's complexity map, for either the original or the collapsed graph.
Definition wf_analyzer.h:80
The workflow in matrix form: the reference's WorkflowRepresentation.
Definition wf_analyzer.h:63
std::map< int, ServiceParameters< T > > serviceParameters
Definition wf_analyzer.h:66
std::vector< int > serviceNodes
Definition wf_analyzer.h:65
std::vector< int > routerNodes
Definition wf_analyzer.h:65
The statistics block of WorkflowAnalysis.
Definition wf_analyzer.h:91
SequenceStats< T > sequenceStats
Definition wf_analyzer.h:92
WorkflowComplexity< T > optimizedComplexity
Definition wf_analyzer.h:98
WorkflowComplexity< T > originalComplexity
Definition wf_analyzer.h:97
BranchStats< T > branchStats
Definition wf_analyzer.h:95
UpdateStats< T > updateStats
Definition wf_analyzer.h:96
ParallelStats< T > parallelStats
Definition wf_analyzer.h:93
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.
Collapse the detected workflow patterns and convolve their service laws.
Sequence pattern detection in a workflow network.