LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_state.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_STATE_H
6#define LINE_API_SN_SN_STATE_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Ports of matlab/src/api/sn/sn_get_state_aggr.m and sn_is_state_valid.m.
12 *
13 * THE STATE ARGUMENT. The reference reads `sn.state`, a cell of one row per
14 * STATEFUL node that the model layer writes at `initDefault` time. This struct
15 * carries only the pieces of a declared state that cannot be derived (a Place's
16 * initial marking, a warm cache's contents, a declared prior over a declared
17 * space), and the solvers build the rest. Both functions therefore take the
18 * state explicitly, which is also what makes them usable on a candidate state
19 * rather than only on the one the struct happens to hold.
20 *
21 * WHAT VALIDITY MEANS. Four things, in the reference's order: no class is
22 * present at a station that does not serve it; no station holds more jobs of a
23 * class than its capacity allows; the number of jobs IN SERVICE does not exceed
24 * the server count at a discipline that cannot overlap service, and never
25 * exceeds the number present; and every closed chain holds exactly its
26 * population. The last is the one that catches a hand-written initial state,
27 * and it is a relative test at CoarseTol because a fractional population is
28 * legal.
29 *
30 * ARITHMETIC: field. Counting and comparison.
31 */
32
33#include <cmath>
34#include <cstddef>
35#include <map>
36#include <utility>
37#include <vector>
38
40#include "line/lang/qn/state.h"
42#include "line/num/number.h"
43#include "line/util/error.h"
44
45namespace line {
46namespace api {
47
48/**
49 * Port of sn_get_state_aggr: the per-class job counts of each stateful node's
50 * state row, with the phase and buffer encoding aggregated away.
51 *
52 * @param state state[isf] is the row of the (isf+1)-th stateful node
53 * @return out[isf] is that node's (ni, nir) pair
54 */
55template <class T>
56std::vector<std::pair<T, std::vector<T>>> sn_get_state_aggr(
57 const qn::NetworkStruct<T>& sn, const std::vector<std::vector<T>>& state) {
58 std::vector<std::pair<T, std::vector<T>>> out;
59 out.reserve(state.size());
60 for (std::size_t isf = 0; isf < state.size(); ++isf) {
61 const std::size_t ind = sn.stateful_nodes[isf];
62 out.push_back(qn::to_marginal_aggr(sn, ind, state[isf]));
63 }
64 return out;
65}
66
67/**
68 * The (nstations x nclasses) per-class job counts of the model's OWN state.
69 *
70 * This is what the reference reads as `State.toMarginal(sn, ist, state{isf})`
71 * inside every `getProb*` getter, and it is a DECLARED quantity rather than a
72 * derived one: `setState` and `initFromMarginal` put the jobs where the caller
73 * asked, and the probability of "the model's state" is the probability of that
74 * placement. The writers emit the row as a one-row `stateSpace` with a `[1]`
75 * `statePrior` and the reader stores the pair, so it reaches here.
76 *
77 * A STATION THAT DECLARES NOTHING FALLS BACK TO THE DEFAULT MARKING -- every
78 * closed class's jobs at its reference station -- which is what `initDefault`
79 * would have put there, so a model that was never initialized answers exactly as
80 * it did before any of this existed. The fallback is per station and not
81 * all-or-nothing, matching the reference, where `setState` on one node leaves
82 * the others at whatever they held.
83 */
84template <class T>
86 const std::size_t M = sn.nstations, R = sn.nclasses;
88 std::vector<bool> declared(M, false);
89 for (std::size_t i = 0; i < M; ++i) {
90 const std::size_t ind = sn.station_to_node[i]; // 1-based node index
91 const typename std::map<std::size_t, Matrix<T>>::const_iterator ss =
92 sn.statespace.find(ind);
93 if (ss == sn.statespace.end() || ss->second.rows() != 1 || ss->second.cols() == 0) continue;
94 std::vector<T> row(ss->second.cols());
95 for (std::size_t c = 0; c < ss->second.cols(); ++c) row[c] = ss->second(0, c);
96 // A row the encoding cannot decode is left to the default marking rather
97 // than reported as zeros: zeros are a state, and the wrong one.
98 std::pair<T, std::vector<T>> m;
99 try {
100 m = qn::to_marginal_aggr(sn, ind, row);
101 } catch (const Error&) {
102 continue;
103 }
104 if (m.second.size() != R) continue;
105 for (std::size_t r = 0; r < R; ++r) nir(i, r) = m.second[r];
106 declared[i] = true;
107 }
108 for (std::size_t r = 0; r < R; ++r) {
109 const double pop = sn.classes[r].population;
110 if (!std::isfinite(pop)) continue; // an open class has no initial jobs
111 const std::size_t rs = sn.classes[r].refstat; // 1-based station
112 if (rs >= 1 && rs <= M && !declared[rs - 1])
113 nir(rs - 1, r) = num_traits<T>::from_double(pop);
114 }
115 return nir;
116}
117
118/**
119 * Port of `State.isValid`, which is the whole body of sn_is_state_valid once
120 * the marginals are formed.
121 *
122 * @param n (nstations x nclasses) jobs present
123 * @param s (nstations x nclasses) jobs in service
124 */
125template <class T>
127 const std::size_t M = sn.nstations, R = sn.nclasses;
128 if (n.rows() == 0 && s.rows() != 0) return false;
129 for (std::size_t i = 0; i < M; ++i) {
130 const std::size_t nd = sn.station_to_node[i];
131 const bool is_place = nd != 0 && sn.nodes[nd - 1].nodetype == qn::NodeType::Place;
132 for (std::size_t r = 0; r < R; ++r) {
133 if (!is_place && !sn.disabled.empty() && sn.disabled[i][r] &&
134 num_traits<T>::to_double(n(i, r)) > 0.0)
135 return false;
136 if (i < sn.classcap.size() && r < sn.classcap[i].size() &&
137 num_traits<T>::to_double(n(i, r)) > sn.classcap[i][r])
138 return false;
139 }
140 }
141 if (s.rows() != 0) {
142 for (std::size_t i = 0; i < M; ++i) {
143 const std::size_t nd = sn.station_to_node[i];
144 if (nd != 0 && sn.nodes[nd - 1].nodetype == qn::NodeType::Place) continue;
145 if (!(sn.stations[i].nservers > 0.0)) continue;
146 double sums = 0.0;
147 for (std::size_t r = 0; r < R; ++r) sums += num_traits<T>::to_double(s(i, r));
148 if (sums > sn.stations[i].nservers) {
149 switch (sn.stations[i].sched) {
150 case qn::SchedStrategy::FCFS:
151 case qn::SchedStrategy::SIRO:
152 case qn::SchedStrategy::LCFS:
153 case qn::SchedStrategy::HOL:
154 case qn::SchedStrategy::POLLING: return false;
155 default: break;
156 }
157 }
158 // the reference's `any(n<s)` is over the WHOLE pair of matrices,
159 // not this station's row; reproduced, because a state that puts a
160 // job in service where none is present is invalid wherever it is
161 for (std::size_t a = 0; a < M; ++a)
162 for (std::size_t r = 0; r < R; ++r)
164 return false;
165 }
166 }
167 for (std::size_t c = 0; c < sn.nchains; ++c) {
168 double njobs_chain = 0.0;
169 bool open = false;
170 for (std::size_t r = 0; r < R; ++r) {
171 if (!sn.chains[c][r]) continue;
172 if (std::isinf(sn.classes[r].population)) open = true;
173 njobs_chain += sn.classes[r].population;
174 }
175 if (open || std::isinf(njobs_chain)) continue;
176 double statejobs = 0.0;
177 for (std::size_t i = 0; i < M; ++i)
178 for (std::size_t r = 0; r < R; ++r)
179 if (sn.chains[c][r]) statejobs += num_traits<T>::to_double(n(i, r));
180 if (!(statejobs > 0.0)) return false;
181 if (std::fabs(1.0 - njobs_chain / statejobs) > qn::GlobalConstants::CoarseTol)
182 return false;
183 }
184 return true;
185}
186
187/**
188 * Port of sn_is_state_valid: form the station marginals of `state` and test
189 * them.
190 *
191 * A station whose row carries several candidate states is answered on its FIRST
192 * row, which is what the reference does after warning: a validity question
193 * about a set of states has no single answer.
194 */
195template <class T>
196bool sn_is_state_valid(const qn::NetworkStruct<T>& sn, const std::vector<std::vector<T>>& state) {
197 const T zero = num_traits<T>::from_int(0);
198 const std::size_t M = sn.nstations, R = sn.nclasses;
199 Matrix<T> n(M, R, zero), s(M, R, zero);
200 for (std::size_t ist = 1; ist <= M; ++ist) {
201 const std::size_t isf = sn.stateful_of_station(ist);
202 if (isf == 0 || isf > state.size()) return false;
203 const std::size_t ind = sn.stateful_nodes[isf - 1];
204 const qn::RowLayout<T> L = qn::row_layout(sn, ind, state[isf - 1].size());
205 std::vector<std::size_t> ph(R, 1), shift(R, 0);
206 for (std::size_t r = 0; r < R; ++r) {
207 ph[r] = L.K[r];
208 shift[r] = L.Ks[r];
209 }
210 const qn::Marginal<T> m = qn::to_marginal(sn, ist, state[isf - 1], ph, shift, L.nvar);
211 for (std::size_t r = 0; r < R; ++r) {
212 n(ist - 1, r) = m.nir[r];
213 s(ist - 1, r) = m.sir[r];
214 }
215 }
216 return sn_state_counts_valid(sn, n, s);
217}
218
219} // namespace api
220} // namespace line
221
222#endif // LINE_API_SN_SN_STATE_H
Base error for the multiprecision C++ port.
Definition error.h:31
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
The exception types the port throws.
bool sn_is_state_valid(const qn::NetworkStruct< T > &sn, const std::vector< std::vector< T > > &state)
Port of sn_is_state_valid: form the station marginals of state and test them.
Definition sn_state.h:196
Matrix< T > sn_declared_marginal(const qn::NetworkStruct< T > &sn)
The (nstations x nclasses) per-class job counts of the model's OWN state.
Definition sn_state.h:85
bool sn_state_counts_valid(const qn::NetworkStruct< T > &sn, const Matrix< T > &n, const Matrix< T > &s)
Port of State.isValid, which is the whole body of sn_is_state_valid once the marginals are formed.
Definition sn_state.h:126
std::vector< std::pair< T, std::vector< T > > > sn_get_state_aggr(const qn::NetworkStruct< T > &sn, const std::vector< std::vector< T > > &state)
Port of sn_get_state_aggr: the per-class job counts of each stateful node's state row,...
Definition sn_state.h:56
Marginal< T > to_marginal(const NetworkStruct< T > &sn, std::size_t ist, const std::vector< T > &state_i, const std::vector< std::size_t > &phasesz, const std::vector< std::size_t > &phaseshift, std::size_t nvar=0)
Port of State.toMarginal for a STATION, one state row at a time.
Definition state.h:130
std::pair< T, std::vector< T > > to_marginal_aggr(const NetworkStruct< T > &sn, std::size_t ind, const std::vector< T > &state_i)
Port of State.toMarginalAggr: the job counts of one node's state row, without the per-phase detail to...
RowLayout< T > row_layout(const NetworkStruct< T > &sn, std::size_t ind, std::size_t width)
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Port of the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
Port of the event half of MATLAB's +State package: the successor states an event produces at one node...
static constexpr double CoarseTol
Definition lang_types.h:669
What State.toMarginal returns for one station and one state row.
Definition state.h:51
std::vector< T > nir
jobs per class
Definition state.h:53
std::vector< T > sir
jobs in service per class
Definition state.h:54
How a station's state row splits into [buffer | server | local vars].
std::vector< std::size_t > Ks
offset of class r's phase block
std::size_t nvar
local-variable width
std::vector< std::size_t > K
phases per class