LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ldes_samplepath.h
Go to the documentation of this file.
1// Copyright (c) 2012-2026, Imperial College London
2// All rights reserved.
3
4/**
5 * @file ldes_samplepath.h
6 * @brief The exact sample-path recorder of the native LDES engine (`--sample-path N`).
7 *
8 * Transcribes the reference's `SamplePathRecorder` and the `sp*` block of
9 * `Solver_ssj`. The engine observes itself at every event boundary: it takes a
10 * snapshot of the jobs held at each station, diffs it against the previous one
11 * by job identity, and turns the difference into SolverSSA-style events plus one
12 * row of LINE State per stateful node. Phase changes that fall between two real
13 * events (the stages of an Erlang, the phases of a walked PH or MAP) are replayed
14 * from the phase SCRIPT of each drawn interval and recorded as virtual PHASE rows.
15 *
16 * NO EXTRA DRAWS: recording consumes nothing from any simulation stream. A walked
17 * family reports the phases it visited through `Sampler::set_trace`, which reads
18 * the draws the walk takes anyway; an Erlang (drawn by convolution, which gives
19 * no stage epochs) is reconstructed from k-1 sorted uniforms of the recorder's
20 * own java.util.Random(seed + 900001).
21 *
22 * Row convention (= SolverSSA): row k is the state OCCUPIED UNTIL t[k] (the left
23 * limit); events tagged step k happen at t[k].
24 */
25
26#ifndef LINE_SOLVERS_LDES_SAMPLEPATH_H
27#define LINE_SOLVERS_LDES_SAMPLEPATH_H
28
29#include <algorithm>
30#include <cmath>
31#include <cstddef>
32#include <cstdint>
33#include <deque>
34#include <limits>
35#include <map>
36#include <memory>
37#include <random>
38#include <string>
39#include <vector>
40
42#include "line/util/rng_ssj.h"
43
44namespace line {
45namespace ldes {
46namespace engine {
47namespace sp {
48
49/** Seed offset of the recorder's own generator (Erlang stage reconstruction). */
50constexpr long long SEED_OFFSET = 900001LL;
51
52/** Event wire codes (= MATLAB EventType values). */
53constexpr double EV_ARV = 1.0;
54constexpr double EV_DEP = 2.0;
55constexpr double EV_PHASE = 3.0;
56constexpr double EV_START = 15.0;
57constexpr double EV_DROP = 17.0;
58
59/** Java's Math.ulp for a double. */
60inline double ulp(double v) {
61 v = std::fabs(v);
62 if (std::isnan(v)) return v;
63 if (std::isinf(v)) return std::numeric_limits<double>::infinity();
64 if (v == std::numeric_limits<double>::max()) return v - std::nextafter(v, 0.0);
65 return std::nextafter(v, std::numeric_limits<double>::infinity()) - v;
66}
67
68/**
69 * True for a family whose draw is a phase WALK the sampler can trace: the
70 * reference's MapSampler / map_sample families, plus at a Source the BMAP and
71 * marked-stationary walks. An Erlang the sampler recovered for convolution
72 * (`erlang_k() > 0`) gives no stage epochs and is reconstructed instead.
73 */
74inline bool walked(const Sampler& s, bool arrival) {
76 switch (s.type()) {
77 case ProcessType::MAP:
78 case ProcessType::MMPP2:
79 case ProcessType::PH:
80 case ProcessType::APH:
81 case ProcessType::HYPEREXP:
82 case ProcessType::COXIAN:
83 case ProcessType::COX2:
84 return true;
85 case ProcessType::ERLANG:
86 return s.erlang_k() == 0;
87 case ProcessType::BMAP:
88 return arrival;
89 default:
90 return arrival && lang::process_is_marked_stationary(s.type());
91 }
92}
93
94/**
95 * Phases the export reports for a law (`spPhaseCount`), or -1 when the law is
96 * time-inhomogeneous and has no fixed phase space.
97 */
98inline int phase_count(const Sampler& s, bool disabled, bool arrival) {
100 const ProcessType pt = s.type();
101 if (disabled || pt == ProcessType::DISABLED) return 1;
102 if (pt == ProcessType::MAPT || pt == ProcessType::PHT || lang::process_is_marked_schedule(pt)) return -1;
103 if (pt == ProcessType::REPLAYER) return 1;
104 if (pt == ProcessType::ERLANG && s.erlang_k() > 0) return std::max(1, s.erlang_k());
105 const bool w = pt == ProcessType::PH || pt == ProcessType::APH || pt == ProcessType::HYPEREXP ||
106 pt == ProcessType::COXIAN || pt == ProcessType::COX2 || pt == ProcessType::MAP ||
107 pt == ProcessType::MMPP2 ||
108 (arrival && (pt == ProcessType::BMAP || lang::process_is_marked_stationary(pt)));
109 if (w && s.map_order() >= 1) return std::max(1, static_cast<int>(s.map_order()));
110 return 1;
111}
112
113/** Phase script of one drawn interval: segment j ends at cum[j] and is spent in ph[j] (0-based). */
114struct Script {
115 std::vector<double> cum;
116 std::vector<int> ph;
117
118 static std::shared_ptr<Script> single(double total) {
119 std::shared_ptr<Script> s = std::make_shared<Script>();
120 s->cum.assign(1, total);
121 s->ph.assign(1, 0);
122 return s;
123 }
124
125 /** Segment holding attained amount `a`, never below `from`. */
126 int seg_at(double a, int from) const {
127 int s = std::max(0, from);
128 const int n = static_cast<int>(cum.size());
129 while (s < n - 1 && cum[static_cast<std::size_t>(s)] <= a) ++s;
130 return s;
131 }
132};
133typedef std::shared_ptr<Script> ScriptP;
134
135/** One job held at a detailed station in a snapshot. */
136struct Job {
137 std::uint64_t key = 0;
138 std::size_t cls = 0;
139 bool in_service = false;
140 /** Attained amount at time t0 and its rate of growth (1 in wall time, the PS share otherwise). */
141 double t0 = 0.0;
142 double att0 = 0.0;
143 double rate = 0.0;
145 int seg = 0;
146
147 int phase() const { return script ? script->ph[static_cast<std::size_t>(seg)] : 0; }
148
149 /** Epoch at which the current segment ends, +Inf if it is the last. */
150 double next_boundary() const {
151 if (!in_service || !script || seg >= static_cast<int>(script->cum.size()) - 1 ||
152 !(rate > 0.0))
153 return std::numeric_limits<double>::infinity();
154 return t0 + (script->cum[static_cast<std::size_t>(seg)] - att0) / rate;
155 }
156};
157
158/** Jobs held everywhere at one instant, indexed by station. */
159struct Snap {
160 /** Per station: waiting jobs head first, then the jobs in service; meaningful when detail[i]. */
161 std::vector<std::vector<Job>> jobs;
162 /** Per station and class: job count. */
163 std::vector<std::vector<int>> counts;
164 /** Per station (sources only) and class: interarrival script in progress, start, segment. */
165 std::vector<std::vector<ScriptP>> src;
166 std::vector<std::vector<double>> src_start;
167 std::vector<std::vector<int>> src_seg;
168};
169
170/** One recorded event without its step and time: {type, node, class, jobId, phase}. */
171struct Ev {
172 double v[5];
173};
174
175/** Transcribes `SamplePathRecorder`: the rows and events of the path. */
176class Recorder {
177public:
178 Recorder() = default;
179 Recorder(std::size_t limit, const std::vector<std::size_t>& stateful_nodes, std::size_t nclasses,
180 const std::vector<std::string>& refusal, const std::vector<bool>& refused)
181 : limit_(limit), nodes_(stateful_nodes), R_(nclasses), refusal_(refusal),
182 refused_(refused), state_rows_(stateful_nodes.size()), aggr_rows_(stateful_nodes.size()) {}
183
184 bool is_full() const { return t_.size() >= limit_; }
185 std::size_t size() const { return t_.size(); }
186
187 void add_row(double time, const std::vector<std::vector<double>>& state,
188 const std::vector<std::vector<double>>& aggr, const std::vector<Ev>& evs) {
189 const double step = static_cast<double>(t_.size());
190 t_.push_back(time);
191 for (std::size_t i = 0; i < nodes_.size(); ++i) {
192 state_rows_[i].push_back(refused_[i] ? std::vector<double>() : state[i]);
193 aggr_rows_[i].push_back(aggr[i]);
194 }
195 for (const Ev& e : evs) {
196 std::vector<double> row(7);
197 row[0] = step;
198 row[1] = time;
199 for (int c = 0; c < 5; ++c) row[static_cast<std::size_t>(c) + 2] = e.v[c];
200 events_.push_back(row);
201 }
202 }
203
204 void count_unbound_script() { ++unbound_; }
205 long long unbound_scripts() const { return unbound_; }
206
207 const std::vector<double>& times() const { return t_; }
208 const std::vector<std::vector<double>>& events() const { return events_; }
209 std::size_t num_stateful() const { return nodes_.size(); }
210 std::size_t stateful_node(std::size_t isf) const { return nodes_[isf]; }
211 bool refused(std::size_t isf) const { return refused_[isf]; }
212 const std::string& refusal(std::size_t isf) const { return refusal_[isf]; }
213
214 /** The detailed rows of node isf, LEFT-padded with zeros to the widest row. */
215 std::vector<std::vector<double>> state(std::size_t isf) const {
216 const std::vector<std::vector<double>>& rows = state_rows_[isf];
217 std::size_t w = 0;
218 for (const std::vector<double>& r : rows) w = std::max(w, r.size());
219 std::vector<std::vector<double>> out(rows.size(), std::vector<double>(w, 0.0));
220 for (std::size_t k = 0; k < rows.size(); ++k)
221 std::copy(rows[k].begin(), rows[k].end(), out[k].begin() + static_cast<std::ptrdiff_t>(w - rows[k].size()));
222 return out;
223 }
224 std::vector<std::vector<double>> aggr(std::size_t isf) const {
225 std::vector<std::vector<double>> out(aggr_rows_[isf].size(), std::vector<double>(R_, 0.0));
226 for (std::size_t k = 0; k < out.size(); ++k)
227 for (std::size_t r = 0; r < R_ && r < aggr_rows_[isf][k].size(); ++r)
228 out[k][r] = aggr_rows_[isf][k][r];
229 return out;
230 }
231
232private:
233 std::size_t limit_ = 0;
234 std::vector<std::size_t> nodes_;
235 std::size_t R_ = 0;
236 std::vector<std::string> refusal_;
237 std::vector<bool> refused_;
238 std::vector<double> t_;
239 std::vector<std::vector<double>> events_;
240 std::vector<std::vector<std::vector<double>>> state_rows_, aggr_rows_;
241 long long unbound_ = 0;
242};
243
244/**
245 * The recorder's bookkeeping between two boundaries: scripts drawn and not yet
246 * bound to a job, source departures and drops, the persistent job ids, and the
247 * snapshot held since the last boundary. The engine fills the snapshots (it owns
248 * the stations); everything that does not read station internals lives here.
249 */
250struct Tracker {
251 // ---- configuration, set by the engine -----------------------------------
252 std::size_t M = 0, R = 0;
253 std::vector<long long> node_of; ///< per station: 0-based node index
254 std::vector<bool> is_source; ///< per station
255 std::vector<bool> is_service; ///< per station: a Queue or a Delay
256 std::vector<bool> detail; ///< per station: detailed rows encoded
257 std::vector<bool> fcfs_buffer; ///< per station: the row carries a buffer
258 std::vector<std::vector<int>> phases; ///< per station and class
259 std::vector<std::vector<int>> map_vars; ///< per station and class
260 std::vector<std::vector<double>> map_var_init;
261 std::vector<long long> station_of_sf; ///< per stateful node: station or -1
262 std::vector<std::vector<double>> const_row; ///< per stateful node that is no station
263
264 // ---- run state ------------------------------------------------------------
266 bool done = false;
267 bool have_held = false;
271 bool traced = false;
272 struct Pending {
273 std::size_t q, k;
275 };
276 std::vector<Pending> pending;
277 std::vector<std::vector<ScriptP>> src_script; ///< [station][class]
278 std::vector<std::vector<double>> src_start;
279 std::vector<std::pair<std::size_t, std::size_t>> src_deps, drops;
280 std::map<std::uint64_t, ScriptP> scripts;
281 std::map<std::uint64_t, long long> ids;
282 std::deque<long long> id_fifo;
283 long long next_id = 0;
284 /** True when the recorder stopped the run (stoppingReason "sample_path"). */
285 bool stopped_run = false;
286 double stop_time = 0.0;
287
288 void seed(long long s) {
289 if (s > 0) {
291 } else {
292 std::random_device rd;
293 rng.set_seed((static_cast<long long>(rd()) << 32) ^ static_cast<long long>(rd()));
294 }
295 }
296
297 /** Hands the scratch trace to a recording sampler call. */
299 scratch.clear();
300 traced = true;
301 return &scratch;
302 }
303
304 /** Script of the draw that just returned `value`: its walk, an Erlang reconstruction, or one segment. */
305 ScriptP script_of(double value, int erlang_k) {
306 const bool tr = traced;
307 traced = false;
308 if (tr && scratch.size() > 0) {
309 const double tot = scratch.total();
310 if (std::fabs(tot - value) <= 1e-9 * std::fabs(value) + 8.0 * ulp(value)) {
311 const std::size_t n = scratch.size();
312 ScriptP s = std::make_shared<Script>();
313 s->cum.assign(n, 0.0);
314 s->ph.assign(n, 0);
315 double acc = 0.0;
316 for (std::size_t i = 0; i < n; ++i) {
317 acc += scratch.dt[i];
318 s->cum[i] = acc;
319 s->ph[i] = scratch.phase[i];
320 }
321 s->cum[n - 1] = value;
322 return s;
323 }
324 return Script::single(value);
325 }
326 if (erlang_k > 1) {
327 const std::size_t k = static_cast<std::size_t>(erlang_k);
328 std::vector<double> u(k - 1);
329 for (std::size_t i = 0; i + 1 < k; ++i) u[i] = rng.next_double();
330 std::sort(u.begin(), u.end());
331 ScriptP s = std::make_shared<Script>();
332 s->cum.assign(k, 0.0);
333 s->ph.assign(k, 0);
334 for (std::size_t i = 0; i + 1 < k; ++i) {
335 s->cum[i] = u[i] * value;
336 s->ph[i] = static_cast<int>(i);
337 }
338 s->cum[k - 1] = value;
339 s->ph[k - 1] = static_cast<int>(k - 1);
340 return s;
341 }
342 return Script::single(value);
343 }
344
345 void capture_service(std::size_t q, std::size_t k, double value, int erlang_k) {
346 Pending p;
347 p.q = q;
348 p.k = k;
349 p.s = script_of(value, erlang_k);
350 pending.push_back(p);
351 }
352 void capture_arrival(std::size_t st, std::size_t k, double value, int erlang_k, double now) {
353 src_script[st][k] = script_of(value, erlang_k);
354 src_start[st][k] = now;
355 }
356 void source_departure(std::size_t st, std::size_t k) { src_deps.push_back(std::make_pair(st, k)); }
357 void drop(std::size_t q, std::size_t k) { drops.push_back(std::make_pair(q, k)); }
358
359 /** Binds a drawn script of (q, k) to a job whose drawn total is `expected`. */
360 ScriptP bind(std::size_t q, std::size_t k, double expected, double now) {
361 long best = -1;
362 double best_err = std::numeric_limits<double>::infinity();
363 for (std::size_t i = 0; i < pending.size(); ++i) {
364 if (pending[i].q != q || pending[i].k != k) continue;
365 const Script& s = *pending[i].s;
366 const double err = std::fabs(s.cum.back() - expected);
367 if (err < best_err) {
368 best_err = err;
369 best = static_cast<long>(i);
370 }
371 }
372 const double tol = 1e-9 * std::fabs(expected) + 8.0 * ulp(std::fabs(now) + std::fabs(expected));
373 if (best >= 0 && best_err <= tol) {
374 ScriptP s = pending[static_cast<std::size_t>(best)].s;
375 pending.erase(pending.begin() + best);
376 const std::size_t n = s->cum.size();
377 s->cum[n - 1] = std::max(expected, n > 1 ? s->cum[n - 2] : 0.0);
378 return s;
379 }
381 return Script::single(expected);
382 }
383
384 /** Script already bound to `key`, or a fresh binding (stored under `key`). */
385 ScriptP script_for(std::uint64_t key, std::size_t q, std::size_t k, double expected, double now) {
386 std::map<std::uint64_t, ScriptP>::const_iterator it = scripts.find(key);
387 if (it != scripts.end()) return it->second;
388 ScriptP s = bind(q, k, expected, now);
389 scripts[key] = s;
390 return s;
391 }
392
393 /** Source part of a snapshot. */
394 void snap_sources(Snap& s, double now, const Snap* prev) const {
395 s.src.assign(M, std::vector<ScriptP>(R));
396 s.src_start.assign(M, std::vector<double>(R, 0.0));
397 s.src_seg.assign(M, std::vector<int>(R, 0));
398 for (std::size_t i = 0; i < M; ++i) {
399 if (!is_source[i]) continue;
400 for (std::size_t k = 0; k < R; ++k) {
401 const ScriptP& sc = src_script[i][k];
402 s.src[i][k] = sc;
403 s.src_start[i][k] = src_start[i][k];
404 if (sc) {
405 const int from = (prev && prev->src[i][k] == sc) ? prev->src_seg[i][k] : 0;
406 s.src_seg[i][k] = sc->seg_at(now - src_start[i][k], from);
407 }
408 }
409 }
410 }
411
412 /**
413 * Encodes snapshot `s` as LINE State rows and per-class counts. `sampler_phase(i, k)`
414 * is the service sampler's walk phase (the reference's `getPhase()`), read for the
415 * MAP local variable of an idle class.
416 */
417 template <class PhaseFn>
418 void encode(const Snap& s, std::vector<std::vector<double>>& state,
419 std::vector<std::vector<double>>& aggr, PhaseFn sampler_phase) const {
420 const std::size_t nsf = station_of_sf.size();
421 state.assign(nsf, std::vector<double>());
422 aggr.assign(nsf, std::vector<double>());
423 const double inf = std::numeric_limits<double>::infinity();
424 for (std::size_t isf = 0; isf < nsf; ++isf) {
425 const long long ist = station_of_sf[isf];
426 std::vector<double> ag(R, 0.0);
427 std::vector<double> row;
428 if (ist >= 0 && is_source[static_cast<std::size_t>(ist)]) {
429 const std::size_t i = static_cast<std::size_t>(ist);
430 std::size_t w = 1;
431 for (std::size_t k = 0; k < R; ++k) {
432 w += static_cast<std::size_t>(phases[i][k]);
433 ag[k] = inf;
434 }
435 row.assign(w, 0.0);
436 row[0] = inf;
437 std::size_t off = 1;
438 for (std::size_t k = 0; k < R; ++k) {
439 const ScriptP& sc = s.src[i][k];
440 if (sc) {
441 const int p = std::min(sc->ph[static_cast<std::size_t>(s.src_seg[i][k])], phases[i][k] - 1);
442 row[off + static_cast<std::size_t>(p)] = 1.0;
443 }
444 off += static_cast<std::size_t>(phases[i][k]);
445 }
446 } else if (ist >= 0 && is_service[static_cast<std::size_t>(ist)]) {
447 const std::size_t i = static_cast<std::size_t>(ist);
448 for (std::size_t k = 0; k < R; ++k) ag[k] = s.counts[i][k];
449 if (detail[i]) {
450 std::size_t nsrv = 0;
451 std::vector<std::size_t> base(R, 0);
452 for (std::size_t k = 0; k < R; ++k) {
453 base[k] = nsrv;
454 nsrv += static_cast<std::size_t>(phases[i][k]);
455 }
456 std::vector<double> srv(nsrv, 0.0);
457 std::vector<double> buf;
458 std::vector<double> map_var(R, 0.0);
459 std::vector<bool> map_busy(R, false);
460 for (const Job& j : s.jobs[i]) {
461 if (j.in_service) {
462 const int p = std::min(j.phase(), phases[i][j.cls] - 1);
463 srv[base[j.cls] + static_cast<std::size_t>(p)] += 1.0;
464 map_var[j.cls] = p + 1;
465 map_busy[j.cls] = true;
466 } else {
467 buf.push_back(static_cast<double>(j.cls + 1));
468 }
469 }
470 const std::size_t nbuf = fcfs_buffer[i] ? std::max<std::size_t>(1, buf.size()) : 0;
471 std::size_t nvar = 0;
472 for (std::size_t k = 0; k < R; ++k) nvar += static_cast<std::size_t>(map_vars[i][k]);
473 row.assign(nbuf + nsrv + nvar, 0.0);
474 for (std::size_t b = 0; b < buf.size(); ++b) row[nbuf - 1 - b] = buf[b];
475 std::copy(srv.begin(), srv.end(), row.begin() + static_cast<std::ptrdiff_t>(nbuf));
476 std::size_t off = nbuf + nsrv;
477 for (std::size_t k = 0; k < R; ++k) {
478 if (map_vars[i][k] > 0) {
479 double v = map_var[k];
480 if (!map_busy[k]) {
481 const int ph = sampler_phase(i, k);
482 v = (ph >= 0) ? ph + 1 : map_var_init[i][k];
483 }
484 row[off++] = v;
485 }
486 }
487 }
488 } else {
489 row = const_row[isf];
490 }
491 state[isf] = row;
492 aggr[isf] = ag;
493 }
494 }
495
496 long long take_id() {
497 if (id_fifo.empty()) return next_id++;
498 const long long id = id_fifo.front();
499 id_fifo.pop_front();
500 return (id < 0) ? next_id++ : id;
501 }
502
503 /** Events turning snapshot `a` into `b`, in the order DEP, ARV, DROP, START, PHASE. */
504 std::vector<Ev> diff(const Snap& a, const Snap& b) {
505 std::vector<Ev> deps, arvs, drps, starts, phs;
506 auto mk = [](double t, double n, double c, double id, double ph) {
507 Ev e;
508 e.v[0] = t;
509 e.v[1] = n;
510 e.v[2] = c;
511 e.v[3] = id;
512 e.v[4] = ph;
513 return e;
514 };
515 for (std::size_t q = 0; q < M; ++q) {
516 if (!is_service[q]) continue;
517 const double node = static_cast<double>(node_of[q]);
518 if (!detail[q]) {
519 for (std::size_t k = 0; k < R; ++k)
520 for (int d = b.counts[q][k]; d < a.counts[q][k]; ++d) {
521 deps.push_back(mk(EV_DEP, node, static_cast<double>(k), -1.0, 0.0));
522 id_fifo.push_back(-1);
523 }
524 continue;
525 }
526 std::map<std::uint64_t, const Job*> bm;
527 for (const Job& j : b.jobs[q]) bm[j.key] = &j;
528 for (const Job& j : a.jobs[q]) {
529 if (bm.count(j.key)) continue;
530 long long jid = -1;
531 std::map<std::uint64_t, long long>::iterator it = ids.find(j.key);
532 if (it != ids.end()) {
533 jid = it->second;
534 ids.erase(it);
535 }
536 scripts.erase(j.key);
537 deps.push_back(mk(EV_DEP, node, static_cast<double>(j.cls), static_cast<double>(jid),
538 j.in_service ? j.phase() + 1.0 : 0.0));
539 id_fifo.push_back(jid);
540 }
541 }
542 for (const std::pair<std::size_t, std::size_t>& sd : src_deps) {
543 const long long id = next_id++;
544 deps.push_back(mk(EV_DEP, static_cast<double>(node_of[sd.first]), static_cast<double>(sd.second),
545 static_cast<double>(id), 0.0));
546 id_fifo.push_back(id);
547 }
548 for (std::size_t q = 0; q < M; ++q) {
549 if (!is_service[q]) continue;
550 const double node = static_cast<double>(node_of[q]);
551 if (!detail[q]) {
552 for (std::size_t k = 0; k < R; ++k)
553 for (int d = a.counts[q][k]; d < b.counts[q][k]; ++d) {
554 take_id();
555 arvs.push_back(mk(EV_ARV, node, static_cast<double>(k), -1.0, 0.0));
556 }
557 continue;
558 }
559 std::map<std::uint64_t, const Job*> am;
560 for (const Job& j : a.jobs[q]) am[j.key] = &j;
561 for (const Job& j : b.jobs[q]) {
562 std::map<std::uint64_t, const Job*>::const_iterator it = am.find(j.key);
563 const Job* old = (it == am.end()) ? nullptr : it->second;
564 if (!old) {
565 const long long id = take_id();
566 ids[j.key] = id;
567 arvs.push_back(mk(EV_ARV, node, static_cast<double>(j.cls), static_cast<double>(id), 0.0));
568 }
569 const double id = static_cast<double>(ids[j.key]);
570 if (j.in_service && (!old || !old->in_service))
571 starts.push_back(mk(EV_START, node, static_cast<double>(j.cls), id, j.phase() + 1.0));
572 else if (j.in_service && old->phase() != j.phase())
573 phs.push_back(mk(EV_PHASE, node, static_cast<double>(j.cls), id, j.phase() + 1.0));
574 }
575 }
576 for (const std::pair<std::size_t, std::size_t>& dr : drops)
577 drps.push_back(mk(EV_DROP, static_cast<double>(node_of[dr.first]), static_cast<double>(dr.second),
578 static_cast<double>(take_id()), 0.0));
579 for (std::size_t i = 0; i < M; ++i) {
580 if (!is_source[i]) continue;
581 for (std::size_t k = 0; k < R; ++k) {
582 const ScriptP& sc = b.src[i][k];
583 if (sc && a.src[i][k] == sc && a.src_seg[i][k] != b.src_seg[i][k])
584 phs.push_back(mk(EV_PHASE, static_cast<double>(node_of[i]), static_cast<double>(k), -1.0,
585 sc->ph[static_cast<std::size_t>(b.src_seg[i][k])] + 1.0));
586 }
587 }
588 std::vector<Ev> evs(deps);
589 evs.insert(evs.end(), arvs.begin(), arvs.end());
590 evs.insert(evs.end(), drps.begin(), drps.end());
591 evs.insert(evs.end(), starts.begin(), starts.end());
592 evs.insert(evs.end(), phs.begin(), phs.end());
593 return evs;
594 }
595
596 /**
597 * Event-boundary hook (`spBoundary`): records what the event that just ran
598 * changed, then, unless the run is stopping, the phase changes due before
599 * `t_next`. `snapshot(now, prev)` builds the current snapshot.
600 *
601 * @return 0 to continue; 1 when the recorder filled at `now` and the run stops
602 * there; 2 when it filled at a virtual row and the run stops at
603 * `stop_time` (> now), before the next event.
604 */
605 template <class SnapFn, class PhaseFn>
606 int boundary(bool stopped, double now, double t_next, SnapFn snapshot, PhaseFn sampler_phase) {
607 if (!have_held) {
608 held = snapshot(now, nullptr);
609 have_held = true;
610 for (std::size_t i = 0; i < M; ++i)
611 if (is_service[i] && detail[i])
612 for (const Job& j : held.jobs[i]) ids[j.key] = next_id++;
613 } else {
614 Snap cur = snapshot(now, &held);
615 const std::vector<Ev> evs = diff(held, cur);
616 if (!evs.empty()) {
617 std::vector<std::vector<double>> st, ag;
618 encode(held, st, ag, sampler_phase);
619 rec.add_row(now, st, ag, evs);
620 }
621 held = cur;
622 }
623 pending.clear();
624 src_deps.clear();
625 drops.clear();
626 id_fifo.clear();
627 if (rec.is_full()) {
628 done = true;
629 if (!stopped) {
630 stopped_run = true;
631 stop_time = now;
632 return 1;
633 }
634 return 0;
635 }
636 if (stopped) {
637 done = true;
638 return 0;
639 }
640 // Virtual PHASE rows: scripted phase changes due before the next event.
641 while (true) {
642 double tau = std::numeric_limits<double>::infinity();
643 for (std::size_t i = 0; i < M; ++i)
644 if (is_service[i] && detail[i])
645 for (const Job& j : held.jobs[i]) tau = std::min(tau, j.next_boundary());
646 for (std::size_t i = 0; i < M; ++i) {
647 if (!is_source[i]) continue;
648 for (std::size_t k = 0; k < R; ++k) {
649 const ScriptP& sc = held.src[i][k];
650 const int sg = held.src_seg[i][k];
651 if (sc && sg < static_cast<int>(sc->cum.size()) - 1)
652 tau = std::min(tau, held.src_start[i][k] + sc->cum[static_cast<std::size_t>(sg)]);
653 }
654 }
655 if (!(tau < t_next)) break;
656 tau = std::max(tau, now);
657 std::vector<std::vector<double>> st, ag;
658 encode(held, st, ag, sampler_phase);
659 std::vector<Ev> evs;
660 for (std::size_t i = 0; i < M; ++i) {
661 if (!(is_service[i] && detail[i])) continue;
662 const double node = static_cast<double>(node_of[i]);
663 for (Job& j : held.jobs[i]) {
664 const double tb = j.next_boundary();
665 if (tb <= tau) {
666 const int before = j.phase();
667 ++j.seg;
668 while (j.next_boundary() <= tau) ++j.seg;
669 if (j.phase() != before) {
670 Ev e;
671 e.v[0] = EV_PHASE;
672 e.v[1] = node;
673 e.v[2] = static_cast<double>(j.cls);
674 e.v[3] = static_cast<double>(ids[j.key]);
675 e.v[4] = j.phase() + 1.0;
676 evs.push_back(e);
677 }
678 }
679 }
680 }
681 for (std::size_t i = 0; i < M; ++i) {
682 if (!is_source[i]) continue;
683 for (std::size_t k = 0; k < R; ++k) {
684 const ScriptP& sc = held.src[i][k];
685 if (!sc) continue;
686 int& sg = held.src_seg[i][k];
687 const int before = sc->ph[static_cast<std::size_t>(sg)];
688 while (sg < static_cast<int>(sc->cum.size()) - 1 &&
689 held.src_start[i][k] + sc->cum[static_cast<std::size_t>(sg)] <= tau)
690 ++sg;
691 if (sc->ph[static_cast<std::size_t>(sg)] != before) {
692 Ev e;
693 e.v[0] = EV_PHASE;
694 e.v[1] = static_cast<double>(node_of[i]);
695 e.v[2] = static_cast<double>(k);
696 e.v[3] = -1.0;
697 e.v[4] = sc->ph[static_cast<std::size_t>(sg)] + 1.0;
698 evs.push_back(e);
699 }
700 }
701 }
702 if (evs.empty()) continue; // a segment boundary that kept the phase (a self-loop sojourn)
703 rec.add_row(tau, st, ag, evs);
704 if (rec.is_full()) {
705 // The last row is stamped tau, later than the clock: stop the run AT
706 // tau, so the statistics integrate the same interval the path covers.
707 done = true;
708 stopped_run = true;
709 stop_time = tau;
710 return 2;
711 }
712 }
713 return 0;
714 }
715};
716
717} // namespace sp
718} // namespace engine
719} // namespace ldes
720} // namespace line
721
722#endif // LINE_SOLVERS_LDES_SAMPLEPATH_H
One variate generator, holding whatever state its family needs.
lang::ProcessType type() const
int erlang_k() const
Phases of an Erlang recovered for convolution, 0 when none was.
std::size_t map_order() const
Order of the (D0,D1) representation, 0 for a family that carries none.
Transcribes SamplePathRecorder: the rows and events of the path.
std::size_t stateful_node(std::size_t isf) const
const std::vector< std::vector< double > > & events() const
std::vector< std::vector< double > > state(std::size_t isf) const
The detailed rows of node isf, LEFT-padded with zeros to the widest row.
const std::string & refusal(std::size_t isf) const
void add_row(double time, const std::vector< std::vector< double > > &state, const std::vector< std::vector< double > > &aggr, const std::vector< Ev > &evs)
bool refused(std::size_t isf) const
Recorder(std::size_t limit, const std::vector< std::size_t > &stateful_nodes, std::size_t nclasses, const std::vector< std::string > &refusal, const std::vector< bool > &refused)
const std::vector< double > & times() const
std::vector< std::vector< double > > aggr(std::size_t isf) const
java.util.Random, the 48-bit LCG of the Java Language Specification.
Definition rng_ssj.h:219
void set_seed(long long seed)
Java's setSeed: scramble by 0x5DEECE66D and mask to 48 bits.
Definition rng_ssj.h:224
double next_double()
Java's nextDouble(): a 26-bit and a 27-bit draw.
Definition rng_ssj.h:253
The variate generators of the native LDES engine.
bool process_is_marked_schedule(ProcessType p)
True when sn.proc holds the MARKED SCHEDULE slot.
Definition lang_types.h:745
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:518
bool process_is_marked_stationary(ProcessType p)
True when the type carries PER-MARK arrival blocks, i.e.
Definition lang_types.h:736
constexpr double EV_DROP
std::shared_ptr< Script > ScriptP
constexpr double EV_DEP
int phase_count(const Sampler &s, bool disabled, bool arrival)
Phases the export reports for a law (spPhaseCount), or -1 when the law is time-inhomogeneous and has ...
constexpr double EV_START
constexpr double EV_PHASE
constexpr double EV_ARV
Event wire codes (= MATLAB EventType values).
bool walked(const Sampler &s, bool arrival)
True for a family whose draw is a phase WALK the sampler can trace: the reference's MapSampler / map_...
constexpr long long SEED_OFFSET
Seed offset of the recorder's own generator (Erlang stage reconstruction).
double ulp(double v)
Java's Math.ulp for a double.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The two random number generators the Java LDES engine draws from, reproduced exactly: SSJ's MRG32k3a ...
The phases one walked interval visited and how long it held each, filled by a sampler only while the ...
double total() const
Sum of the holdings, in order, as the reference adds them.
std::vector< double > dt
One recorded event without its step and time: {type, node, class, jobId, phase}.
One job held at a detailed station in a snapshot.
double t0
Attained amount at time t0 and its rate of growth (1 in wall time, the PS share otherwise).
double next_boundary() const
Epoch at which the current segment ends, +Inf if it is the last.
Phase script of one drawn interval: segment j ends at cum[j] and is spent in ph[j] (0-based).
int seg_at(double a, int from) const
Segment holding attained amount a, never below from.
static std::shared_ptr< Script > single(double total)
Jobs held everywhere at one instant, indexed by station.
std::vector< std::vector< Job > > jobs
Per station: waiting jobs head first, then the jobs in service; meaningful when detail[i].
std::vector< std::vector< int > > counts
Per station and class: job count.
std::vector< std::vector< double > > src_start
std::vector< std::vector< ScriptP > > src
Per station (sources only) and class: interarrival script in progress, start, segment.
std::vector< std::vector< int > > src_seg
The recorder's bookkeeping between two boundaries: scripts drawn and not yet bound to a job,...
std::vector< std::pair< std::size_t, std::size_t > > src_deps
std::vector< std::vector< double > > const_row
per stateful node that is no station
std::vector< std::vector< double > > map_var_init
std::vector< bool > is_source
per station
void source_departure(std::size_t st, std::size_t k)
void drop(std::size_t q, std::size_t k)
std::vector< Pending > pending
std::vector< std::pair< std::size_t, std::size_t > > drops
std::vector< std::vector< int > > map_vars
per station and class
std::deque< long long > id_fifo
std::vector< std::vector< int > > phases
per station and class
std::vector< long long > station_of_sf
per stateful node: station or -1
ScriptP script_of(double value, int erlang_k)
Script of the draw that just returned value: its walk, an Erlang reconstruction, or one segment.
std::vector< bool > fcfs_buffer
per station: the row carries a buffer
ScriptP script_for(std::uint64_t key, std::size_t q, std::size_t k, double expected, double now)
Script already bound to key, or a fresh binding (stored under key).
void encode(const Snap &s, std::vector< std::vector< double > > &state, std::vector< std::vector< double > > &aggr, PhaseFn sampler_phase) const
Encodes snapshot s as LINE State rows and per-class counts.
std::vector< std::vector< double > > src_start
std::vector< long long > node_of
per station: 0-based node index
std::vector< Ev > diff(const Snap &a, const Snap &b)
Events turning snapshot a into b, in the order DEP, ARV, DROP, START, PHASE.
ScriptP bind(std::size_t q, std::size_t k, double expected, double now)
Binds a drawn script of (q, k) to a job whose drawn total is expected.
void snap_sources(Snap &s, double now, const Snap *prev) const
Source part of a snapshot.
std::map< std::uint64_t, long long > ids
std::vector< bool > is_service
per station: a Queue or a Delay
std::vector< std::vector< ScriptP > > src_script
[station][class]
int boundary(bool stopped, double now, double t_next, SnapFn snapshot, PhaseFn sampler_phase)
Event-boundary hook (spBoundary): records what the event that just ran changed, then,...
void capture_arrival(std::size_t st, std::size_t k, double value, int erlang_k, double now)
void capture_service(std::size_t q, std::size_t k, double value, int erlang_k)
bool stopped_run
True when the recorder stopped the run (stoppingReason "sample_path").
PhaseTrace * trace_fill()
Hands the scratch trace to a recording sampler call.
std::map< std::uint64_t, ScriptP > scripts