LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_ld_is.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_LD_IS_H
6#define LINE_API_PFQN_LD_IS_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Importance-sampling estimate of the normalizing constant of a closed
12 * LOAD-DEPENDENT product-form network.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_ld_is.m, cross-checked against
15 * jar/src/main/java/jline/api/pfqn/nc/Pfqn_ld_is.java.
16 *
17 * Identity. Every product-form station's balance function is the sum, over the
18 * orderings q of a given per-class count vector n, of an ordered product of a
19 * per-position factor,
20 *
21 * F_i(n) = |n|!/prod_r(n_r!) prod_r L(i,r)^{n_r} / prod_{k=1}^{|n|} mu_i(k)
22 * = sum_{q: |q| = n} prod_{p=1}^{|n|} L(i,q_p) / mu_i(p),
23 *
24 * so with ell = sum(N) and a cut vector splitting an ordering c of all ell
25 * jobs into S contiguous segments (one per station),
26 *
27 * G(N) = sum_c sum_{cuts} prod_m prod_p L(m, seg_m(p)) / mu_m(p).
28 *
29 * The delay is the station with mu_Z(k) = k, a single server is mu_i(k) = 1
30 * and a c-server queue is mu_i(k) = min(k,c).
31 *
32 * Estimator. An ordering c is drawn by placing a uniformly random present
33 * class at each step; for the sampled c the inner sum over ALL cut vectors is
34 * evaluated EXACTLY by the dynamic program A_0(0) = 1,
35 * A_m(k) = sum_{j<=k} A_{m-1}(j) w_m(c_{j+1..k}), in O(S ell^2). The estimate
36 * is the sample mean of A_S(ell)/p(c), which is unbiased for G(N).
37 *
38 * Deviation from the reference, deliberate: MATLAB accumulates log p(c) and
39 * multiplies by exp(-logp). Here 1/p(c) is accumulated directly as the product
40 * of the integer branching factors, which is the same number computed without
41 * a log/exp round trip and is exact in every arithmetic. The two agree to
42 * rounding in double.
43 *
44 * Arithmetic: INEXACT BY CONSTRUCTION. The estimator's output is a random
45 * variable whose value depends on the drawn orderings, so no arithmetic makes
46 * it exact; it is gated on has_transcendental because it reports lG = log(G)
47 * and because the sampler itself needs a real-valued uniform stream.
48 *
49 * RNG contract: see pfqn_mc_common.h. Comparable to MATLAB only in
50 * distribution, never stream for stream; reproducible within this port only
51 * when the generator is passed in the same state.
52 */
53
54#include <cstddef>
55#include <vector>
56
59#include "line/num/number.h"
60#include "line/util/error.h"
61#include "line/util/matrix.h"
62
63namespace line {
64namespace pfqn {
65
66/**
67 * @brief Importance-sampling estimate of the normalizing constant of a closed
68 * LOAD-DEPENDENT product-form network.
69 *
70 * @param L (M x R) per-class demands at the M queueing stations
71 * @param N (R) closed population vector
72 * @param Z (R) aggregated think times; empty or all zero for no delay
73 * @param mu (M x k) load-dependent capacities, mu(i,k-1) with k jobs at
74 * station i; empty for the load-independent case mu = 1. A
75 * short row is extended with its last entry, as in the
76 * reference.
77 * @param samples number of importance samples
78 * @param rng explicit generator, advanced by the call
79 */
80template <class T>
81NcResult<T> pfqn_ld_is(const Matrix<T>& L, const std::vector<int>& N, const std::vector<T>& Z,
82 const Matrix<T>& mu, std::size_t samples, McRng& rng) {
84 "pfqn_ld_is requires transcendental arithmetic: it is a Monte Carlo estimator, "
85 "inexact by construction, and reports the log of its own estimate");
86
87 const std::size_t M = L.empty() ? 0 : L.rows();
88 const std::size_t R = N.size();
89 if (!L.empty() && L.cols() != R)
90 throw InputError("pfqn_ld_is: L must have as many columns as N has classes");
91 if (!Z.empty() && Z.size() != R) throw InputError("pfqn_ld_is: Z has the wrong length");
92 for (int n : N)
93 if (n < 0) throw InputError("pfqn_ld_is: negative population");
94 if (samples == 0) throw InputError("pfqn_ld_is: at least one sample is required");
95
96 const T zero = num_traits<T>::from_int(0);
97 const T one = num_traits<T>::from_int(1);
98
99 long ell_l = 0;
100 for (int n : N) ell_l += n;
101 if (ell_l == 0) return {one, 0.0};
102 const std::size_t ell = static_cast<std::size_t>(ell_l);
103
104 // ---- station list: the M queues, plus the delay as mu_Z(k) = k ---------
105 bool hasZ = false;
106 for (std::size_t r = 0; r < Z.size(); ++r)
107 if (Z[r] > zero) hasZ = true;
108 const std::size_t S = M + (hasZ ? 1u : 0u);
109 if (S == 0) throw InputError("pfqn_ld_is: no station carries any demand");
110
111 Matrix<T> D(S, R, zero);
112 Matrix<T> B(S, ell, one);
113 for (std::size_t i = 0; i < M; ++i) {
114 for (std::size_t r = 0; r < R; ++r) D(i, r) = L(i, r);
115 if (mu.empty()) continue;
116 if (mu.rows() != M) throw InputError("pfqn_ld_is: mu has the wrong station count");
117 const std::size_t kmax = mu.cols() < ell ? mu.cols() : ell;
118 for (std::size_t k = 0; k < kmax; ++k) B(i, k) = mu(i, k);
119 for (std::size_t k = kmax; k < ell; ++k) B(i, k) = mu(i, mu.cols() - 1);
120 }
121 if (hasZ) {
122 for (std::size_t r = 0; r < R; ++r) D(S - 1, r) = Z[r];
123 for (std::size_t k = 0; k < ell; ++k) B(S - 1, k) = num_traits<T>::from_int(static_cast<long>(k) + 1);
124 }
125 for (std::size_t i = 0; i < S; ++i)
126 for (std::size_t k = 0; k < ell; ++k)
127 if (!(B(i, k) > zero))
128 throw InputError("pfqn_ld_is: load-dependent capacities must be strictly positive");
129
130 T acc = zero;
131 std::vector<int> x(R);
132 std::vector<std::size_t> c(ell);
133 std::vector<std::size_t> avail(R);
134 std::vector<T> A(ell + 1), Anew(ell + 1);
135
136 for (std::size_t s = 0; s < samples; ++s) {
137 // ---- draw an ordering, uniformly random present class at each step --
138 x = N;
139 T invp = one; // 1/p(c) = product of the branching factors
140 for (std::size_t p = 0; p < ell; ++p) {
141 std::size_t na = 0;
142 for (std::size_t r = 0; r < R; ++r)
143 if (x[r] > 0) avail[na++] = r;
144 const std::size_t pick = avail[mc_uniform_int(rng, na)];
145 c[p] = pick;
146 invp *= num_traits<T>::from_int(static_cast<long>(na));
147 x[pick] -= 1;
148 }
149
150 // ---- exact inner sum over every cut vector, by dynamic programming --
151 A.assign(ell + 1, zero);
152 A[0] = one;
153 for (std::size_t m = 0; m < S; ++m) {
154 Anew.assign(ell + 1, zero);
155 for (std::size_t j = 0; j <= ell; ++j) {
156 if (A[j] == zero) continue;
157 Anew[j] += A[j]; // empty segment at station m
158 T w = one;
159 for (std::size_t k = j; k < ell; ++k) {
160 w *= D(m, c[k]) / B(m, k - j);
161 if (w == zero) break;
162 Anew[k + 1] += A[j] * w;
163 }
164 }
165 A.swap(Anew);
166 }
167 acc += A[ell] * invp;
168 }
169
170 const T G = acc / num_traits<T>::from_int(static_cast<long>(samples));
171 return {G, num_traits<T>::log_as_double(G)};
172}
173
174/** Reference default of 1e4 samples. */
175template <class T>
176NcResult<T> pfqn_ld_is(const Matrix<T>& L, const std::vector<int>& N, const std::vector<T>& Z,
177 const Matrix<T>& mu, McRng& rng) {
178 return pfqn_ld_is(L, N, Z, mu, static_cast<std::size_t>(10000), rng);
179}
180
181} // namespace pfqn
182} // namespace line
183
184#endif // LINE_API_PFQN_LD_IS_H
InputError(const std::string &what)
Definition error.h:39
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
The exception types the port throws.
Dense matrix and non-owning view.
NcResult< T > pfqn_ld_is(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu, std::size_t samples, McRng &rng)
Importance-sampling estimate of the normalizing constant of a closed LOAD-DEPENDENT product-form netw...
Definition pfqn_ld_is.h:81
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
std::uint64_t mc_uniform_int(McRng &g, std::uint64_t n)
Uniform integer on [0, n), unbiased by rejection.
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,...
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition pfqn_ca.h:44