LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ssa_types.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_SSA_SSA_TYPES_H
6#define LINE_SOLVERS_SSA_SSA_TYPES_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Controls, results and the random source of SolverSSA.
12 *
13 * WHICH RANDOM STREAM, AND WHAT THAT COSTS. An SSA answer is a function of the
14 * random stream, so a seed-fixed golden belongs to one implementation only.
15 * MATLAB draws from `rand` (its own Mersenne Twister wrapper); the JAR and
16 * native Python share an MT19937. This port uses MT19937 too, but it does NOT
17 * claim stream compatibility with any of them: the ORDER in which the sample
18 * path consumes draws is part of the algorithm, and no two of the four
19 * implementations consume in the same order (the JAR, for instance, spends a
20 * draw on a single-phase entry-phase pick where this port does not). So a
21 * cross-codebase check against this engine is STATISTICAL, never exact --
22 * which is what `_kb/14-cpp-multiprecision.md` records and what the tests
23 * assert.
24 */
25
26#include <cstddef>
27#include <cstdint>
28#include <limits>
29#include <random>
30#include <string>
31#include <vector>
32
33#include "line/util/matrix.h"
34
35namespace line {
36namespace ssa {
37
38/** Helpers shared by the SSA engines that do not belong to a single one. */
39namespace ssa_detail {
40
41/**
42 * Add the START/PREEMPT counts of one successor row to the per-state rate
43 * accumulators, weighted by the rate of the arc that carries them. A node that
44 * is not a station contributes nothing: only a station has a server to seize.
45 */
46template <class T, class Outcome>
47inline void add_tag_rates(std::vector<std::vector<double>>& start,
48 std::vector<std::vector<double>>& preempt,
49 const T& sn, std::size_t node, const Outcome& oc, std::size_t row,
50 double w) {
51 if (!(w != 0) || node == 0 || node > sn.nodes.size()) return;
52 const std::size_t isf = sn.stateful_index(node);
53 if (isf == 0 || isf > start.size()) return;
54 if (row < oc.start.size())
55 for (std::size_t j = 0; j < oc.start[row].size(); ++j) {
56 const std::size_t cls = oc.start[row][j];
57 if (cls >= 1 && cls <= start[isf - 1].size()) start[isf - 1][cls - 1] += w;
58 }
59 if (row < oc.preempt.size())
60 for (std::size_t j = 0; j < oc.preempt[row].size(); ++j) {
61 const std::size_t cls = oc.preempt[row][j];
62 if (cls >= 1 && cls <= preempt[isf - 1].size()) preempt[isf - 1][cls - 1] += w;
63 }
64}
65
66} // namespace ssa_detail
67
68/** Controls, defaulting to `SolverOptions('SSA')` in the reference. */
69struct SsaOptions {
70 /** `default` and `nrm` both select the Next Reaction Method here. */
71 std::string method = "default";
72 /** Reaction firings to simulate; `options.samples` in the reference. */
73 std::size_t samples = 10000;
74 /** `options.seed`; LINE's own default is 23000. */
75 unsigned long seed = 23000;
76 bool verbose = false;
77 /**
78 * `options.config.warmupfrac`: the leading fraction of the path discarded
79 * before the means are taken.
80 *
81 * DECLARED HERE, NOT ON `SsaSerialOptions`, although only the serial and
82 * replicated engines read one: `ssa_serial_options` builds the engine's
83 * knobs by copying the BASE slice of the caller's, so a field on the
84 * derived struct is unreachable from a caller and kept its default however
85 * the request was spelled -- which is what made `--warmupfrac` a knob the
86 * C++ CLI could not offer at all.
87 */
88 double warmupfrac = 0.0;
89 /**
90 * `options.config.state_space_gen` of `solver_ssa_analyzer_nrm.m`: which of
91 * the two NRM engines runs. `none` and `default` take the plain engine,
92 * which integrates the metrics along the path; anything else takes the
93 * tabulating one, which records the states it visits and forms the means as
94 * `pi * A` over them. The two answer the same question by different routes,
95 * which is what makes them testable against each other.
96 */
97 std::string state_space_gen = "default";
98};
99
100/** What the analyzer returns, in the same shape as the MVA and fluid results. */
103 std::vector<double> CN, XN;
104 /**
105 * The DERIVED rates, (nstations x nclasses): how often per unit time a
106 * class-r service STARTS at station i, and how often a class-r job in
107 * service is PUSHED BACK into the buffer there. At a lossless station with
108 * no in-service abandonment StartN == TN + PreemptN, up to simulation
109 * error; SolverCTMC reports the exact value of the same quantity.
110 */
112 /** The concrete algorithm, as the reference's `method`. */
113 std::string method = "nrm";
114 /** Simulated time the metrics are averaged over; the reference's `totalTime`. */
115 double simulated_time = 0.0;
116 /** Reaction firings actually performed. */
117 std::size_t samples = 0;
118};
119
120/**
121 * The uniform source, MATLAB's `rand`.
122 *
123 * A 53-bit uniform assembled from two MT19937 words, the construction numpy
124 * and the JAR's RandomManager both use, shifted by half an ulp so the value is
125 * strictly inside (0,1). The shift is not cosmetic: `-log(u)` is the
126 * exponential clock of every reaction and an exact zero would make it
127 * infinite, which the run loop reads as a deadlock.
128 */
129class SsaRng {
130public:
131 explicit SsaRng(unsigned long seed) : g_(static_cast<std::uint_fast32_t>(seed)) {}
132
133 double uniform() {
134 const std::uint64_t a = g_() >> 5, b = g_() >> 6;
135 return (static_cast<double>(a) * 67108864.0 + static_cast<double>(b) + 0.5) /
136 9007199254740992.0;
137 }
138
139 /** Uniform index in [0, n), the reference's `1 + floor(rand*n)`. */
140 std::size_t index(std::size_t n) {
141 if (n == 0) return 0;
142 const std::size_t k = static_cast<std::size_t>(uniform() * static_cast<double>(n));
143 return k < n ? k : n - 1;
144 }
145
146 /**
147 * Index drawn from the unnormalized nonnegative weights `p`, the
148 * reference's `drawFromDist`: an all-zero weight vector yields index 0.
149 */
150 std::size_t draw(const std::vector<double>& p) {
151 double tot = 0.0;
152 for (double x : p) tot += x;
153 if (!(tot > 0.0)) return 0;
154 const double u = uniform() * tot;
155 double acc = 0.0;
156 for (std::size_t i = 0; i < p.size(); ++i) {
157 acc += p[i];
158 if (acc > u) return i;
159 }
160 return p.size() - 1;
161 }
162
163private:
164 std::mt19937 g_;
165};
166
167} // namespace ssa
168} // namespace line
169
170#endif // LINE_SOLVERS_SSA_SSA_TYPES_H
std::size_t draw(const std::vector< double > &p)
Index drawn from the unnormalized nonnegative weights p, the reference's drawFromDist: an all-zero we...
Definition ssa_types.h:150
std::size_t index(std::size_t n)
Uniform index in [0, n), the reference's 1 + floor(rand*n).
Definition ssa_types.h:140
SsaRng(unsigned long seed)
Definition ssa_types.h:131
Dense matrix and non-owning view.
Controls, defaulting to SolverOptions('SSA') in the reference.
Definition ssa_types.h:69
std::size_t samples
Reaction firings to simulate; options.samples in the reference.
Definition ssa_types.h:73
double warmupfrac
options.config.warmupfrac: the leading fraction of the path discarded before the means are taken.
Definition ssa_types.h:88
std::string state_space_gen
options.config.state_space_gen of solver_ssa_analyzer_nrm.m: which of the two NRM engines runs.
Definition ssa_types.h:97
std::string method
default and nrm both select the Next Reaction Method here.
Definition ssa_types.h:71
unsigned long seed
options.seed; LINE's own default is 23000.
Definition ssa_types.h:75
What the analyzer returns, in the same shape as the MVA and fluid results.
Definition ssa_types.h:101
std::vector< double > XN
Definition ssa_types.h:103
std::vector< double > CN
Definition ssa_types.h:103
Matrix< double > UN
Definition ssa_types.h:102
Matrix< double > RN
Definition ssa_types.h:102
Matrix< double > StartN
The DERIVED rates, (nstations x nclasses): how often per unit time a class-r service STARTS at statio...
Definition ssa_types.h:111
double simulated_time
Simulated time the metrics are averaged over; the reference's totalTime.
Definition ssa_types.h:115
Matrix< double > TN
Definition ssa_types.h:102
std::size_t samples
Reaction firings actually performed.
Definition ssa_types.h:117
Matrix< double > QN
Definition ssa_types.h:102
std::string method
The concrete algorithm, as the reference's method.
Definition ssa_types.h:113
Matrix< double > PreemptN
Definition ssa_types.h:111