LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_tag_transform.h
Go to the documentation of this file.
1// Copyright (c) 2012-2026, QORE Lab, Imperial College London
2// All rights reserved.
3#ifndef LINE_SOLVERS_TR_FJ_TAG_TRANSFORM_H
4#define LINE_SOLVERS_TR_FJ_TAG_TRANSFORM_H
5
6/**
7 * @file
8 * @ingroup line_solvers
9 * Fork-join TAG AUGMENTATION: the fold-back half of the transform/lift pair
10 * that CTMC and SSA share.
11 *
12 * `qn::fj_tag` is the OTHER fork-join route. Where `mmt` and `ht` drive an
13 * outer fixed point for MVA, NC and Fluid (`solvers/mva/fj_driver.h`), the tag
14 * augmentation is EXACT and single pass: it rewrites the struct so each sibling
15 * branch carries its own auxiliary class, the engine runs unchanged on that
16 * struct, and the auxiliary columns are folded back at the end.
17 *
18 * This is deliberately NOT built on the `fj_driver.h` shape. That driver owns a
19 * loop and takes the inner solve as a template parameter because MMT re-solves
20 * a transformed model repeatedly. `fj_tag` substitutes the struct and then the
21 * caller's own engine runs on it to completion: there is no callback seam and
22 * no second pass, so the reusable unit is the fold-back, not a driver.
23 *
24 * THE ONE C++-SPECIFIC POINT. CTMC and SSA carry DIFFERENT result containers
25 * (`ctmc::CtmcAvg<T>` over `Matrix<T>`, `ssa::SsaSolution` over
26 * `Matrix<double>`), which is why this is templated on the container rather
27 * than taking one. It is the same obstacle python meets, where SolverMVA keeps
28 * a dict and SolverNC a dataclass.
29 *
30 * Mirrors MATLAB `matlab/src/solvers/TR/solver_tr_fjtag_analyzer.m`, python
31 * `line_solver/solvers/fjtag_transform.py` and the JAR
32 * `jline.solvers.tr.FJTagTransform`.
33 */
34
35#include <cstddef>
36#include <type_traits>
37#include <vector>
38
40#include "line/num/number.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace tr {
46
47/**
48 * Whether the model needs the tag augmentation at all.
49 *
50 * A Fork or a Join sends the model down the `qn::fj_tag` route; neither means
51 * it stays on the ordinary one. Four analyzers carried their own copy of this
52 * loop (CTMC steady-state and transient, SSA serial, the chain tables), which
53 * is the C++ share of the bookkeeping that MATLAB, the JAR and python absorbed
54 * into an `expand` phase.
55 *
56 * There is deliberately NO `expand` here to match those three. `qn::fj_tag`
57 * ALREADY returns the context they had to build by hand: `FjTagged` carries the
58 * augmented struct, the sync list, `fjclassmap` and `korig` together, so
59 * wrapping it would add a layer without removing a duplication.
60 */
61template <class T>
63 if (sn.has_fork()) return true;
64 for (std::size_t i = 0; i < sn.nodes.size(); ++i)
65 if (sn.nodes[i].nodetype == lang::NodeType::Join) return true;
66 return false;
67}
68
69/**
70 * Reduce the augmented metrics onto the original classes.
71 *
72 * A sibling class is a PART of the class it was forked from, so its queue
73 * length, utilization and throughput are exact aggregates and simply add.
74 * Response time is NOT additive and is recomputed by Little's law afterwards.
75 * The SYSTEM metrics are truncated rather than summed: `XN` is the departure
76 * rate at the parent class's reference station, which no sibling visits, and
77 * adding a branch's throughput to it would count each forked task once per
78 * branch.
79 *
80 * @tparam T the struct's numeric type
81 * @tparam Avg the caller's result container, with QN, UN, TN, RN, CN, XN
82 */
83template <class T, class Avg>
85 const std::vector<std::size_t>& fjclassmap, std::size_t korig) {
86 typedef typename std::remove_cv<
87 typename std::remove_reference<decltype(a.QN(0, 0))>::type>::type S;
88 const S zero = num_traits<S>::from_int(0);
89 const std::size_t M = a.QN.rows();
90 for (std::size_t x = 0; x < fjclassmap.size(); ++x) {
91 const std::size_t r = fjclassmap[x];
92 if (r == 0) continue;
93 for (std::size_t i = 0; i < M; ++i) {
94 a.QN(i, r - 1) += a.QN(i, x);
95 a.UN(i, r - 1) += a.UN(i, x);
96 a.TN(i, r - 1) += a.TN(i, x);
97 }
98 }
99 Matrix<S> Q(M, korig, zero), U(M, korig, zero), TT(M, korig, zero), Rr(M, korig, zero);
100 for (std::size_t i = 0; i < M; ++i)
101 for (std::size_t k = 0; k < korig; ++k) {
102 Q(i, k) = a.QN(i, k);
103 U(i, k) = a.UN(i, k);
104 TT(i, k) = a.TN(i, k);
105 Rr(i, k) = num_traits<S>::to_double(TT(i, k)) > 0 ? S(Q(i, k) / TT(i, k)) : zero;
106 }
107 a.QN = Q;
108 a.UN = U;
109 a.TN = TT;
110 a.RN = Rr;
111 // A Join's response time is QLen over the SIBLING arrival rate, not over
112 // its own firing rate: a Join sees one arrival per sibling for every job it
113 // releases. Applied here, on the FOLDED table, so both engines and every
114 // in-process caller read the same convention.
116 a.CN.resize(korig);
117 a.XN.resize(korig);
118}
119
120} // namespace tr
121} // namespace line
122
123#endif // LINE_SOLVERS_TR_FJ_TAG_TRANSFORM_H
A network plus its refreshed NetworkStruct.
Dense matrix and non-owning view.
void sn_apply_join_respt(const qn::NetworkStruct< T > &L, const Matrix< T > &QN, const Matrix< T > &AN, Matrix< T > &RN)
Port of sn_get_arvr_from_tput: the arrival rate each station sees, from the throughputs and the class...
Matrix< T > sn_get_arvr_from_tput(const qn::NetworkStruct< T > &L, const Matrix< T > &TN)
void fj_foldback(const qn::NetworkStruct< T > &sn, Avg &a, const std::vector< std::size_t > &fjclassmap, std::size_t korig)
Reduce the augmented metrics onto the original classes.
bool has_fork_join(const qn::NetworkStruct< T > &sn)
Whether the model needs the tag augmentation at all.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.