LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_aggregate_chains.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_SN_SN_AGGREGATE_CHAINS_H
6#define LINE_API_SN_SN_AGGREGATE_CHAINS_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Collapse every chain onto one class, port of `ModelAdapter.aggregateChains`.
12 *
13 * The reference is `matlab/src/io/@@ModelAdapter/aggregateChains.m`, reached
14 * from the model as `model.aggregateChains(suffix)`; the JAR twin is
15 * `ModelAdapter.aggregateChains` and the python one
16 * `ModelAdapter.aggregate_chains`.
17 *
18 * A CHAIN is a set of classes that can switch into one another, so the jobs of
19 * a chain are one circulating population however many class labels they wear on
20 * the way round. The transform replaces each chain by a SINGLE class carrying
21 * that population (or that total arrival rate, for an open chain), the chain's
22 * service demands and the chain-level routing. The result has no class
23 * switching at all, which is what makes it worth building: the state space of a
24 * multiclass model is the product over classes, and a model whose K classes
25 * form C < K chains solves in the state space of C.
26 *
27 * WHAT IS PRESERVED, and it is chain-level and not class-level. Per chain: the
28 * population, the arrival rate, the per-station service DEMAND, and the routing
29 * between stations. Per class: nothing -- that is the point, and it is what the
30 * `alpha` matrix returned here exists to undo. `alpha(i,r)` is class r's share
31 * of its chain's visits at station i, so a chain result at station i splits
32 * back over its classes in those proportions (see the reference's
33 * `sn_deaggregate_chain_results`, which consumes exactly `Lchain`, `STchain`,
34 * `Vchain` and `alpha` from the block returned here).
35 *
36 * THE AGGREGATE IS EXACT ON A PRODUCT-FORM MODEL and an approximation
37 * otherwise, for one reason: the chain's service law at a station is refitted
38 * from the chain MEAN and SCV, mixing the classes' own laws. Where the model is
39 * product form the station is insensitive to everything past the mean and the
40 * refit costs nothing; where it is not, the refit is a two-moment
41 * approximation. The fit follows the reference's ladder exactly --
42 *
43 * |SCV - 1| < FineTol Exp(1/ST) the insensitive case
44 * SCV < FineTol Det(ST) the degenerate lower end
45 * SCV < 1 Erlang(round(1/SCV)) the reference rounds, not ceils
46 * SCV > 1 HyperExp fitted to (ST, SCV)
47 *
48 * -- and the Erlang order is `round(1/SCV)` here because that is what
49 * `aggregateChains.m` writes; `Erlang.fitMeanAndSCV` ceils instead, and the two
50 * disagree at every SCV that is not the reciprocal of an integer. The first
51 * three rungs are exact-arithmetic clean; the HyperExp one needs a square root,
52 * so at a non-transcendental T it is FITTED IN DOUBLE and its three parameters
53 * lifted back, which costs nothing that was exact to begin with -- the rung is a
54 * two-moment approximation of a mixture and carries no exactness claim, while the
55 * routing, the demands, the populations and the aggregation around it stay exact.
56 *
57 * THE ROUTING IS READ OFF `rt`, NOT `rtnodes`, and that is load bearing. `rt`
58 * is the stochastic complement of the routing over the STATEFUL nodes, so the
59 * nodes the aggregate does not carry -- Routers, and the ClassSwitch nodes that
60 * `link` synthesizes for exactly the switching this transform is eliminating --
61 * are already folded away, along with the Sink, which is not stateful in this
62 * port nor in the reference. That last one is why an open chain still routes:
63 * a `Q -> Sink -> Source` path in the original appears in `rt` as `Q -> Source`
64 * (the sink closure is inside the complement), so the aggregate's open chain
65 * returns to the Source and the refresh re-derives its own closure. Measured on
66 * a two-class open chain in MATLAB 2026-08-15: the aggregate routes
67 * Source -> Q1 -> Q2 -> Source and reports the exact per-station utilizations.
68 *
69 * ONE DELIBERATE DEPARTURE FROM MATLAB. The reference `line_warning`s and SKIPS
70 * a node kind it does not carry (anything that is not Source, Sink, Queue,
71 * Delay, Router or ClassSwitch), which silently returns a model with a node
72 * missing and its routing rewired around the hole. This port refuses by name
73 * instead, as the rest of the port does: a model the transform cannot carry is
74 * a diagnostic, not a quietly different model. A ClassSwitch is still dropped
75 * rather than refused, since eliminating class switching is the transform's
76 * whole purpose and the reference drops it on both of its branches.
77 *
78 * ARITHMETIC: rational in the routing and the visits; the HyperExp fit is
79 * transcendental (`hyperexp_fit_mean_scv`) and drops to a double fit at an exact
80 * T, as `network_reader.h` already does for every T.
81 */
82
83#include <cmath>
84#include <cstddef>
85#include <string>
86#include <vector>
87
92#include "line/num/number.h"
94#include "line/util/error.h"
95#include "line/util/matrix.h"
96
97namespace line {
98namespace api {
99
100/**
101 * Everything needed to map a chain-level result back onto the classes.
102 *
103 * The reference's `deaggInfo`, minus its `originalSn` field: a struct is a
104 * value here and the caller already holds the one it passed in, so carrying a
105 * second copy would double the memory for nothing. Nothing in
106 * `sn_deaggregate_chain_results` reads it either -- the documented call is
107 * `sn_deaggregate_chain_results(sn, Lchain, [], STchain, Vchain, alpha, ...)`.
108 */
109template <class T>
111 Matrix<T> alpha; ///< (M x K) class share of its chain's visits at a station
112 Matrix<T> Lchain; ///< (M x C) chain demand
113 Matrix<T> STchain; ///< (M x C) chain mean service time
114 Matrix<T> Vchain; ///< (M x C) chain visits
115 Matrix<T> SCVchain; ///< (M x C)
116 std::vector<double> Nchain; ///< (C) population, infinite for an open chain
117 std::vector<T> lambdachain; ///< (C) aggregate arrival rate, 0 for a closed chain
118 std::vector<bool> isopenchain; ///< (C)
119 std::vector<std::vector<std::size_t> > inchain; ///< (C) 1-based class indices
120 std::vector<std::size_t> refstat; ///< (K) 1-based reference station
121 std::vector<std::size_t> refstatchain; ///< (C) 1-based reference station
122 bool isaggregated = false; ///< false when C == K and the model was merely copied
123 std::size_t nclasses = 0; ///< K of the ORIGINAL model
124 std::size_t nchains = 0; ///< C, the class count of the aggregate
125};
126
127/** What `sn_aggregate_chains` returns. */
128template <class T>
130 explicit ChainAggregationResult(const qn::Network<T>& m) : model(m) {}
131 qn::Network<T> model; ///< the aggregate, one class per chain
132 Matrix<T> alpha; ///< (M x K), the same matrix as `deagg.alpha`
133 /** `chainclass[c]` is the 1-based class index of chain c+1 in `model`. */
134 std::vector<std::size_t> chainclass;
135 /** `stationnode[i]` is the aggregate's 1-based node for original station i+1, 0 if dropped. */
136 std::vector<std::size_t> stationnode;
138};
139
140/**
141 * @brief Collapse every chain onto one class, port of
142 * `ModelAdapter.aggregateChains`.
143 *
144 * @param sn a REFRESHED model (the chains, `rt` and the rates are read)
145 * @param suffix appended to each chain class name, as the reference's argument
146 */
147template <class T>
149 const std::string& suffix = std::string()) {
150 const T zero = num_traits<T>::from_int(0);
151 const T one = num_traits<T>::from_int(1);
152 const double fineTol = lang::GlobalConstants::FineTol;
153 const std::size_t M = sn.nstations, K = sn.nclasses, C = sn.nchains;
154
155 if (M == 0 || K == 0)
156 throw InputError("sn_aggregate_chains: the model has no stations or no classes");
157 if (C == 0 || sn.inchain.size() != C)
158 throw InputError(
159 "sn_aggregate_chains: the model is not refreshed (it declares no chains); call "
160 "refresh_struct() or get_struct() first");
161
162 // ---- C == K: nothing to merge ------------------------------------------
163 //
164 // The reference returns a COPY rather than running the general path, and
165 // that is not an optimisation: the general path refits every service law
166 // from its first two moments, so running it here would replace each
167 // station's own distribution by a two-moment surrogate of itself and change
168 // every non-product-form answer for no gain.
169 if (C == K) {
170 qn::Network<T> copy(sn.name);
171 copy.raw_struct() = sn;
173 // MATLAB returns eye(M,K) here, which is what `alpha` degenerates to
174 // only when the station and class axes happen to line up; it is carried
175 // verbatim so a caller comparing against the reference sees the same
176 // matrix, and `isaggregated` is what says not to trust it.
177 out.alpha = Matrix<T>(M, K, zero);
178 for (std::size_t i = 0; i < M && i < K; ++i) out.alpha(i, i) = one;
179 out.deagg.alpha = out.alpha;
180 out.deagg.inchain = sn.inchain;
181 out.deagg.isaggregated = false;
182 out.deagg.nclasses = K;
183 out.deagg.nchains = C;
184 out.chainclass.resize(C, 0);
185 for (std::size_t c = 0; c < C; ++c) out.chainclass[c] = c + 1;
186 out.stationnode.resize(M, 0);
187 for (std::size_t i = 0; i < M; ++i) out.stationnode[i] = sn.station_to_node[i];
188 return out;
189 }
190
192
193 // ---- which chains are open, and at what aggregate rate ------------------
194 std::vector<bool> isOpenChain(C, false);
195 std::vector<T> lambdaChain(C, zero);
196 for (std::size_t c = 0; c < C; ++c) {
197 const std::vector<std::size_t>& ic = sn.inchain[c];
198 for (std::size_t a = 0; a < ic.size(); ++a)
199 if (!std::isfinite(sn.classes[ic[a] - 1].population)) isOpenChain[c] = true;
200 if (!isOpenChain[c] || sn.sourceIdx == 0) continue;
201 // The reference sums `sn.rates(sourceStation, inchain)` with 'omitnan';
202 // a disabled arrival is the NaN it is omitting.
203 for (std::size_t a = 0; a < ic.size(); ++a) {
204 const std::size_t rr = ic[a];
205 if (sn.disabled[sn.sourceIdx - 1][rr - 1]) continue;
206 const double v = num_traits<T>::to_double(sn.rates(sn.sourceIdx - 1, rr - 1));
207 if (std::isfinite(v)) lambdaChain[c] += sn.rates(sn.sourceIdx - 1, rr - 1);
208 }
209 }
210
211 // ---- the nodes the aggregate carries ------------------------------------
212 qn::Network<T> chainModel(sn.name.empty() ? std::string("aggregated")
213 : sn.name + "_aggregated");
214 std::vector<std::size_t> nodeMap(sn.nodes.size() + 1, 0); // old 1-based node -> new node
215 std::size_t sourceNode = 0;
216 for (std::size_t i = 1; i <= sn.nodes.size(); ++i) {
217 const qn::NodeDef& nd = sn.nodes[i - 1];
218 switch (nd.nodetype) {
219 case qn::NodeType::Source:
220 nodeMap[i] = chainModel.add_source(nd.name);
221 sourceNode = nodeMap[i];
222 break;
223 case qn::NodeType::Sink: nodeMap[i] = chainModel.add_sink(nd.name); break;
224 case qn::NodeType::Delay: nodeMap[i] = chainModel.add_delay(nd.name); break;
225 case qn::NodeType::Router: nodeMap[i] = chainModel.add_router(nd.name); break;
226 case qn::NodeType::Queue: {
227 const qn::Station<T>& st = sn.stations[nd.station - 1];
228 nodeMap[i] = chainModel.add_queue(nd.name, st.sched);
229 if (!std::isinf(st.nservers))
230 chainModel.set_number_of_servers(nodeMap[i], st.nservers);
231 if (st.cap > 0.0 && std::isfinite(st.cap))
232 chainModel.set_capacity(nodeMap[i], st.cap);
233 break;
234 }
235 case qn::NodeType::ClassSwitch:
236 // DROPPED, not refused: the aggregate has one class per chain
237 // and therefore nothing left to switch. This covers both the
238 // user's own ClassSwitch nodes and the ones `link` synthesizes,
239 // which is the reference's behaviour on both of its branches.
240 break;
241 default:
242 throw UnsupportedError(
243 std::string("sn_aggregate_chains: node '") + nd.name + "' is a " +
245 ", which the chain aggregate cannot carry; the transform keeps Source, Sink, "
246 "Queue, Delay and Router and drops ClassSwitch");
247 }
248 }
249
250 std::vector<std::size_t> stationNode(M + 1, 0); // old 1-based station -> new node
251 for (std::size_t i = 1; i <= M; ++i) stationNode[i] = nodeMap[sn.station_to_node[i - 1]];
252
253 // ---- one class per chain -------------------------------------------------
254 std::vector<std::size_t> chainClass(C, 0);
255 for (std::size_t c = 0; c < C; ++c) {
256 const std::vector<std::size_t>& ic = sn.inchain[c];
257 std::string nm = (ic.size() == 1) ? sn.classes[ic[0] - 1].name
258 : (std::string("Chain") + std::to_string(c + 1));
259 if (!suffix.empty()) nm += suffix;
260 if (isOpenChain[c]) {
261 chainClass[c] = chainModel.add_open_class(nm);
262 } else {
263 const std::size_t refst = dem.refstatchain[c];
264 if (refst == 0 || refst > M || stationNode[refst] == 0)
265 throw InputError("sn_aggregate_chains: the reference station of chain " +
266 std::to_string(c + 1) + " is not carried by the aggregate");
267 chainClass[c] = chainModel.add_closed_class(nm, dem.Nchain[c], stationNode[refst]);
268 }
269 }
270
271 // ---- the aggregate arrival rates ----------------------------------------
272 for (std::size_t c = 0; c < C; ++c) {
273 if (!isOpenChain[c]) continue;
274 if (sourceNode == 0)
275 throw InputError(
276 "sn_aggregate_chains: chain " + std::to_string(c + 1) +
277 " is open but the model carries no Source to attach its arrivals to");
278 if (num_traits<T>::to_double(lambdaChain[c]) > 0.0)
279 chainModel.set_arrival(sourceNode, chainClass[c],
280 lang::Distrib<T>::exp_rate(lambdaChain[c]));
281 }
282
283 // ---- the chain service laws ---------------------------------------------
284 for (std::size_t i = 1; i <= M; ++i) {
285 if (stationNode[i] == 0) continue;
286 const qn::NodeType ty = sn.nodes[sn.station_to_node[i - 1] - 1].nodetype;
287 if (ty == qn::NodeType::Source || ty == qn::NodeType::Sink) continue;
288 for (std::size_t c = 0; c < C; ++c) {
289 const double st = num_traits<T>::to_double(dem.STchain(i - 1, c));
290 if (!(st > 0.0) || !std::isfinite(st)) {
291 // No service for this chain here. Left at the table's own
292 // Disabled, which is what the reference sets explicitly.
293 chainModel.set_service(stationNode[i], chainClass[c],
295 continue;
296 }
297 double scv = num_traits<T>::to_double(dem.SCVchain(i - 1, c));
298 if (!std::isfinite(scv) || scv <= 0.0) scv = 1.0; // default to exponential
299 const T mean = dem.STchain(i - 1, c);
301 if (std::fabs(scv - 1.0) < fineTol) {
303 } else if (scv < 1.0) {
304 if (scv < fineTol) {
305 d = lang::Distrib<T>::det(mean);
306 } else {
307 // `Erlang.fitMeanAndOrder(mean, k)` is Erlang(k/mean, k);
308 // the reference ROUNDS 1/SCV here rather than ceiling it.
309 long k = std::lround(1.0 / scv);
310 if (k < 1) k = 1;
311 const T ph = T(num_traits<T>::from_int(k) / mean);
312 d = lang::Distrib<T>::erlang(ph, static_cast<std::size_t>(k));
313 }
314 } else {
315 // THE HYPEREXP RUNG IS FITTED IN DOUBLE AT EXACT ARITHMETIC, and it
316 // has to be selected at COMPILE time. `hyperexp_fit_mean_scv`
317 // static_asserts on transcendental arithmetic (the moment
318 // discriminant is a square root), and a template instantiates every
319 // branch of this ladder whatever the runtime SCV is -- so calling it
320 // unguarded made the whole of `sn_aggregate_chains`, and every caller
321 // of it up to `solver_ctmc_run_analyzer`, uninstantiable at exact arithmetic,
322 // including the Exp, Det and Erlang rungs that never reach here.
323 //
324 // Fitting in double and lifting the three parameters is what
325 // `network_reader.h::hyperexp_fit_mean_scv` already does for every T,
326 // and it costs nothing that was exact to begin with: this rung IS a
327 // two-moment approximation of a chain that mixes several classes'
328 // service laws, so its parameters carry no exactness claim. What
329 // stays exact is everything around it -- the routing, the demands,
330 // the populations and the aggregation itself. The alternative,
331 // refusing, would leave a chain with SCV > 1 with no exact-arithmetic
332 // path at all.
333 if constexpr (!num_traits<T>::has_transcendental) {
334 // params = (p, lambda1, lambda2), the order `Distrib::hyperexp`
335 // pushes them in.
337 num_traits<T>::to_double(mean), scv);
341 } else {
343 }
344 }
345 chainModel.set_service(stationNode[i], chainClass[c], d);
346 }
347 }
348
349 // ---- the chain routing ---------------------------------------------------
350 //
351 // p_chain(i -> j) = sum_{k,s in chain} alpha(i,k) * rt((i,k) -> (j,s)),
352 // i.e. the class-level routing weighted by how the chain's visits at i are
353 // shared among its classes. `rt` is indexed (stateful-1)*K + class.
355 for (std::size_t c = 0; c < C; ++c) {
356 const std::vector<std::size_t>& ic = sn.inchain[c];
357 Matrix<T> Pc(chainModel.raw_struct().nodes.size() + 1,
358 chainModel.raw_struct().nodes.size() + 1, zero);
359 for (std::size_t i = 1; i <= M; ++i) {
360 if (stationNode[i] == 0) continue;
361 const qn::NodeType tyi = sn.nodes[sn.station_to_node[i - 1] - 1].nodetype;
362 // A closed chain neither leaves through the Sink nor enters at the
363 // Source, so those rows are not its routing.
364 if (!isOpenChain[c] &&
365 (tyi == qn::NodeType::Source || tyi == qn::NodeType::Sink))
366 continue;
367 const std::size_t isf_i = sn.stateful_of_station(i);
368 for (std::size_t j = 1; j <= M; ++j) {
369 if (stationNode[j] == 0) continue;
370 const std::size_t isf_j = sn.stateful_of_station(j);
371 T pij = zero;
372 for (std::size_t a = 0; a < ic.size(); ++a) {
373 const std::size_t k = ic[a];
374 if (!(num_traits<T>::to_double(dem.alpha(i - 1, k - 1)) > 0.0)) continue;
375 for (std::size_t b = 0; b < ic.size(); ++b) {
376 const std::size_t s = ic[b];
377 const std::size_t from = (isf_i - 1) * K + k - 1;
378 const std::size_t to = (isf_j - 1) * K + s - 1;
379 if (from >= sn.rt.rows() || to >= sn.rt.cols()) continue;
380 const T p_ks = sn.rt(from, to);
381 if (num_traits<T>::to_double(p_ks) > 0.0)
382 pij += T(dem.alpha(i - 1, k - 1) * p_ks);
383 }
384 }
385 if (num_traits<T>::to_double(pij) > fineTol)
386 Pc(stationNode[i], stationNode[j]) = pij;
387 }
388 }
389 // Renormalise each row. The weights already sum to one when the chain
390 // decomposition and the alpha shares agree; the reference renormalises
391 // anyway and warns above 1%, and the correction is kept here so a
392 // rounding residue cannot turn into a leaking chain.
393 for (std::size_t a = 1; a < Pc.rows(); ++a) {
394 T rowSum = zero;
395 for (std::size_t b = 1; b < Pc.cols(); ++b) rowSum += Pc(a, b);
396 if (!(num_traits<T>::to_double(rowSum) > fineTol)) continue;
397 for (std::size_t b = 1; b < Pc.cols(); ++b)
398 if (num_traits<T>::to_double(Pc(a, b)) != 0.0) {
399 Pc(a, b) = T(Pc(a, b) / rowSum);
400 P.set(chainClass[c], chainClass[c], a, b, Pc(a, b));
401 }
402 }
403 }
404 chainModel.link(P);
405
406 // ---- the result ----------------------------------------------------------
407 ChainAggregationResult<T> out(chainModel);
408 out.alpha = dem.alpha;
409 out.chainclass = chainClass;
410 out.stationnode.assign(M, 0);
411 for (std::size_t i = 1; i <= M; ++i) out.stationnode[i - 1] = stationNode[i];
412 out.deagg.alpha = dem.alpha;
413 out.deagg.Lchain = dem.Lchain;
414 out.deagg.STchain = dem.STchain;
415 out.deagg.Vchain = dem.Vchain;
416 out.deagg.SCVchain = dem.SCVchain;
417 out.deagg.Nchain = dem.Nchain;
418 out.deagg.lambdachain = lambdaChain;
419 out.deagg.isopenchain = isOpenChain;
420 out.deagg.inchain = sn.inchain;
421 out.deagg.refstat.assign(K, 0);
422 for (std::size_t r = 0; r < K; ++r) out.deagg.refstat[r] = sn.classes[r].refstat;
423 out.deagg.refstatchain = dem.refstatchain;
424 out.deagg.isaggregated = true;
425 out.deagg.nclasses = K;
426 out.deagg.nchains = C;
427 return out;
428}
429
430} // namespace api
431} // namespace line
432
433#endif // LINE_API_SN_SN_AGGREGATE_CHAINS_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
A queueing network under construction.
std::size_t add_source(const std::string &nm)
The external arrival station.
std::size_t add_open_class(const std::string &nm, int prio=0)
An open class.
std::size_t add_delay(const std::string &nm)
An infinite-server station (a Delay, MATLAB's Delay / DelayStation).
std::size_t add_router(const std::string &nm)
A stateless routing node.
void set_number_of_servers(std::size_t node, double n)
queue.setNumberOfServers(n).
std::size_t add_queue(const std::string &nm, SchedStrategy sched=SchedStrategy::FCFS)
A queueing station.
std::size_t add_closed_class(const std::string &nm, double njobs, std::size_t refstat_node, int prio=0)
A closed class of the given population, referencing a station node.
std::size_t add_sink(const std::string &nm)
The external departure node.
void set_capacity(std::size_t node, double k)
station.setCapacity(k), the K of Kendall's notation.
NetworkStruct< T > & raw_struct()
The struct WITHOUT refreshing it, for a caller that is still building.
void link(const RoutingMatrix< T > &Pm)
model.link(P): install the routing.
void set_service(std::size_t node, std::size_t cls, const Distrib< T > &d)
station.setService(class, dist).
void set_arrival(std::size_t node, std::size_t cls, const Distrib< T > &d)
source.setArrival(class, dist): the same table, at the Source.
The routing matrix a model script fills in, MATLAB's P cell array.
void set(std::size_t r, std::size_t s, std::size_t i, std::size_t j, const T &p)
The moment fitters the reference distributions carry as STATIC FACTORIES: Erlang.fitMeanAndOrder,...
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.
ChainAggregationResult< T > sn_aggregate_chains(const qn::NetworkStruct< T > &sn, const std::string &suffix=std::string())
Collapse every chain onto one class, port of ModelAdapter.aggregateChains.
const char * node_type_to_text(NodeType t)
Name of a node kind, for diagnostics.
Definition lang_types.h:341
Distrib< T > hyperexp_fit_mean_scv(const T &mean, const T &scv)
HyperExp.fitMeanAndSCV(MEAN, SCV), which is map_hyperexp at p = 0.99 read back as (p,...
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
Definition sn_chain.h:63
The Network constructor API: Queue, Delay, Source, Sink, Router, ClassSwitch, Cache,...
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Chain aggregation and de-aggregation.
Everything needed to map a chain-level result back onto the classes.
std::vector< double > Nchain
(C) population, infinite for an open chain
std::vector< std::vector< std::size_t > > inchain
(C) 1-based class indices
Matrix< T > Vchain
(M x C) chain visits
std::vector< std::size_t > refstat
(K) 1-based reference station
bool isaggregated
false when C == K and the model was merely copied
std::size_t nclasses
K of the ORIGINAL model.
std::vector< T > lambdachain
(C) aggregate arrival rate, 0 for a closed chain
Matrix< T > STchain
(M x C) chain mean service time
Matrix< T > alpha
(M x K) class share of its chain's visits at a station
std::vector< std::size_t > refstatchain
(C) 1-based reference station
Matrix< T > Lchain
(M x C) chain demand
std::size_t nchains
C, the class count of the aggregate.
What sn_aggregate_chains returns.
Matrix< T > alpha
(M x K), the same matrix as deagg.alpha
std::vector< std::size_t > stationnode
stationnode[i] is the aggregate's 1-based node for original station i+1, 0 if dropped.
ChainAggregationResult(const qn::Network< T > &m)
qn::Network< T > model
the aggregate, one class per chain
std::vector< std::size_t > chainclass
chainclass[c] is the 1-based class index of chain c+1 in model.
static Distrib exp_rate(const T &r)
Definition lang_types.h:814
static Distrib disabled_dist()
Definition lang_types.h:857
std::vector< T > params
Constructor arguments, in MATLAB getParam order.
Definition lang_types.h:734
static Distrib det(const T &m)
Definition lang_types.h:858
static Distrib hyperexp(const T &p, const T &lambda1, const T &lambda2)
Definition lang_types.h:956
static Distrib erlang(const T &phase_rate, std::size_t r)
Erlang(alpha, r): r phases of rate alpha, as MATLAB's Erlang(phaseRate, nphases).
Definition lang_types.h:873
static Distrib exp_mean(const T &m)
Definition lang_types.h:799
static constexpr double FineTol
Definition lang_types.h:668
The chain-level view of a layer, as sn_get_demands_chain returns it.
Definition sn_chain.h:46
std::vector< std::size_t > refstatchain
(C) 1-based reference station
Definition sn_chain.h:53
std::vector< double > Nchain
(C) population, infinite for an open chain
Definition sn_chain.h:51
Matrix< T > alpha
(M x K) class share of its chain's visits at a station
Definition sn_chain.h:50
Matrix< T > STchain
(M x C) mean service time
Definition sn_chain.h:48
Matrix< T > Lchain
(M x C) demand
Definition sn_chain.h:47
Matrix< T > Vchain
(M x C) visits
Definition sn_chain.h:49
Matrix< T > SCVchain
(M x C)
Definition sn_chain.h:52
A node of the network.
One station of the network.
double cap
Station capacity in Kendall's K, as setCapacity sets it.
SchedStrategy sched
double nservers
may be infinite (a Delay, or an inf-scheduled task)