LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ldes_warm_start.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_SOLVERS_WRAPPERS_LDES_LDES_WARM_START_H
6#define LINE_SOLVERS_WRAPPERS_LDES_LDES_WARM_START_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The LDES warm start: `SolverLDES(model, initSolver)` / `initFromSolver`.
12 *
13 * SOURCE. `@@NetworkSolver/warmStartPlacement.m` and
14 * `@@SolverLDES/initFromSolver.m`. An auxiliary solver's steady state decides
15 * an integer job placement (nstations x nclasses), which reaches the engine as
16 * `LdesOptions.init_sol` (`--initsol`, STATION-MAJOR) with the warmup filter
17 * off, since a run that starts near steady state has no transient to remove.
18 *
19 * TWO ARMS, as in the reference, and the caller picks one by which function it
20 * calls rather than by the dynamic type of a solver object:
21 *
22 * - `warm_start_placement_from_qlen`: any solver's mean queue lengths, floored
23 * per station and completed by LARGEST REMAINDER so each closed population is
24 * conserved; an open class is rounded and not completed.
25 * - `warm_start_placement_from_ctmc`: the MODE of the exact stationary law
26 * aggregated over per-(station, class) job counts. Ties go to the
27 * lexicographically smallest aggregate state, which is the row `unique` puts
28 * first and `max` then returns in the reference.
29 *
30 * Only a Queue or a Delay receives jobs; every other station keeps a zero row.
31 */
32
33#include <cmath>
34#include <cstddef>
35#include <map>
36#include <vector>
37
42#include "line/util/error.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace ldes {
47
48namespace warm_detail {
49
50template <class T>
51bool is_service_station(const qn::NetworkStruct<T>& sn, std::size_t i) {
52 const lang::NodeType nt = sn.stations[i].nodetype;
53 return nt == lang::NodeType::Queue || nt == lang::NodeType::Delay;
54}
55
56} // namespace warm_detail
57
58/**
59 * Port of `placementFromMeanQLen`: an integer placement from mean queue lengths.
60 *
61 * @param sn the refreshed network struct the means were computed on
62 * @param QN (nstations x nclasses) steady-state mean queue lengths
63 */
64template <class T>
66 const Matrix<T>& QN) {
67 const std::size_t M = sn.nstations, K = sn.nclasses;
68 if (QN.rows() != M || QN.cols() != K)
69 throw InputError("warm_start_placement_from_qlen: QN must be (nstations x nclasses)");
70 Matrix<double> P(M, K, 0.0);
71 for (std::size_t r = 0; r < K; ++r) {
72 const double njobs = sn.classes[r].population;
73 const bool closed = std::isfinite(njobs);
74 std::vector<double> floors(M, 0.0), fracs(M, -1.0);
75 std::vector<bool> eligible(M, false);
76 for (std::size_t i = 0; i < M; ++i) {
77 eligible[i] = warm_detail::is_service_station(sn, i);
78 if (!eligible[i]) continue;
79 const double m = std::max(0.0, num_traits<T>::to_double(QN(i, r)));
80 if (closed) {
81 floors[i] = std::floor(m);
82 fracs[i] = m - floors[i];
83 } else {
84 floors[i] = std::round(m);
85 }
86 P(i, r) = floors[i];
87 }
88 if (!closed) continue;
89 double placed = 0.0;
90 for (std::size_t i = 0; i < M; ++i) placed += floors[i];
91 long residual = std::lround(njobs) - std::lround(placed);
92 while (residual > 0) {
93 // `max` returns the FIRST maximum, so ties go to the lower station.
94 std::size_t bi = 0;
95 for (std::size_t i = 1; i < M; ++i)
96 if (fracs[i] > fracs[bi]) bi = i;
97 if (fracs[bi] < 0.0) {
98 // Every remainder is spent: the leftover jobs go to the reference
99 // station (or the first eligible one) so the population holds.
100 std::size_t ref = sn.classes[r].refstat; // 1-based
101 std::size_t at = M;
102 if (ref >= 1 && ref <= M && eligible[ref - 1]) at = ref - 1;
103 for (std::size_t i = 0; at == M && i < M; ++i)
104 if (eligible[i]) at = i;
105 if (at == M)
106 throw InputError("warm_start_placement_from_qlen: closed class '" +
107 sn.classes[r].name + "' has no Queue or Delay to start at");
108 P(at, r) += static_cast<double>(residual);
109 break;
110 }
111 P(bi, r) += 1.0;
112 fracs[bi] = -1.0;
113 --residual;
114 }
115 }
116 return P;
117}
118
119/**
120 * Port of `placementFromCtmcSteadyState`: the mode of the aggregate stationary law.
121 *
122 * @param sn the refreshed network struct
123 * @param opt the CTMC options of the auxiliary solve (cutoff, method)
124 */
125template <class T>
127 const ctmc::CtmcOptions& opt) {
129 // A fork-join chain lives on the augmented struct, whose classes are not the
130 // model's; the aggregate state would be read against the wrong columns.
131 if (!sol.fjclassmap.empty())
132 throw UnsupportedError(
133 "warm_start_placement_from_ctmc: a fork-join model is solved on its tag-augmented "
134 "chain; warm-start it from a mean-value solver instead");
135 const std::size_t M = sn.nstations, K = sn.nclasses;
136 const Matrix<T> SSq = ctmc::ctmc_state_space_aggr(sn, sol.chain.space);
137 std::map<std::vector<long>, double> aggr;
138 for (std::size_t s = 0; s < SSq.rows(); ++s) {
139 double p = num_traits<T>::to_double(sol.pi[s]);
140 if (p < 1e-14) p = 0.0; // GlobalConstants.Zero, as the reference clips it
141 std::vector<long> key(M * K);
142 for (std::size_t c = 0; c < M * K; ++c)
143 key[c] = std::lround(num_traits<T>::to_double(SSq(s, c)));
144 aggr[key] += p;
145 }
146 if (aggr.empty())
147 throw InputError("warm_start_placement_from_ctmc: the chain has no states");
148 // Ordered keys, strict comparison: the first (smallest) of several modes wins.
149 std::map<std::vector<long>, double>::const_iterator best = aggr.begin();
150 for (std::map<std::vector<long>, double>::const_iterator it = aggr.begin(); it != aggr.end();
151 ++it)
152 if (it->second > best->second) best = it;
153 Matrix<double> P(M, K, 0.0);
154 for (std::size_t i = 0; i < M; ++i) {
155 if (!warm_detail::is_service_station(sn, i)) continue;
156 for (std::size_t r = 0; r < K; ++r) P(i, r) = static_cast<double>(best->first[i * K + r]);
157 }
158 return P;
159}
160
161/**
162 * Port of `initFromSolver`'s last step: the placement becomes `init_sol`,
163 * STATION-MAJOR (`reshape(placement', 1, M*K)`), and the transient filter is
164 * disabled with `tranfilter = fixed`, `warmupfrac = 0`.
165 */
167 o.init_sol.clear();
168 for (std::size_t i = 0; i < P.rows(); ++i)
169 for (std::size_t r = 0; r < P.cols(); ++r) o.init_sol.push_back(P(i, r));
170 o.tranfilter = "fixed";
171 o.warmupfrac = 0.0;
172}
173
174} // namespace ldes
175} // namespace line
176
177#endif // LINE_SOLVERS_WRAPPERS_LDES_LDES_WARM_START_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.
The exception types the port throws.
The option and result records of SolverLDES, the discrete-event simulator.
Dense matrix and non-owning view.
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:326
bool is_service_station(const qn::NetworkStruct< T > &sn, std::size_t i)
Matrix< double > warm_start_placement_from_qlen(const qn::NetworkStruct< T > &sn, const Matrix< T > &QN)
Port of placementFromMeanQLen: an integer placement from mean queue lengths.
Matrix< double > warm_start_placement_from_ctmc(const qn::NetworkStruct< T > &sn, const ctmc::CtmcOptions &opt)
Port of placementFromCtmcSteadyState: the mode of the aggregate stationary law.
void init_from_placement(LdesOptions &o, const Matrix< double > &P)
Port of initFromSolver's last step: the placement becomes init_sol, STATION-MAJOR (reshape(placement'...
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Port of solver_ctmc.m: the infinitesimal generator of a queueing network, assembled from the enumerat...
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
The SolverCTMC knobs this port honours.
Everything one CTMC solve produces.
std::vector< std::size_t > fjclassmap
fjclassmap when the model was fork-join, empty otherwise: the ORIGINAL class of each auxiliary siblin...
std::vector< T > pi
stationary distribution over chain.space
The knobs of one LDES run.
double warmupfrac
–warmupfrac, only for tranfilter=fixed
std::string tranfilter
–tranfilter: mser5 | fixed | none
std::vector< double > init_sol
–initsol, the warm-start placement as a STATION-MAJOR vector [st0_cl0, st0_cl1, .....