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"
41
42namespace line {
43namespace ldes {
44
45/**
46 * Port of `SolverLDES.listValidMethods`.
47 *
48 * Two names, and only one engine behind them: 'parallel' asks the engine for
49 * INDEPENDENT REPLICATIONS and the mean over them, which the client turns into
50 * `--replications` (the caller's count when set, 8 otherwise). It is not a
51 * second simulator, which is why the list is this short in all four codebases.
52 * 'para' is its short spelling, as in SolverSSA.
53 */
54inline std::vector<std::string> list_valid_methods() {
55 // the names and their order are this solver's row of the method registry (doc/line-methods.json)
56 return ::line::method_registry::methods("LDES");
57}
58
59/**
60 * The knobs of one LDES run.
61 *
62 * `samples` is an EVENT BUDGET (service completions), not a sample size in the
63 * statistical sense, and `events` is the same budget under the name the newer
64 * API uses: when set it overrides `samples`, as it does in both clients.
65 */
67 std::size_t samples = 200000; ///< -s, service-completion budget
68 std::size_t events = 0; ///< 0 = not given; overrides `samples` when set
69 long seed = 23000; ///< --seed; -1 requests a random stream
70 std::string method = "default";
71
72 // ---- convergence-based stopping ----------------------------------------
73 bool cnvgon = false; ///< --cnvgon
74 double cnvgtol = 0.05; ///< --cnvgtol
75 int cnvgbatch = 20; ///< --cnvgbatch, batches before the first check
76 int cnvgchk = 0; ///< --cnvgchk, events between checks; 0 = samples/50
77
78 // ---- warmup (transient) filter -----------------------------------------
79 std::string tranfilter = "mser5"; ///< --tranfilter: mser5 | fixed | none
80 int mserbatch = 5; ///< --mserbatch, MSER batch size
81 double warmupfrac = 0.2; ///< --warmupfrac, only for tranfilter=fixed
82 /// --tranobs, equally spaced transient observation points over [t0,t1]; fixes the time grid only.
83 /// 0 = not given: the engine default, 1000 in both the native and the JAR engine.
84 int tranobs = 0;
85
86 // ---- confidence intervals ----------------------------------------------
87 std::string cimethod = "obm"; ///< --cimethod: obm | bm | spectral | none
88 double obmoverlap = 0.5; ///< --obmoverlap; 0 reduces OBM to plain batch means
89 int ciminbatch = 10; ///< --ciminbatch
90 int ciminobs = 100; ///< --ciminobs, below which no CI is reported
91 /**
92 * Confidence level of the reported half-widths.
93 *
94 * `LDESOptions` sets 0.95 where the shared `SolverOptions` leaves it at 0
95 * (disabled), so the LDES default is intervals ON. It is not a CLI flag:
96 * the engine reads it from the options object, so the subprocess client
97 * has no way to vary it and the native engine keeps the same default.
98 */
99 double confint = 0.95;
100 double spectral_low_freq_frac = 0.25; ///< --spectrallowfreqfrac
101 /**
102 * `options.config.runLengthPlan`: ask for the run length this run SHOULD
103 * have had, for a target relative precision.
104 *
105 * Non-positive leaves the plan uncomputed. The half-widths above pin the
106 * ASYMPTOTIC variance of each estimator -- not its stationary variance,
107 * which on M/M/1 differs from it by a factor blowing up like (1-rho)^-2 --
108 * and `sim_runlength_plan` turns that into the run length that reaches this
109 * precision. The plan lands in `LdesResult::run_length_plan`.
110 */
112
113 // ---- discrete time ------------------------------------------------------
114 bool slotted = false; ///< --slotted, run on the slot lattice
115 double slot_length = 1.0; ///< --slotlength
116
117 // ---- busy periods -------------------------------------------------------
118 /**
119 * --busyperiod: highest busy period order measured, 0 disabling the
120 * measurement. A busy period of order n for a set of stations runs from the
121 * instant an arrival raises the jobs it holds to n up to the instant it
122 * falls back below n (Daduna, J. ACM 35(3), 1988). When positive the orders
123 * 1..busy_period_orders are measured for every station, for every
124 * station-class pair, and for every set in `busy_period_subnets`.
125 */
127 /** --busyperiod-subnet, zero-based station indexes, one flag per set. */
128 std::vector<std::vector<std::size_t>> busy_period_subnets;
129
130 // ---- replication --------------------------------------------------------
131 int replications = 0; ///< --replications; 0 = not given (one path)
132 int numthreads = 0; ///< --numthreads; 0 = not given
133
134 // ---- horizon and budget --------------------------------------------------
135 bool has_timespan = false; ///< true when [t0,t1] was set: a TRANSIENT run
136 double t0 = 0.0;
137 double t1 = 0.0;
138 /** --maxtime, a COOPERATIVE wall-clock budget the event loop polls. */
139 double timeout = std::numeric_limits<double>::infinity();
140
141 /**
142 * --initsol, the warm-start placement as a STATION-MAJOR vector
143 * [st0_cl0, st0_cl1, ..., stM-1_clK-1]. Empty = the engine's own default
144 * (closed jobs at their reference stations).
145 */
146 std::vector<double> init_sol;
147
148 /**
149 * --initjobs, the JOB-LEVEL initial state, one row per job:
150 * [station, class, remaining, elapsed, qage, sage, order, id].
151 *
152 * Unlike `init_sol`, which gives counts only and so restarts every job with
153 * a fresh service draw, each row carries the job's residual work, so a run
154 * continues exactly where an earlier one stopped (`LdesResult::finalJobs` is
155 * written in this layout). remaining > 0 is used as given, -1 draws a full
156 * service time and -2 draws the residual given the attained service
157 * `elapsed`. qage and sage are the time already spent at the station and in
158 * the system; `order` ranks the jobs of a station (the first ones take the
159 * servers) and `id` (optional, -1 untagged) is echoed in `finalJobs` and
160 * `jobDepartures`. Takes precedence over `init_sol`. Supported at FCFS
161 * queues, the PS family and Delays.
162 */
164 /** --trackjobs: fill `finalJobs` and `jobDepartures`; implied by `init_jobs`. */
165 bool track_jobs = false;
166
167 /**
168 * --export-histogram and --trajectory: the joint-state residence-time law
169 * and the state path.
170 *
171 * A TRANSIENT run fills both unconditionally, because it has to walk the
172 * state to produce QNt at all. In STEADY STATE they are opt-in: the
173 * histogram costs a map lookup per event and the trajectory grows one row
174 * per event, which on a million-completion run is a million rows nobody
175 * asked for.
176 */
177 bool export_histogram = false;
178 bool export_trajectory = false;
179
180 /**
181 * --respt-samples: every per-visit response time, not just its mean.
182 *
183 * It is the input to an EMPIRICAL CDF, which a mean cannot stand in for, and
184 * it costs one push per completion, so it is opt-in like the two above.
185 */
186 bool export_respt = false;
187
188 /**
189 * Base URL of an LDES REST server (the imperialqore/ldes container). When
190 * set the model is POSTed to `<url>/api/v1/solve` instead of being handed to
191 * a local binary; the wire format is the same on both sides, so a fixed
192 * seed gives the same numbers.
193 */
194 std::string rest_url;
195
196 bool verbose = false; ///< echo the resolved command line before running it
197};
198
199/** Per-cache hit/miss/latency, as the `cacheMetrics` block carries them. */
201 Matrix<double> hit; ///< (1 x nclasses) hit probability
202 Matrix<double> delayed; ///< (1 x nclasses) delayed-hit probability
203 Matrix<double> miss; ///< (1 x nclasses) miss probability
204 Matrix<double> latency; ///< (1 x nclasses) expected retrieval latency
205 Matrix<double> hitList; ///< (nclasses x nlists) hit probability by list
206 Matrix<double> itemProb; ///< (nitems x nlists+1) item position law
207 Matrix<double> listCost; ///< (1 x nlists) mean storage cost held per list
208};
209
210/**
211 * One `ldes-result` document, parsed.
212 *
213 * A metric the engine did not report stays EMPTY rather than becoming a matrix
214 * of zeros: zero is a measurement and absence is not, and the tables below
215 * distinguish them.
216 */
218 // ---- dimensions and labels ----------------------------------------------
219 std::size_t nstations = 0;
220
221 /** The jobs present at the end of the run, in the `init_jobs` layout (track_jobs). */
223 /** [id, station, class, time] of every departure of a tagged init_jobs job (track_jobs). */
225 std::size_t nclasses = 0;
226 std::size_t nchains = 0;
227 std::size_t nregions = 0;
228 std::vector<std::string> station_names;
229 std::vector<std::string> class_names;
230
231 // ---- means, (nstations x nclasses) --------------------------------------
233 Matrix<double> CN, XN; ///< (1 x nclasses), per-class visits and system tput
234 Matrix<double> DropRateJoin; ///< quorum-Join sibling drops, Join rows only
235
236 // ---- fork-join sections, (npairs x nclasses): the ldes-result "forkJoin" block ----------------------------
237 /** Fork and Join node NAMES of each pair, ordered by fork node then join node; empty without a pair. */
238 std::vector<std::string> fj_forks, fj_joins;
239 Matrix<double> FJQLen, FJRespT, FJTput; ///< getAvgFJ, FJQLen = FJTput * FJRespT
240
241 // ---- confidence intervals and relative precision -------------------------
243 /**
244 * `options.config.runLengthPlan`: the run length the caller would need for
245 * the precision they asked for, from the half-widths above. Empty unless
246 * `LdesOptions::run_length_plan_precision` is positive.
247 */
252
253 // ---- finite capacity regions, (nregions x nclasses) ----------------------
256
257 // ---- impatience, (nstations x nclasses) ----------------------------------
261
262 /** Per-cache metrics, keyed by the Cache NODE name. */
263 std::map<std::string, LdesCacheMetrics> cache_metrics;
264
265 /**
266 * The exact joint-state residence-time histogram (--export-histogram).
267 *
268 * `histogram_space` is (nstates x nstations*nclasses) in the aggregate
269 * layout `ctmc_state_space_aggr` builds and `histogram_time` the residence
270 * time of each row, so E[r] = sum_s (t_s / sum t) r(state_s) is exact also
271 * for a NONLINEAR reward. `traj_*` is the same encoding along the sampled
272 * path, downsampled, and is what the transient reward reads.
273 */
278
279 /**
280 * One measured busy period target (--busyperiod): a station, a
281 * station-class pair, or a declared station set. `mean` and `count` are
282 * indexed by order 1..K, and a mean whose count is zero is not an
283 * observation. `job_class` is -1 when the target aggregates every class.
284 */
286 std::string name;
287 std::vector<std::size_t> stations; ///< station indexes, zero-based
288 int job_class = -1;
289 std::vector<double> mean;
290 std::vector<double> count;
291 };
292 std::vector<BusyPeriodTarget> busy_periods;
293
294 // ---- transient trajectory (--timespan + --trajectory) --------------------
295 std::vector<double> t; ///< the time vector, empty on a steady-state run
296 /**
297 * [STATION][class] -> (npoints x 2), columns [value, time].
298 *
299 * THE OUTER INDEX IS THE STATION, not the stateful node: the engine builds
300 * these as `result.QNt = new Matrix[numStations][numClasses]` and fills them
301 * at `serviceStation` (`Solver_ssj.getTransientLDESResultBody`). The two
302 * index spaces coincide only when every stateful node is a station, so on a
303 * model holding a Router, a Cache or a Place the stateful index selects
304 * another station's series or runs off the end. That is the defect recorded
305 * as BUG-95 and fixed in the MATLAB and Python clients.
306 *
307 * THE VALUES ARE INTERVAL TIME-AVERAGES, not the integer states the path
308 * visits: each entry is (integral of the queue length over the sampling
309 * interval) / (interval length). They are a trajectory of MEANS, so they
310 * must not be compared against a state to estimate a probability -- use
311 * `histogram_space` / `histogram_time` for that.
312 */
313 std::vector<std::vector<Matrix<double>>> QNt, UNt, TNt;
314 /** [station][class] -> the per-job response times the engine recorded. */
315 std::vector<std::vector<std::vector<double>>> respTimeSamples;
316
317 // ---- provenance -----------------------------------------------------------
318 std::string method = "default";
319 double runtime = 0.0;
320 bool converged = false;
321 /** convergence | max_events | max_sim_events | max_time. */
322 std::string stopping_reason;
323 /// ldes-result "warnings": what the engine warned on its own console, e.g. a
324 /// parallel pool that timed out and averaged fewer replications than requested
325 std::vector<std::string> warnings;
328 /** True when the HARD subprocess bound fired, not the cooperative one. */
329 bool timed_out = false;
330 /** "native" or "jar": which runner produced these numbers. */
331 std::string engine;
332};
333
334} // namespace ldes
335} // namespace line
336
337#endif // LINE_SOLVERS_WRAPPERS_LDES_LDES_OPTIONS_H
Dense matrix and non-owning view.
The method names of each solver, in listing order.
std::vector< std::string > list_valid_methods()
Port of SolverLDES.listValidMethods.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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
Matrix< double > init_jobs
–initjobs, the JOB-LEVEL initial state, one row per job: [station, class, remaining,...
int tranobs
–tranobs, equally spaced transient observation points over [t0,t1]; fixes the time grid only.
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)
bool track_jobs
–trackjobs: fill finalJobs and jobDepartures; implied by init_jobs.
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
std::vector< std::string > fj_forks
Fork and Join node NAMES of each pair, ordered by fork node then join node; empty without a pair.
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 > FJTput
getAvgFJ, FJQLen = FJTput * FJRespT
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 > FJQLen
Matrix< double > UNfcr
Matrix< double > FJRespT
Matrix< double > WeightNfcr
std::vector< std::string > warnings
ldes-result "warnings": what the engine warned on its own console, e.g.
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
std::vector< std::string > fj_joins
Matrix< double > jobDepartures
[id, station, class, time] of every departure of a tagged init_jobs job (track_jobs).
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 > finalJobs
The jobs present at the end of the run, in the init_jobs layout (track_jobs).
Matrix< double > ANfcr
Matrix< double > ANCI
The plan of sim_runlength_plan: what the run should have been.