LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_mc_common.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_API_PFQN_MC_COMMON_H
6
#define LINE_API_PFQN_MC_COMMON_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* Randomness scaffolding shared by the Monte Carlo normalizing-constant
12
* estimators (pfqn_mci, pfqn_is, pfqn_ld_is, pfqn_oi_is, pfqn_pas_is, pfqn_ls,
13
* pfqn_mmsample2) and by the perfect sampler pfqn_cftp.
14
*
15
* This header is NOT a port of a MATLAB function. It exists because the MATLAB
16
* references draw from the global MATLAB stream (rand / randi / mvnrnd, seeded
17
* out of band by `rng(options.seed)`), and a library must not carry a hidden
18
* global stream. Every estimator in this tree therefore takes a
19
* `std::mt19937_64&` as an explicit argument and consumes it through the
20
* helpers below.
21
*
22
* REPRODUCIBILITY CONTRACT, which every estimator's own header repeats:
23
*
24
* - The estimate is comparable to MATLAB only IN DISTRIBUTION, never stream
25
* for stream. MATLAB's Mersenne Twister, its uniform-to-integer mapping and
26
* its normal transform all differ from the ones here, so the same seed does
27
* NOT produce the same sample path and the two estimates agree only up to
28
* Monte Carlo error. What IS comparable is the estimand: both target the
29
* exact constant of pfqn_ca, and both converge to it at the 1/sqrt(n) rate.
30
*
31
* - Within this port, reproducibility requires passing a generator in the
32
* same state. Two calls with generators seeded identically, on identical
33
* inputs, in the same build, produce bit-identical output; the generator is
34
* advanced by the call, so a second call on the same generator object does
35
* not repeat the first. No estimator seeds, re-seeds or copies the
36
* generator internally.
37
*
38
* - The uniform-to-integer map here is rejection based, not modulo based, so
39
* it is unbiased and identical on every platform and standard-library
40
* version. std::uniform_int_distribution and std::normal_distribution are
41
* deliberately avoided: their output is implementation defined, which would
42
* make a fixed-seed regression test non-portable.
43
*
44
* Arithmetic: everything here is inherently inexact (uniform deviates are
45
* dyadic approximations of a continuous law, the normal transform needs
46
* log/cos), so each consumer gates on num_traits<T>::has_transcendental.
47
*/
48
49
#include <cmath>
50
#include <cstdint>
51
#include <limits>
52
#include <random>
53
#include <vector>
54
55
#include "
line/num/number.h
"
56
#include "
line/util/error.h
"
57
58
namespace
line
{
59
namespace
pfqn
{
60
61
/** The generator type every Monte Carlo entry point in this tree accepts. */
62
using
McRng
= std::mt19937_64;
63
64
/**
65
* Uniform deviate on [0,1) with 53 significant bits, as a double. The top 53
66
* bits of one 64-bit draw are used, so exactly one generator step is consumed
67
* per deviate and the mapping is fully specified.
68
*/
69
inline
double
mc_uniform01
(
McRng
& g) {
70
return
static_cast<
double
>
(g() >> 11) * (1.0 / 9007199254740992.0);
71
}
72
73
/** The same deviate materialized in the working arithmetic. */
74
template
<
class
T>
75
T
mc_uniform
(
McRng
& g) {
76
return
num_traits<T>::from_double
(
mc_uniform01
(g));
77
}
78
79
/**
80
* Uniform integer on [0, n), unbiased by rejection. Consumes one generator
81
* step per attempt; the rejection probability is below 2^-64 * n, so for the
82
* class counts these estimators use it never rejects in practice.
83
*/
84
inline
std::uint64_t
mc_uniform_int
(
McRng
& g, std::uint64_t n) {
85
if
(n == 0)
throw
InputError
(
"mc_uniform_int: empty range"
);
86
if
(n == 1)
return
0;
87
const
std::uint64_t threshold = (0u - n) % n;
// 2^64 mod n
88
std::uint64_t r;
89
do
{
90
r = g();
91
}
while
(r < threshold);
92
return
r % n;
93
}
94
95
/**
96
* Standard normal deviate by the Box-Muller transform. Two uniforms are drawn
97
* and only the cosine branch is kept, so the routine holds no state between
98
* calls: a generator handed to two different estimators cannot be
99
* cross-contaminated by a cached second variate.
100
*/
101
inline
double
mc_normal01
(
McRng
& g) {
102
double
u1 =
mc_uniform01
(g);
103
const
double
u2 =
mc_uniform01
(g);
104
// log(0) would be -inf; the smallest representable positive deviate keeps
105
// the transform finite without perturbing the distribution measurably.
106
if
(u1 <= 0.0) u1 = 1.0 / 9007199254740992.0;
107
return
std::sqrt(-2.0 * std::log(u1)) * std::cos(6.283185307179586476925286766559 * u2);
108
}
109
110
/**
111
* log(mean(exp(v))), computed by factoring out the maximum so that the
112
* exponentials stay in range. MATLAB's logmeanexp, which every estimator that
113
* averages log-weights calls.
114
*/
115
inline
double
mc_logmeanexp
(
const
std::vector<double>& v) {
116
if
(v.empty())
return
-std::numeric_limits<double>::infinity();
117
double
m = -std::numeric_limits<double>::infinity();
118
for
(
double
x : v)
119
if
(x > m) m = x;
120
if
(!std::isfinite(m))
return
m;
121
double
acc = 0.0;
122
for
(
double
x : v) acc += std::exp(x - m);
123
return
m + std::log(acc /
static_cast<
double
>
(v.size()));
124
}
125
126
/**
127
* exp of a log-domain value, materialized in the working arithmetic. Overflows
128
* to infinity in double exactly where the references do, and stays in range
129
* for the high-precision backends.
130
*/
131
template
<
class
T>
132
T
mc_exp
(
double
lv) {
133
using
std::exp;
134
return
exp(
num_traits<T>::from_double
(lv));
135
}
136
137
/**
138
* log(n!) for a non-negative integer n, the factln / gammaln(1+n) of the
139
* references, accumulated in the working arithmetic so the high-precision
140
* backends do not lose the digits a double lgamma would drop.
141
*/
142
template
<
class
T>
143
double
mc_log_factorial
(
long
n) {
144
if
(n < 0)
throw
InputError
(
"mc_log_factorial: negative argument"
);
145
if
(n < 2)
return
0.0;
146
return
num_traits<T>::log_as_double
(
num_factorial<T>
(
static_cast<
unsigned
>
(n)));
147
}
148
149
}
// namespace pfqn
150
}
// namespace line
151
152
#endif
// LINE_API_PFQN_MC_COMMON_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
error.h
The exception types the port throws.
line::pfqn
Definition
cd_peak_scaling.h:43
line::pfqn::mc_log_factorial
double mc_log_factorial(long n)
log(n!) for a non-negative integer n, the factln / gammaln(1+n) of the references,...
Definition
pfqn_mc_common.h:143
line::pfqn::McRng
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
Definition
pfqn_mc_common.h:62
line::pfqn::mc_logmeanexp
double mc_logmeanexp(const std::vector< double > &v)
log(mean(exp(v))), computed by factoring out the maximum so that the exponentials stay in range.
Definition
pfqn_mc_common.h:115
line::pfqn::mc_uniform_int
std::uint64_t mc_uniform_int(McRng &g, std::uint64_t n)
Uniform integer on [0, n), unbiased by rejection.
Definition
pfqn_mc_common.h:84
line::pfqn::mc_uniform01
double mc_uniform01(McRng &g)
Uniform deviate on [0,1) with 53 significant bits, as a double.
Definition
pfqn_mc_common.h:69
line::pfqn::mc_exp
T mc_exp(double lv)
exp of a log-domain value, materialized in the working arithmetic.
Definition
pfqn_mc_common.h:132
line::pfqn::mc_normal01
double mc_normal01(McRng &g)
Standard normal deviate by the Box-Muller transform.
Definition
pfqn_mc_common.h:101
line::pfqn::mc_uniform
T mc_uniform(McRng &g)
The same deviate materialized in the working arithmetic.
Definition
pfqn_mc_common.h:75
line
Definition
aoi_dist2ph.h:52
line::num_factorial
T num_factorial(unsigned n)
Factorial as a value of T.
Definition
number.h:184
number.h
Number-type abstraction for the templated API port.
line::num_traits
Definition
number.h:111
include
line
api
pfqn
pfqn_mc_common.h
Generated by
1.18.0