LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_mmsample2.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_MMSAMPLE2_H
6#define LINE_API_PFQN_MMSAMPLE2_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Sampled McKenna-Mitra integral form of the normalizing constant of a
12 * repairman (single-queue plus delay) model.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_mmsample2.m, cross-checked
15 * against jar/src/main/java/jline/api/pfqn/nc/Pfqn_mmsample2.java.
16 *
17 * For a single queue the McKenna-Mitra form collapses to the one-dimensional
18 * integral
19 *
20 * G(N) = 1/prod_r N_r! int_0^inf e^{-u} prod_r (Z_r + L_r u)^{N_r} du,
21 *
22 * whose log-integrand at the grid point u is
23 *
24 * f(u) = -u + sum_r N_r log(Z_r + L_r u).
25 *
26 * The reference lays a grid of ceil(samples/2) uniform points on [0,1) plus
27 * ceil(samples/2) points log-spaced over [1, 1e5], SORTS it, rescales the
28 * demands so that every coefficient is at least one, takes the panel widths
29 * du = [v(1), diff(v)] (the first panel covering [0, v(1)]) and reports the
30 * quadrature sum in the log domain,
31 *
32 * lG = log sum_i du_i exp(f(v_i)) - sum_r log N_r! + sum(N) log(scaleFactor).
33 *
34 * (An earlier reference took the maximum of the weighted log-integrand over an
35 * unsorted grid with the integrand (Z_r + L_r) u; MATLAB has since been
36 * corrected and this port follows the corrected estimator.)
37 *
38 * Only the FIRST row of L is read, matching the reference: this is a
39 * single-queue model and additional rows are ignored.
40 *
41 * Arithmetic: INEXACT BY CONSTRUCTION. The grid is random, the integrand is
42 * evaluated in the log domain, and the log-spaced block is generated by
43 * exponentiation.
44 *
45 * RNG contract: see pfqn_mc_common.h. Comparable to MATLAB only in
46 * distribution, never stream for stream; reproducible within this port only
47 * when the generator is passed in the same state.
48 */
49
50#include <algorithm>
51#include <cmath>
52#include <cstddef>
53#include <limits>
54#include <vector>
55
58#include "line/num/number.h"
59#include "line/util/error.h"
60#include "line/util/matrix.h"
61
62namespace line {
63namespace pfqn {
64
65/**
66 * @brief Sampled McKenna-Mitra integral form of the normalizing constant of a
67 * repairman (single-queue plus delay) model.
68 *
69 * @param L (M x R) demands; only row 0 is read, as in the reference
70 * @param N (R) population per class
71 * @param Z (R) think times
72 * @param samples grid size; half uniform on [0,1), half log-spaced on [1,1e5]
73 * @param rng explicit generator, advanced by the call
74 */
75template <class T>
76NcResult<T> pfqn_mmsample2(const Matrix<T>& L, const std::vector<int>& N, const std::vector<T>& Z,
77 std::size_t samples, McRng& rng) {
79 "pfqn_mmsample2 requires transcendental arithmetic: it samples a random "
80 "quadrature grid and evaluates a log-domain integrand on it");
81
82 const std::size_t R = N.size();
83 if (L.empty()) throw InputError("pfqn_mmsample2: empty demand matrix");
84 if (L.cols() != R) throw InputError("pfqn_mmsample2: L and N disagree on the class count");
85 if (Z.size() != R) throw InputError("pfqn_mmsample2: Z has the wrong length");
86 for (int n : N)
87 if (n < 0) throw InputError("pfqn_mmsample2: negative population");
88 if (samples == 0) throw InputError("pfqn_mmsample2: at least one grid point is required");
89
90 // ---- rescale so that every coefficient is at least one -----------------
91 T mn = L(0, 0);
92 for (std::size_t i = 0; i < L.rows(); ++i)
93 for (std::size_t r = 0; r < R; ++r)
94 if (L(i, r) < mn) mn = L(i, r);
95 for (std::size_t r = 0; r < R; ++r)
96 if (Z[r] < mn) mn = Z[r];
97 const T scale = num_traits<T>::from_double(1e-7) + mn;
98 if (!(scale > num_traits<T>::from_int(0)))
99 throw NumericError(ErrorKind{"Degenerate"}, "pfqn_mmsample2: non-positive scale factor, demands must be positive");
100
101 // scaled coefficients Z_r/scale and L_r/scale (first row of L)
102 std::vector<double> Zs(R), Ls(R);
103 for (std::size_t r = 0; r < R; ++r) {
104 Zs[r] = num_traits<T>::to_double(T(Z[r] / scale));
105 Ls[r] = num_traits<T>::to_double(T(L(0, r) / scale));
106 }
107 long Ntot = 0;
108 for (int n : N) Ntot += n;
109
110 // ---- the grid: uniform block plus the log-spaced block, SORTED ---------
111 const std::size_t nu = static_cast<std::size_t>(
112 std::ceil(0.5 * static_cast<double>(samples)));
113 const std::size_t nl = static_cast<std::size_t>(
114 std::ceil(0.5 * static_cast<double>(samples)));
115 std::vector<double> v;
116 v.reserve(nu + nl);
117 for (std::size_t i = 0; i < nu; ++i) v.push_back(mc_uniform01(rng));
118 for (std::size_t i = 0; i < nl; ++i) {
119 const double t = nl == 1 ? 0.0 : 5.0 * static_cast<double>(i) / static_cast<double>(nl - 1);
120 v.push_back(std::pow(10.0, t));
121 }
122 std::sort(v.begin(), v.end());
123
124 // lterms_i = log(du_i) - v_i + sum_r N_r log(Z_r + L_r v_i), du = [v(1), diff(v)]
125 std::vector<double> lterms;
126 lterms.reserve(v.size());
127 double lmax = -std::numeric_limits<double>::infinity();
128 for (std::size_t i = 0; i < v.size(); ++i) {
129 const double du = i == 0 ? v[0] : v[i] - v[i - 1];
130 double f = std::log(du) - v[i];
131 for (std::size_t r = 0; r < R; ++r) {
132 if (N[r] == 0) continue;
133 f += static_cast<double>(N[r]) * std::log(Zs[r] + Ls[r] * v[i]);
134 }
135 lterms.push_back(f);
136 if (f > lmax) lmax = f;
137 }
138 double ssum = 0.0;
139 for (double f : lterms)
140 if (std::isfinite(f)) ssum += std::exp(f - lmax);
141
142 double lG = lmax + std::log(ssum);
143 for (std::size_t r = 0; r < R; ++r) lG -= mc_log_factorial<T>(N[r]);
144 lG += static_cast<double>(Ntot) * num_traits<T>::log_as_double(scale);
145 return {mc_exp<T>(lG), lG};
146}
147
148/** Reference call shape with an explicit grid size. */
149template <class T>
150NcResult<T> pfqn_mmsample2(const Matrix<T>& L, const std::vector<int>& N, const std::vector<T>& Z,
151 McRng& rng) {
152 return pfqn_mmsample2(L, N, Z, static_cast<std::size_t>(100000), rng);
153}
154
155} // namespace pfqn
156} // namespace line
157
158#endif // LINE_API_PFQN_MMSAMPLE2_H
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
bool empty() const
Definition matrix.h:92
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
Dense matrix and non-owning view.
double mc_log_factorial(long n)
log(n!) for a non-negative integer n, the factln / gammaln(1+n) of the references,...
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
NcResult< T > pfqn_mmsample2(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, std::size_t samples, McRng &rng)
Sampled McKenna-Mitra integral form of the normalizing constant of a repairman (single-queue plus del...
double mc_uniform01(McRng &g)
Uniform deviate on [0,1) with 53 significant bits, as a double.
T mc_exp(double lv)
exp of a log-domain value, materialized in the working arithmetic.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Randomness scaffolding shared by the Monte Carlo normalizing-constant estimators (pfqn_mci,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition pfqn_ca.h:44