LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ldes_options.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_OPTIONS_H
6#define LINE_SOLVERS_WRAPPERS_LDES_LDES_OPTIONS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The option and result records of SolverLDES, the discrete-event simulator.
12 *
13 * SOURCE. `LDESOptions` transcribes `python/line_solver/solvers/wrappers/
14 * solver_ldes/ldes_options.py` and the `ldesOpt` reads of
15 * `@@SolverLDES/solveCli.m`; `LdesResult` transcribes the `ldes-result`
16 * document `jline/io/LDESResultIO.java` writes, block for block. The two
17 * subprocess clients that precede this one (MATLAB, native Python) parse a
18 * SUBSET of that document; this record carries all of it, because a block that
19 * is parsed nowhere is a block whose meaning drifts unnoticed.
20 *
21 * EVERY KNOB IS A CLI FLAG, and the defaults here are the ENGINE's, not this
22 * port's: `ldes_flags` below emits a flag only when the value differs from the
23 * default, exactly as `_build_flag_args` and `solveCli` do, so a default run
24 * produces the minimal command line an older AOT native binary still parses. A
25 * default that disagreed with `LdesCLI`'s would therefore be invisible in the
26 * command line and change the answer -- which is why `seed` is the one
27 * exception and is ALWAYS emitted: the CLI default is -1 (a random seed), not
28 * 23000, so omitting it at the nominal seed would make the run irreproducible
29 * and silently different from the MATLAB and Python clients.
30 */
31
32#include <cstddef>
33#include <limits>
34#include <map>
35#include <string>
36#include <vector>
37
39#include "line/util/matrix.h"
40
41namespace line {
42namespace ldes {
43
44/**
45 * Port of `SolverLDES.listValidMethods`.
46 *
47 * Two names, and only one engine behind them: 'parallel' asks the engine for
48 * INDEPENDENT REPLICATIONS and the mean over them, which the client turns into
49 * `--replications` (the caller's count when set, 8 otherwise). It is not a
50 * second simulator, which is why the list is this short in all four codebases.
51 */
52inline std::vector<std::string> list_valid_methods() {
53 return {"default", "parallel"};
54}
55
56/**
57 * The knobs of one LDES run.
58 *
59 * `samples` is an EVENT BUDGET (service completions), not a sample size in the
60 * statistical sense, and `events` is the same budget under the name the newer
61 * API uses: when set it overrides `samples`, as it does in both clients.
62 */
64 std::size_t samples = 200000; ///< -s, service-completion budget
65 std::size_t events = 0; ///< 0 = not given; overrides `samples` when set
66 long seed = 23000; ///< --seed; -1 requests a random stream
67 std::string method = "default";
68
69 // ---- convergence-based stopping ----------------------------------------
70 bool cnvgon = false; ///< --cnvgon
71 double cnvgtol = 0.05; ///< --cnvgtol
72 int cnvgbatch = 20; ///< --cnvgbatch, batches before the first check
73 int cnvgchk = 0; ///< --cnvgchk, events between checks; 0 = samples/50
74
75 // ---- warmup (transient) filter -----------------------------------------
76 std::string tranfilter = "mser5"; ///< --tranfilter: mser5 | fixed | none
77 int mserbatch = 5; ///< --mserbatch, MSER batch size
78 double warmupfrac = 0.2; ///< --warmupfrac, only for tranfilter=fixed
79
80 // ---- confidence intervals ----------------------------------------------
81 std::string cimethod = "obm"; ///< --cimethod: obm | bm | spectral | none
82 double obmoverlap = 0.5; ///< --obmoverlap; 0 reduces OBM to plain batch means
83 int ciminbatch = 10; ///< --ciminbatch
84 int ciminobs = 100; ///< --ciminobs, below which no CI is reported
85 /**
86 * Confidence level of the reported half-widths.
87 *
88 * `LDESOptions` sets 0.95 where the shared `SolverOptions` leaves it at 0
89 * (disabled), so the LDES default is intervals ON. It is not a CLI flag:
90 * the engine reads it from the options object, so the subprocess client
91 * has no way to vary it and the native engine keeps the same default.
92 */
93 double confint = 0.95;
94 double spectral_low_freq_frac = 0.25; ///< --spectrallowfreqfrac
95 /**
96 * `options.config.runLengthPlan`: ask for the run length this run SHOULD
97 * have had, for a target relative precision.
98 *
99 * Non-positive leaves the plan uncomputed. The half-widths above pin the
100 * ASYMPTOTIC variance of each estimator -- not its stationary variance,
101 * which on M/M/1 differs from it by a factor blowing up like (1-rho)^-2 --
102 * and `sim_runlength_plan` turns that into the run length that reaches this
103 * precision. The plan lands in `LdesResult::run_length_plan`.
104 */
106
107 // ---- discrete time ------------------------------------------------------
108 bool slotted = false; ///< --slotted, run on the slot lattice
109 double slot_length = 1.0; ///< --slotlength
110
111 // ---- busy periods -------------------------------------------------------
112 /**
113 * --busyperiod: highest busy period order measured, 0 disabling the
114 * measurement. A busy period of order n for a set of stations runs from the
115 * instant an arrival raises the jobs it holds to n up to the instant it
116 * falls back below n (Daduna, J. ACM 35(3), 1988). When positive the orders
117 * 1..busy_period_orders are measured for every station, for every
118 * station-class pair, and for every set in `busy_period_subnets`.
119 */
121 /** --busyperiod-subnet, zero-based station indexes, one flag per set. */
122 std::vector<std::vector<std::size_t>> busy_period_subnets;
123
124 // ---- replication --------------------------------------------------------
125 int replications = 0; ///< --replications; 0 = not given (one path)
126 int numthreads = 0; ///< --numthreads; 0 = not given
127
128 // ---- horizon and budget --------------------------------------------------
129 bool has_timespan = false; ///< true when [t0,t1] was set: a TRANSIENT run
130 double t0 = 0.0;
131 double t1 = 0.0;
132 /** --maxtime, a COOPERATIVE wall-clock budget the event loop polls. */
133 double timeout = std::numeric_limits<double>::infinity();
134
135 /**
136 * --initsol, the warm-start placement as a STATION-MAJOR vector
137 * [st0_cl0, st0_cl1, ..., stM-1_clK-1]. Empty = the engine's own default
138 * (closed jobs at their reference stations).
139 */
140 std::vector<double> init_sol;
141
142 /**
143 * --export-histogram and --trajectory: the joint-state residence-time law
144 * and the state path.
145 *
146 * A TRANSIENT run fills both unconditionally, because it has to walk the
147 * state to produce QNt at all. In STEADY STATE they are opt-in: the
148 * histogram costs a map lookup per event and the trajectory grows one row
149 * per event, which on a million-completion run is a million rows nobody
150 * asked for.
151 */
152 bool export_histogram = false;
153 bool export_trajectory = false;
154
155 /**
156 * --respt-samples: every per-visit response time, not just its mean.
157 *
158 * It is the input to an EMPIRICAL CDF, which a mean cannot stand in for, and
159 * it costs one push per completion, so it is opt-in like the two above.
160 */
161 bool export_respt = false;
162
163 /**
164 * Base URL of an LDES REST server (the imperialqore/ldes container). When
165 * set the model is POSTed to `<url>/api/v1/solve` instead of being handed to
166 * a local binary; the wire format is the same on both sides, so a fixed
167 * seed gives the same numbers.
168 */
169 std::string rest_url;
170
171 bool verbose = false; ///< echo the resolved command line before running it
172};
173
174/** Per-cache hit/miss/latency, as the `cacheMetrics` block carries them. */
176 Matrix<double> hit; ///< (1 x nclasses) hit probability
177 Matrix<double> delayed; ///< (1 x nclasses) delayed-hit probability
178 Matrix<double> miss; ///< (1 x nclasses) miss probability
179 Matrix<double> latency; ///< (1 x nclasses) expected retrieval latency
180 Matrix<double> hitList; ///< (nclasses x nlists) hit probability by list
181 Matrix<double> itemProb; ///< (nitems x nlists+1) item position law
182 Matrix<double> listCost; ///< (1 x nlists) mean storage cost held per list
183};
184
185/**
186 * One `ldes-result` document, parsed.
187 *
188 * A metric the engine did not report stays EMPTY rather than becoming a matrix
189 * of zeros: zero is a measurement and absence is not, and the tables below
190 * distinguish them.
191 */
193 // ---- dimensions and labels ----------------------------------------------
194 std::size_t nstations = 0;
195 std::size_t nclasses = 0;
196 std::size_t nchains = 0;
197 std::size_t nregions = 0;
198 std::vector<std::string> station_names;
199 std::vector<std::string> class_names;
200
201 // ---- means, (nstations x nclasses) --------------------------------------
203 Matrix<double> CN, XN; ///< (1 x nclasses), per-class visits and system tput
204 Matrix<double> DropRateJoin; ///< quorum-Join sibling drops, Join rows only
205
206 // ---- confidence intervals and relative precision -------------------------
208 /**
209 * `options.config.runLengthPlan`: the run length the caller would need for
210 * the precision they asked for, from the half-widths above. Empty unless
211 * `LdesOptions::run_length_plan_precision` is positive.
212 */
217
218 // ---- finite capacity regions, (nregions x nclasses) ----------------------
221
222 // ---- impatience, (nstations x nclasses) ----------------------------------
226
227 /** Per-cache metrics, keyed by the Cache NODE name. */
228 std::map<std::string, LdesCacheMetrics> cache_metrics;
229
230 /**
231 * The exact joint-state residence-time histogram (--export-histogram).
232 *
233 * `histogram_space` is (nstates x nstations*nclasses) in the aggregate
234 * layout `ctmc_state_space_aggr` builds and `histogram_time` the residence
235 * time of each row, so E[r] = sum_s (t_s / sum t) r(state_s) is exact also
236 * for a NONLINEAR reward. `traj_*` is the same encoding along the sampled
237 * path, downsampled, and is what the transient reward reads.
238 */
243
244 /**
245 * One measured busy period target (--busyperiod): a station, a
246 * station-class pair, or a declared station set. `mean` and `count` are
247 * indexed by order 1..K, and a mean whose count is zero is not an
248 * observation. `job_class` is -1 when the target aggregates every class.
249 */
251 std::string name;
252 std::vector<std::size_t> stations; ///< station indexes, zero-based
253 int job_class = -1;
254 std::vector<double> mean;
255 std::vector<double> count;
256 };
257 std::vector<BusyPeriodTarget> busy_periods;
258
259 // ---- transient trajectory (--timespan + --trajectory) --------------------
260 std::vector<double> t; ///< the time vector, empty on a steady-state run
261 /**
262 * [STATION][class] -> (npoints x 2), columns [value, time].
263 *
264 * THE OUTER INDEX IS THE STATION, not the stateful node: the engine builds
265 * these as `result.QNt = new Matrix[numStations][numClasses]` and fills them
266 * at `serviceStation` (`Solver_ssj.getTransientLDESResultBody`). The two
267 * index spaces coincide only when every stateful node is a station, so on a
268 * model holding a Router, a Cache or a Place the stateful index selects
269 * another station's series or runs off the end. That is the defect recorded
270 * as BUG-95 and fixed in the MATLAB and Python clients.
271 *
272 * THE VALUES ARE INTERVAL TIME-AVERAGES, not the integer states the path
273 * visits: each entry is (integral of the queue length over the sampling
274 * interval) / (interval length). They are a trajectory of MEANS, so they
275 * must not be compared against a state to estimate a probability -- use
276 * `histogram_space` / `histogram_time` for that.
277 */
278 std::vector<std::vector<Matrix<double>>> QNt, UNt, TNt;
279 /** [station][class] -> the per-job response times the engine recorded. */
280 std::vector<std::vector<std::vector<double>>> respTimeSamples;
281
282 // ---- provenance -----------------------------------------------------------
283 std::string method = "default";
284 double runtime = 0.0;
285 bool converged = false;
286 /** convergence | max_events | max_sim_events | max_time. */
287 std::string stopping_reason;
290 /** True when the HARD subprocess bound fired, not the cooperative one. */
291 bool timed_out = false;
292 /** "native" or "jar": which runner produced these numbers. */
293 std::string engine;
294};
295
296} // namespace ldes
297} // namespace line
298
299#endif // LINE_SOLVERS_WRAPPERS_LDES_LDES_OPTIONS_H
Dense matrix and non-owning view.
std::vector< std::string > list_valid_methods()
Port of SolverLDES.listValidMethods.
Run-length planning for steady-state simulation.
Per-cache hit/miss/latency, as the cacheMetrics block carries them.
Matrix< double > listCost
(1 x nlists) mean storage cost held per list
Matrix< double > latency
(1 x nclasses) expected retrieval latency
Matrix< double > hitList
(nclasses x nlists) hit probability by list
Matrix< double > itemProb
(nitems x nlists+1) item position law
Matrix< double > delayed
(1 x nclasses) delayed-hit probability
Matrix< double > miss
(1 x nclasses) miss probability
Matrix< double > hit
(1 x nclasses) hit probability
The knobs of one LDES run.
double warmupfrac
–warmupfrac, only for tranfilter=fixed
int ciminbatch
–ciminbatch
double spectral_low_freq_frac
–spectrallowfreqfrac
bool verbose
echo the resolved command line before running it
double cnvgtol
–cnvgtol
double confint
Confidence level of the reported half-widths.
std::string rest_url
Base URL of an LDES REST server (the imperialqore/ldes container).
double obmoverlap
–obmoverlap; 0 reduces OBM to plain batch means
double run_length_plan_precision
options.config.runLengthPlan: ask for the run length this run SHOULD have had, for a target relative ...
std::vector< std::vector< std::size_t > > busy_period_subnets
–busyperiod-subnet, zero-based station indexes, one flag per set.
long seed
–seed; -1 requests a random stream
bool slotted
–slotted, run on the slot lattice
int mserbatch
–mserbatch, MSER batch size
std::string cimethod
–cimethod: obm | bm | spectral | none
double slot_length
–slotlength
int cnvgchk
–cnvgchk, events between checks; 0 = samples/50
bool export_respt
–respt-samples: every per-visit response time, not just its mean.
int replications
–replications; 0 = not given (one path)
int ciminobs
–ciminobs, below which no CI is reported
std::size_t events
0 = not given; overrides samples when set
int cnvgbatch
–cnvgbatch, batches before the first check
bool export_histogram
–export-histogram and –trajectory: the joint-state residence-time law and the state path.
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, .....
int numthreads
–numthreads; 0 = not given
std::size_t samples
-s, service-completion budget
double timeout
–maxtime, a COOPERATIVE wall-clock budget the event loop polls.
bool has_timespan
true when [t0,t1] was set: a TRANSIENT run
int busy_period_orders
–busyperiod: highest busy period order measured, 0 disabling the measurement.
One measured busy period target (–busyperiod): a station, a station-class pair, or a declared station...
std::vector< std::size_t > stations
station indexes, zero-based
One ldes-result document, parsed.
Matrix< double > balkedCustomers
Matrix< double > QNSamples
sim::RunLengthPlan< double > run_length_plan
std::map< std::string, LdesCacheMetrics > cache_metrics
Per-cache metrics, keyed by the Cache NODE name.
Matrix< double > avgOrbitSize
Matrix< double > QNRelPrec
Matrix< double > TNCI
Matrix< double > WNfcr
std::vector< std::vector< Matrix< double > > > QNt
[STATION][class] -> (npoints x 2), columns [value, time].
Matrix< double > renegingRate
Matrix< double > XN
(1 x nclasses), per-class visits and system tput
std::vector< std::vector< std::vector< double > > > respTimeSamples
[station][class] -> the per-job response times the engine recorded.
Matrix< double > UNfcr
Matrix< double > WeightNfcr
Matrix< double > RNRelPrec
Matrix< double > avgRenegingWaitTime
Matrix< double > QNfcr
Matrix< double > traj_space
bool has_run_length_plan
options.config.runLengthPlan: the run length the caller would need for the precision they asked for,...
Matrix< double > UN
Matrix< double > DropRateNfcr
Matrix< double > histogram_space
The exact joint-state residence-time histogram (–export-histogram).
std::string stopping_reason
convergence | max_events | max_sim_events | max_time.
std::vector< std::vector< Matrix< double > > > UNt
std::vector< std::string > class_names
Matrix< double > TNRelPrec
Matrix< double > TN
Matrix< double > UNCI
Matrix< double > TNSamples
Matrix< double > RN
Matrix< double > UNRelPrec
std::string engine
"native" or "jar": which runner produced these numbers.
Matrix< double > QNCI
Matrix< double > traj_time
std::vector< BusyPeriodTarget > busy_periods
Matrix< double > CN
Matrix< double > RNfcr
Matrix< double > WN
Matrix< double > retriedCustomers
Matrix< double > retrialDropped
Matrix< double > AN
long long total_simulated_events
Matrix< double > MemOccNfcr
Matrix< double > TNfcr
std::vector< std::string > station_names
Matrix< double > DropRateJoin
quorum-Join sibling drops, Join rows only
Matrix< double > UNSamples
bool timed_out
True when the HARD subprocess bound fired, not the cooperative one.
Matrix< double > balkingProbability
Matrix< double > renegedCustomers
Matrix< double > histogram_time
Matrix< double > RNSamples
Matrix< double > RNCI
std::vector< double > t
the time vector, empty on a steady-state run
Matrix< double > WNCI
Matrix< double > QN
std::vector< std::vector< Matrix< double > > > TNt
Matrix< double > ANfcr
Matrix< double > ANCI
The plan of sim_runlength_plan: what the run should have been.