LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ctmc_fcr.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_CTMC_SOLVER_CTMC_FCR_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_FCR_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Finite Capacity Regions in SolverCTMC: the DROP rule, as a filter on the
12 * enumerated state space, and the gate that refuses WAITQ.
13 *
14 * WHY DROP IS A FILTER AND WAITQ IS NOT. Under DROP a job refused entry to a
15 * full region is LOST, so the chain simply never occupies a state violating the
16 * region's caps: removing those states and letting `make_infgen` re-close the
17 * rows is exactly the censored chain, and it is what the reference does (its
18 * `spaceGeneratorNodes` bound plus the post-generation filter). Under WAITQ the
19 * job leaves its station and PARKS in a per-region FIFO outside every station,
20 * to be released head-of-line as capacity frees; that queue is extra state the
21 * region owns, so the state vector has to be augmented with one token buffer
22 * per region and the transition relation rebuilt around it. The two are not
23 * variants of one mechanism, which is why only the first is here and the second
24 * is refused BY NAME rather than approximated by dropping.
25 *
26 * THE REGION BOUND ALSO BELONGS IN THE ENUMERATION, not only after it. The
27 * automatic cutoff is region-blind and can far exceed any reachable population
28 * -- cutoff 10 for a region capped at 4 -- and filtering only after generation
29 * means enumerating an intractable space first. Filtering here is correct but
30 * not sufficient for large models; see the note on `ctmc_region_class_cap`.
31 */
32
33#include <algorithm>
34#include <cmath>
35#include <cstddef>
36#include <vector>
37
39#include "line/lang/qn/state.h"
41#include "line/util/error.h"
42
43namespace line {
44namespace ctmc {
45
47
48/**
49 * Refuse the region rules this port does not implement.
50 *
51 * WAITQ needs the per-region token FIFO described above; BAS, BBS and RSRD are
52 * blocking rules whose held-job marker this generator does not carry either.
53 * Each is named so a caller learns which rule stopped it rather than seeing a
54 * region silently behave as DROP.
55 */
56template <class T>
58 for (std::size_t f = 0; f < sn.regions.size(); ++f)
59 for (std::size_t r = 0; r < sn.regions[f].rule.size(); ++r) {
60 const DropStrategy d = sn.regions[f].rule[r];
61 if (d == DropStrategy::DROP) continue;
62 const char* nm = d == DropStrategy::WAITQ ? "WAITQ"
63 : d == DropStrategy::BAS ? "BAS"
64 : d == DropStrategy::BBS ? "BBS"
65 : d == DropStrategy::RSRD ? "RSRD"
66 : "an unknown rule";
67 throw UnsupportedError(
68 std::string("SolverCTMC: finite capacity region ") + std::to_string(f + 1) +
69 " applies " + nm + " to class " + std::to_string(r + 1) +
70 ", which augments the state with a per-region waiting queue (WAITQ) or a held-job "
71 "marker (BAS/BBS/RSRD); only DROP is ported, and it is a filter on the state "
72 "space rather than extra state");
73 }
74}
75
76/**
77 * True when `nir` -- the per-(station, class) counts of one state, in
78 * `(ist-1)*K + k` order -- satisfies every region.
79 *
80 * The caps are compared against the counts SUMMED OVER THE MEMBER STATIONS,
81 * which is the whole point of a region: a per-station cap cannot express "at
82 * most 6 jobs between these three stations". The reference's -1 sentinel means
83 * unbounded and is skipped rather than compared.
84 */
85template <class T>
86bool ctmc_region_admissible(const NetworkStruct<T>& sn, const std::vector<T>& nir) {
87 const std::size_t M = sn.stations.size(), K = sn.nclasses;
88 for (std::size_t f = 0; f < sn.regions.size(); ++f) {
89 const typename NetworkStruct<T>::Region& rg = sn.regions[f];
90 double total = 0, memory = 0;
91 std::vector<double> per_class(K, 0.0);
92 double gcap = -1.0, memcap = -1.0;
93 std::vector<double> ccap(K, -1.0);
94 bool any_member = false;
95 for (std::size_t i = 0; i < M; ++i) {
96 if (i >= rg.members.size() || !rg.members[i]) continue;
97 any_member = true;
98 for (std::size_t k = 0; k < K; ++k) {
99 const double n = num_traits<T>::to_double(nir[i * K + k]);
100 if (!std::isfinite(n)) continue; // a Source's Inf sentinel
101 per_class[k] += n;
102 total += n;
103 memory += n * num_traits<T>::to_double(rg.size[k]);
104 }
105 // The caps are replicated on every member row, so the first member
106 // carries them; taking the tightest guards a hand-built struct.
107 for (std::size_t k = 0; k < K; ++k)
108 if (rg.cap[i][k] != -1.0)
109 ccap[k] = ccap[k] == -1.0 ? rg.cap[i][k] : std::min(ccap[k], rg.cap[i][k]);
110 if (rg.cap[i][K] != -1.0)
111 gcap = gcap == -1.0 ? rg.cap[i][K] : std::min(gcap, rg.cap[i][K]);
112 if (rg.maxmem[i] != -1.0)
113 memcap = memcap == -1.0 ? rg.maxmem[i] : std::min(memcap, rg.maxmem[i]);
114 }
115 if (!any_member) continue;
116 if (gcap != -1.0 && total > gcap + 1e-9) return false;
117 if (memcap != -1.0 && memory > memcap + 1e-9) return false;
118 for (std::size_t k = 0; k < K; ++k)
119 if (ccap[k] != -1.0 && per_class[k] > ccap[k] + 1e-9) return false;
120 // The linear constraint A n <= b, evaluated on the region-wide counts.
121 for (std::size_t row = 0; row < rg.lincon_A.rows() && row < rg.lincon_b.size(); ++row) {
122 double lhs = 0;
123 for (std::size_t k = 0; k < K && k < rg.lincon_A.cols(); ++k)
124 lhs += num_traits<T>::to_double(rg.lincon_A(row, k)) * per_class[k];
125 if (lhs > num_traits<T>::to_double(rg.lincon_b[row]) + 1e-9) return false;
126 }
127 }
128 return true;
129}
130
131/**
132 * The states of `space` a DROP region admits, in their original order.
133 *
134 * Order is preserved so the caller can restrict the arrival and departure rate
135 * arrays with the same index set; reordering here would silently misalign them.
136 */
137template <class T>
138std::vector<NetState<T>> ctmc_filter_regions(const NetworkStruct<T>& sn,
139 const std::vector<NetState<T>>& space) {
140 if (sn.regions.empty()) return space;
142 const Matrix<T> A = ctmc_state_space_aggr(sn, space);
143 std::vector<NetState<T>> out;
144 for (std::size_t s = 0; s < space.size(); ++s) {
145 std::vector<T> nir(A.cols());
146 for (std::size_t c = 0; c < A.cols(); ++c) nir[c] = A(s, c);
147 if (ctmc_region_admissible(sn, nir)) out.push_back(space[s]);
148 }
149 if (out.empty())
150 throw UnsupportedError(
151 "SolverCTMC: no state satisfies the finite capacity regions; check that the region "
152 "caps admit the model's population");
153 return out;
154}
155
156/**
157 * True where a class sits at a station inside a DROP region, per station.
158 *
159 * `solver_ctmc_avg_from_pi` needs it for the same reason it needs a finite
160 * capacity: a job that can be dropped never entered service, so the offered
161 * arrival rate is not what the server did and only the carried rate is a
162 * utilization.
163 */
164template <class T>
165std::vector<bool> ctmc_in_drop_region(const NetworkStruct<T>& sn) {
166 std::vector<bool> in(sn.stations.size(), false);
167 for (std::size_t f = 0; f < sn.regions.size(); ++f) {
168 bool has_drop = false;
169 for (std::size_t r = 0; r < sn.regions[f].rule.size(); ++r)
170 if (sn.regions[f].rule[r] == DropStrategy::DROP) has_drop = true;
171 if (!has_drop) continue;
172 for (std::size_t i = 0; i < in.size() && i < sn.regions[f].members.size(); ++i)
173 if (sn.regions[f].members[i]) in[i] = true;
174 }
175 return in;
176}
177
178} // namespace ctmc
179} // namespace line
180
181#endif // LINE_SOLVERS_CTMC_SOLVER_CTMC_FCR_H
std::size_t cols() const
Definition matrix.h:90
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
The exception types the port throws.
std::vector< bool > ctmc_in_drop_region(const NetworkStruct< T > &sn)
True where a class sits at a station inside a DROP region, per station.
bool ctmc_region_admissible(const NetworkStruct< T > &sn, const std::vector< T > &nir)
True when nir – the per-(station, class) counts of one state, in (ist-1)*K + k order – satisfies ever...
std::vector< NetState< T > > ctmc_filter_regions(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
The states of space a DROP region admits, in their original order.
void ctmc_check_region_rules(const NetworkStruct< T > &sn)
Refuse the region rules this port does not implement.
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...
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
Definition lang_types.h:424
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 the MATLAB +State package: the encoding that turns a station's state row into marginal job co...
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
Matrix< T > lincon_A
optional linear constraint A n <= b
std::vector< std::vector< double > > cap
(nstations x nclasses+1), -1 = unbounded
std::vector< double > maxmem
per member station, -1 = unbounded
std::vector< T > size
per class; size is the memory footprint
std::vector< bool > members
membership, independent of the caps
One network state: the per-stateful-node local rows it is composed of.
Definition state.h:2140