LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
rng_ssj.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_UTIL_RNG_SSJ_H
6
#define LINE_UTIL_RNG_SSJ_H
7
8
/**
9
* @file
10
* @ingroup line_util
11
* The two random number generators the Java LDES engine draws from, reproduced
12
* exactly: SSJ's `MRG32k3a` and `java.util.Random`.
13
*
14
* WHY THIS EXISTS. `common/ldes` is to become the C++ engine compiled, in place
15
* of a GraalVM image of the Java one, and a simulator that answers with a
16
* different sample path at the same seed is not a replacement -- every seeded
17
* golden in MATLAB, Python and the parity harness would re-baseline, and
18
* "LDES agrees across the codebases at a given seed", which `CLAUDE.md` states
19
* as an invariant, would weaken to "agrees in distribution". The C++ engine
20
* previously drew from `std::mt19937_64`; this header is the first half of
21
* closing that gap, and `ldes_sampler.h` is the second (the variate algorithms
22
* must consume the same uniforms in the same order).
23
*
24
* WHAT MADE IT TRACTABLE. `Solver_ssj` never relies on SSJ's package-level
25
* stream sequence: every stream is created and then given an EXPLICIT six-long
26
* seed derived from the run seed and a per-(node, class) offset, e.g.
27
*
28
* long offset = ((long) (numSources + svcIdx) * numClasses + k) * 10 + 1000;
29
* stream.setSeed(new long[] { seed + offset, ..., seed + offset + 5 });
30
*
31
* so none of SSJ's 2^127 jump-ahead machinery is on the path. Matching the
32
* engine means matching the recurrence, `nextValue`/`nextDouble`, and those
33
* offsets -- not the stream hierarchy.
34
*
35
* MRG32k3a is L'Ecuyer's combined multiple recursive generator (Operations
36
* Research 47(1), 1999): two order-three recurrences modulo m1 = 2^32 - 209 and
37
* m2 = 2^32 - 22853, combined by subtraction. SSJ computes it in DOUBLES with
38
* the multipliers split so every intermediate stays exactly representable, and
39
* that arithmetic is reproduced here rather than an integer rewrite: the double
40
* form is what the reference runs, and the two agree only if the rounding does.
41
*
42
* `java.util.Random` is the specified 48-bit LCG of the Java Language
43
* Specification: seed scrambling by 0x5DEECE66D, `next(bits)` taking the high
44
* bits, `nextDouble` from a 26-bit and a 27-bit draw, and `nextInt(bound)` with
45
* its rejection loop for non-power-of-two bounds. All of it is normative, so a
46
* faithful transcription is exact by construction.
47
*/
48
49
#include <cmath>
50
#include <cstdint>
51
#include <string>
52
53
#include "
line/util/error.h
"
54
55
namespace
line
{
56
namespace
rng
{
57
58
// ---------------------------------------------------------------------------
59
// SSJ MRG32k3a
60
// ---------------------------------------------------------------------------
61
62
/**
63
* SSJ's `umontreal.ssj.rng.MRG32k3a`, state and all.
64
*
65
* The state is the six doubles (Cg[0..5]) SSJ keeps; `set_seed` takes the same
66
* six-long vector `setSeed(long[])` takes and applies the same validation, so a
67
* call site can be transcribed from the Java verbatim.
68
*/
69
class
Mrg32k3a
{
70
public
:
71
Mrg32k3a
() { reset_to_default(); }
72
73
/** SSJ's setSeed(long[6]). Values are taken modulo the two moduli. */
74
void
set_seed
(
const
long
long
s[6]) {
75
validate(s);
76
for
(
int
i = 0; i < 6; ++i) cg_[i] = static_cast<double>(s[i]);
77
}
78
79
/** Convenience for the engine's `{seed+off, ..., seed+off+5}` idiom. */
80
void
set_seed_offset
(
long
long
seed,
long
long
offset) {
81
long
long
s[6];
82
for
(
int
i = 0; i < 6; ++i) s[i] = seed + offset + i;
83
set_seed
(s);
84
}
85
86
/**
87
* SSJ's nextValue(): the combined generator, returning a double in (0, 1).
88
* Never returns 0; may return values arbitrarily close to 1.
89
*/
90
double
next_double
() {
91
// Component 1
92
double
p1 = kA12 * cg_[1] - kA13n * cg_[0];
93
double
k = std::floor(p1 / kM1);
94
p1 -= k * kM1;
95
if
(p1 < 0.0) p1 += kM1;
96
cg_[0] = cg_[1];
97
cg_[1] = cg_[2];
98
cg_[2] = p1;
99
100
// Component 2
101
double
p2 = kA21 * cg_[5] - kA23n * cg_[3];
102
k = std::floor(p2 / kM2);
103
p2 -= k * kM2;
104
if
(p2 < 0.0) p2 += kM2;
105
cg_[3] = cg_[4];
106
cg_[4] = cg_[5];
107
cg_[5] = p2;
108
109
// Combination
110
return
((p1 > p2) ? (p1 - p2) * kNorm : (p1 - p2 + kM1) * kNorm);
111
}
112
113
/** SSJ's nextInt(i, j): a uniform integer on [i, j]. */
114
int
next_int
(
int
i,
int
j) {
115
if
(i > j)
throw
InputError
(
"Mrg32k3a::next_int: empty range"
);
116
return
i +
static_cast<
int
>
(
next_double
() * (
static_cast<
double
>
(j - i) + 1.0));
117
}
118
119
/** The six state doubles, for tests that pin the state and not only the draws. */
120
double
state
(
int
i)
const
{
return
cg_[i]; }
121
122
private
:
123
void
reset_to_default() {
124
// SSJ's default initial seed (12345 in all six slots).
125
for
(
int
i = 0; i < 6; ++i) cg_[i] = 12345.0;
126
}
127
128
static
void
validate(
const
long
long
s[6]) {
129
for
(
int
i = 0; i < 3; ++i) {
130
if
(s[i] < 0 ||
static_cast<
double
>
(s[i]) >= kM1)
131
throw
InputError
(
"Mrg32k3a::set_seed: the first three seeds must be in [0, m1)"
);
132
}
133
for
(
int
i = 3; i < 6; ++i) {
134
if
(s[i] < 0 ||
static_cast<
double
>
(s[i]) >= kM2)
135
throw
InputError
(
"Mrg32k3a::set_seed: the last three seeds must be in [0, m2)"
);
136
}
137
if
(s[0] == 0 && s[1] == 0 && s[2] == 0)
138
throw
InputError
(
"Mrg32k3a::set_seed: the first three seeds must not all be zero"
);
139
if
(s[3] == 0 && s[4] == 0 && s[5] == 0)
140
throw
InputError
(
"Mrg32k3a::set_seed: the last three seeds must not all be zero"
);
141
}
142
143
// L'Ecuyer's constants, exactly as SSJ spells them.
144
static
constexpr
double
kM1 = 4294967087.0;
145
static
constexpr
double
kM2 = 4294944443.0;
146
static
constexpr
double
kA12 = 1403580.0;
147
static
constexpr
double
kA13n = 810728.0;
148
static
constexpr
double
kA21 = 527612.0;
149
static
constexpr
double
kA23n = 1370589.0;
150
/** 1/(m1+1), SSJ's norm, so the result lies strictly inside (0,1). */
151
static
constexpr
double
kNorm = 2.328306549295727688e-10;
152
153
double
cg_[6];
154
};
155
156
// ---------------------------------------------------------------------------
157
// java.util.Random
158
// ---------------------------------------------------------------------------
159
160
/**
161
* `java.util.Random`, the 48-bit LCG of the Java Language Specification.
162
*
163
* The engine uses 23 of these alongside the MRG streams, for the choices that
164
* are draws rather than variates: which batch size a BMAP service releases,
165
* which branch a probabilistic route takes, and so on. Transcribed rather than
166
* approximated, for the same reason as above.
167
*/
168
class
JavaRandom
{
169
public
:
170
explicit
JavaRandom
(
long
long
seed = 0) {
set_seed
(seed); }
171
172
/** Java's setSeed: scramble by 0x5DEECE66D and mask to 48 bits. */
173
void
set_seed
(
long
long
seed) {
174
seed_ = (
static_cast<
uint64_t
>
(seed) ^ 0x5DEECE66DULL) & ((1ULL << 48) - 1);
175
have_next_gaussian_ =
false
;
176
}
177
178
/** Java's next(bits). */
179
int32_t
next
(
int
bits) {
180
seed_ = (seed_ * 0x5DEECE66DULL + 0xBULL) & ((1ULL << 48) - 1);
181
return
static_cast<
int32_t
>
(
static_cast<
int64_t
>
(seed_) >> (48 - bits));
182
}
183
184
/** Java's nextInt(). */
185
int32_t
next_int
() {
return
next
(32); }
186
187
/** Java's nextInt(bound), including the rejection loop it documents. */
188
int32_t
next_int
(int32_t bound) {
189
if
(bound <= 0)
throw
InputError
(
"JavaRandom::next_int: bound must be positive"
);
190
if
((bound & -bound) == bound) {
// power of two
191
return
static_cast<
int32_t
>
((
static_cast<
int64_t
>
(bound) *
next
(31)) >> 31);
192
}
193
int32_t bits, val;
194
do
{
195
bits =
next
(31);
196
val = bits % bound;
197
}
while
(bits - val + (bound - 1) < 0);
198
return
val;
199
}
200
201
/** Java's nextDouble(): a 26-bit and a 27-bit draw. */
202
double
next_double
() {
203
return
static_cast<
double
>
((
static_cast<
int64_t
>
(
next
(26)) << 27) +
next
(27)) /
204
static_cast<
double
>
(1LL << 53);
205
}
206
207
/** Java's nextLong(). */
208
int64_t
next_long
() {
209
return
(
static_cast<
int64_t
>
(
next
(32)) << 32) +
next
(32);
210
}
211
212
/** Java's nextBoolean(). */
213
bool
next_boolean
() {
return
next
(1) != 0; }
214
215
/** Java's nextGaussian(), the polar method with its cached second value. */
216
double
next_gaussian
() {
217
if
(have_next_gaussian_) {
218
have_next_gaussian_ =
false
;
219
return
next_gaussian_;
220
}
221
double
v1, v2, s;
222
do
{
223
v1 = 2.0 *
next_double
() - 1.0;
224
v2 = 2.0 *
next_double
() - 1.0;
225
s = v1 * v1 + v2 * v2;
226
}
while
(s >= 1.0 || s == 0.0);
227
const
double
multiplier = std::sqrt(-2.0 * std::log(s) / s);
228
next_gaussian_ = v2 * multiplier;
229
have_next_gaussian_ =
true
;
230
return
v1 * multiplier;
231
}
232
233
private
:
234
uint64_t seed_ = 0;
235
bool
have_next_gaussian_ =
false
;
236
double
next_gaussian_ = 0.0;
237
};
238
239
}
// namespace rng
240
}
// namespace line
241
242
#endif
// LINE_UTIL_RNG_SSJ_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::rng::JavaRandom::JavaRandom
JavaRandom(long long seed=0)
Definition
rng_ssj.h:170
line::rng::JavaRandom::next_boolean
bool next_boolean()
Java's nextBoolean().
Definition
rng_ssj.h:213
line::rng::JavaRandom::next
int32_t next(int bits)
Java's next(bits).
Definition
rng_ssj.h:179
line::rng::JavaRandom::set_seed
void set_seed(long long seed)
Java's setSeed: scramble by 0x5DEECE66D and mask to 48 bits.
Definition
rng_ssj.h:173
line::rng::JavaRandom::next_gaussian
double next_gaussian()
Java's nextGaussian(), the polar method with its cached second value.
Definition
rng_ssj.h:216
line::rng::JavaRandom::next_int
int32_t next_int(int32_t bound)
Java's nextInt(bound), including the rejection loop it documents.
Definition
rng_ssj.h:188
line::rng::JavaRandom::next_double
double next_double()
Java's nextDouble(): a 26-bit and a 27-bit draw.
Definition
rng_ssj.h:202
line::rng::JavaRandom::next_long
int64_t next_long()
Java's nextLong().
Definition
rng_ssj.h:208
line::rng::JavaRandom::next_int
int32_t next_int()
Java's nextInt().
Definition
rng_ssj.h:185
line::rng::Mrg32k3a::Mrg32k3a
Mrg32k3a()
Definition
rng_ssj.h:71
line::rng::Mrg32k3a::set_seed
void set_seed(const long long s[6])
SSJ's setSeed(long[6]).
Definition
rng_ssj.h:74
line::rng::Mrg32k3a::next_int
int next_int(int i, int j)
SSJ's nextInt(i, j): a uniform integer on [i, j].
Definition
rng_ssj.h:114
line::rng::Mrg32k3a::next_double
double next_double()
SSJ's nextValue(): the combined generator, returning a double in (0, 1).
Definition
rng_ssj.h:90
line::rng::Mrg32k3a::state
double state(int i) const
The six state doubles, for tests that pin the state and not only the draws.
Definition
rng_ssj.h:120
line::rng::Mrg32k3a::set_seed_offset
void set_seed_offset(long long seed, long long offset)
Convenience for the engine's {seed+off, ..., seed+off+5} idiom.
Definition
rng_ssj.h:80
error.h
The exception types the port throws.
line::rng
Definition
rng_ssj.h:56
line
Definition
aoi_dist2ph.h:52
include
line
util
rng_ssj.h
Generated by
1.18.0