LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_simplex.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_PFQN_SIMPLEX_H
6#define LINE_API_PFQN_PFQN_SIMPLEX_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Shared machinery for closures of the simplex factor of the McKenna-Mitra integral,
12 * used by pfqn_aghq.h.
13 *
14 * At Z = 0 the integrand is homogeneous of degree sum(N), so y = v*x separates and the
15 * radius integrates exactly to gamma(N+M), leaving an integral over the unit simplex in
16 * which ALL of the error of the logistic expansion lives. With Z > 0 that factorisation
17 * is gone: the radius cannot be marginalised and is integrated numerically here rather
18 * than closed, leaving the same M-1 simplex directions to a closure.
19 *
20 * ARITHMETIC. Everything here is a Laplace-type approximation or a quadrature of a
21 * transcendental integrand, so each entry point is gated on
22 * num_traits<T>::has_transcendental exactly as pfqn_le is.
23 *
24 * EIGENSOLVER. util/eig.h is LAPACK and double-only, while this family is templated on
25 * T, so a cyclic Jacobi eigensolver is carried here instead. It is used for the
26 * Golub-Welsch construction of the quadrature nodes and for the principal-axis frame of
27 * the adaptive Gauss-Hermite rule. Jacobi is chosen over a tridiagonal QL because the
28 * matrices are small, it needs no shift strategy, and it is symmetric-exact by
29 * construction.
30 */
31
32#include <cmath>
33#include <cstddef>
34#include <vector>
35
38#include "line/num/number.h"
39#include "line/util/error.h"
40#include "line/util/matrix.h"
41
42namespace line {
43namespace pfqn {
44namespace simplex {
45
46/**
47 * Eigenvalues and eigenvectors of a symmetric matrix by cyclic Jacobi, ascending.
48 * V.col(k) is the unit eigenvector of d[k].
49 */
50template <class T>
51void sym_eig(Matrix<T> A, std::vector<T>& d, Matrix<T>& V) {
52 using std::abs;
53 using std::sqrt;
54 const std::size_t n = A.rows();
55 const T zero = num_traits<T>::from_int(0);
56 const T one = num_traits<T>::from_int(1);
57 V = Matrix<T>(n, n);
58 for (std::size_t i = 0; i < n; ++i)
59 for (std::size_t j = 0; j < n; ++j) V(i, j) = (i == j) ? one : zero;
60 for (int sweep = 0; sweep < 100; ++sweep) {
61 T off = zero;
62 for (std::size_t p = 0; p + 1 < n; ++p)
63 for (std::size_t q = p + 1; q < n; ++q) off += A(p, q) * A(p, q);
64 if (num_traits<T>::to_double(off) <= 1e-30) break;
65 for (std::size_t p = 0; p + 1 < n; ++p) {
66 for (std::size_t q = p + 1; q < n; ++q) {
67 if (num_traits<T>::to_double(abs(A(p, q))) == 0.0) continue;
68 T theta = T((A(q, q) - A(p, p)) / (A(p, q) + A(p, q)));
69 T t = T(one / T(abs(theta) + sqrt(T(theta * theta + one))));
70 if (num_traits<T>::to_double(theta) < 0.0) t = T(zero - t);
71 T c = T(one / sqrt(T(t * t + one)));
72 T s = T(t * c);
73 for (std::size_t k = 0; k < n; ++k) {
74 T akp = A(k, p), akq = A(k, q);
75 A(k, p) = T(c * akp - s * akq);
76 A(k, q) = T(s * akp + c * akq);
77 }
78 for (std::size_t k = 0; k < n; ++k) {
79 T apk = A(p, k), aqk = A(q, k);
80 A(p, k) = T(c * apk - s * aqk);
81 A(q, k) = T(s * apk + c * aqk);
82 }
83 for (std::size_t k = 0; k < n; ++k) {
84 T vkp = V(k, p), vkq = V(k, q);
85 V(k, p) = T(c * vkp - s * vkq);
86 V(k, q) = T(s * vkp + c * vkq);
87 }
88 }
89 }
90 }
91 d.assign(n, zero);
92 for (std::size_t i = 0; i < n; ++i) d[i] = A(i, i);
93 for (std::size_t i = 0; i + 1 < n; ++i) { // selection sort, ascending
94 std::size_t m = i;
95 for (std::size_t j = i + 1; j < n; ++j)
97 if (m != i) {
98 T tmp = d[i];
99 d[i] = d[m];
100 d[m] = tmp;
101 for (std::size_t k = 0; k < n; ++k) {
102 T v = V(k, i);
103 V(k, i) = V(k, m);
104 V(k, m) = v;
105 }
106 }
107 }
108}
109
110/** Nodes and weights of the Gauss rule with the given Jacobi off-diagonal. */
111template <class T>
112void golub_welsch(const std::vector<T>& off, const T& mu0, std::vector<T>& x,
113 std::vector<T>& w) {
114 const std::size_t n = off.size() + 1;
115 const T zero = num_traits<T>::from_int(0);
116 Matrix<T> J(n, n);
117 for (std::size_t i = 0; i < n; ++i)
118 for (std::size_t j = 0; j < n; ++j) J(i, j) = zero;
119 for (std::size_t k = 0; k + 1 < n; ++k) {
120 J(k, k + 1) = off[k];
121 J(k + 1, k) = off[k];
122 }
123 Matrix<T> V(n, n);
124 sym_eig(J, x, V);
125 w.assign(n, zero);
126 for (std::size_t k = 0; k < n; ++k) w[k] = T(mu0 * V(0, k) * V(0, k));
127}
128
129/** N-point Gauss-Legendre rule on [-1,1]. */
130template <class T>
131void gauss_legendre(std::size_t n, std::vector<T>& x, std::vector<T>& w) {
132 using std::sqrt;
133 std::vector<T> off(n - 1);
134 for (std::size_t k = 1; k < n; ++k) {
135 T kk = num_traits<T>::from_int(static_cast<long>(k));
136 off[k - 1] = T(kk / sqrt(T(num_traits<T>::from_int(4) * kk * kk -
138 }
140}
141
142/** Q-point Gauss-Hermite rule of the probabilists' weight exp(-z^2/2). */
143template <class T>
144void gauss_hermite(std::size_t q, std::vector<T>& z, std::vector<T>& w) {
145 using std::sqrt;
146 const T twopi = num_traits<T>::from_double(6.283185307179586476925286766559);
147 if (q == 1) {
148 z.assign(1, num_traits<T>::from_int(0));
149 w.assign(1, sqrt(twopi));
150 return;
151 }
152 std::vector<T> off(q - 1);
153 for (std::size_t k = 1; k < q; ++k)
154 off[k - 1] = sqrt(num_traits<T>::from_int(static_cast<long>(k)));
155 golub_welsch(off, T(sqrt(twopi)), z, w);
156}
157
158/** Log-integrand of the radial integral in t = log v, Jacobian included. */
159template <class T>
160T radial_logf(const T& t, const std::vector<T>& c, const std::vector<T>& N,
161 const std::vector<T>& Z, std::size_t M) {
162 using std::exp;
163 using std::log;
164 const T tiny = num_traits<T>::from_double(2.2250738585072014e-308);
165 T v = exp(t);
166 T f = T(num_traits<T>::from_int(0) - v +
167 num_traits<T>::from_int(static_cast<long>(M)) * t);
168 for (std::size_t r = 0; r < c.size(); ++r) {
169 T d = T(Z[r] + v * c[r]);
170 if (num_traits<T>::to_double(d) < 2.2250738585072014e-308) d = tiny;
171 f += N[r] * log(d);
172 }
173 return f;
174}
175
176/** log J(c) and the moments of the tilted law of the radius. */
177template <class T>
178struct Radial {
179 T lJ;
180 std::vector<T> G;
183};
184
185/**
186 * log J(c) = log int_0^inf exp(-v) v^(M-1) prod_r (Z_r + v c_r)^N_r dv, plus the moments
187 * of the tilted law of v that the simplex derivatives need: G_r = E[T_r], vbar = E[v]
188 * and Lam = cov(T) - diag(E[T^2]/N) = grad^2_c log J, with T_r(v) = N_r v/(Z_r + v c_r).
189 * Quadrature runs in t = log v, where the integrand is bounded at both ends, over two
190 * Gauss-Legendre panels meeting at the mode, each widened until the log-integrand has
191 * fallen 60 nats so the discarded tails are below 1e-26 in relative terms.
192 */
193template <class T>
194Radial<T> radial(const std::vector<T>& c, const std::vector<T>& N, const std::vector<T>& Z,
195 std::size_t M) {
197 "radial requires transcendental arithmetic (quadrature of an integral)");
198 using std::abs;
199 using std::exp;
200 using std::log;
201 using std::sqrt;
202 const std::size_t R = c.size();
203 const T zero = num_traits<T>::from_int(0);
204 const T tiny = num_traits<T>::from_double(2.2250738585072014e-308);
205 const T Md = num_traits<T>::from_int(static_cast<long>(M));
206
207 static std::vector<T> vg, wg;
208 if (vg.empty()) gauss_legendre<T>(64, vg, wg);
209
210 T Ntot = zero;
211 for (std::size_t r = 0; r < R; ++r) Ntot += N[r];
212 T t = log(T(Ntot + Md));
213 for (int it = 0; it < 200; ++it) {
214 T v = exp(t);
215 T f1 = T(Md - v), f2 = T(zero - v);
216 for (std::size_t r = 0; r < R; ++r) {
217 T d = T(Z[r] + v * c[r]);
218 if (num_traits<T>::to_double(d) < 2.2250738585072014e-308) d = tiny;
219 f1 += N[r] * T(v * c[r]) / d;
220 f2 += N[r] * T(v * c[r]) * Z[r] / T(d * d);
221 }
222 if (num_traits<T>::to_double(f2) > -1e-300) break;
223 double stepd = num_traits<T>::to_double(T(zero - f1) / f2);
224 if (stepd > 2.0) stepd = 2.0;
225 if (stepd < -2.0) stepd = -2.0;
226 t += num_traits<T>::from_double(stepd);
227 if (stepd < 1e-13 && stepd > -1e-13) break;
228 }
229 T v = exp(t);
230 T f2 = T(zero - v);
231 for (std::size_t r = 0; r < R; ++r) {
232 T d = T(Z[r] + v * c[r]);
233 if (num_traits<T>::to_double(d) < 2.2250738585072014e-308) d = tiny;
234 f2 += N[r] * T(v * c[r]) * Z[r] / T(d * d);
235 }
236 T sig = num_traits<T>::from_int(1);
237 if (num_traits<T>::to_double(f2) < -1e-300) sig = T(num_traits<T>::from_int(1) /
238 sqrt(T(zero - f2)));
239 T fm = radial_logf(t, c, N, Z, M);
240 T lim = T(t + num_traits<T>::from_double(745.0));
241 T a = T(num_traits<T>::from_double(12.0) * sig);
243 for (int k = 0; k < 60; ++k) {
244 if (num_traits<T>::to_double(T(t - a)) <= -745.0) break;
245 if (num_traits<T>::to_double(radial_logf(T(t - a), c, N, Z, M)) <
246 num_traits<T>::to_double(fm) - 60.0)
247 break;
248 a = T(num_traits<T>::from_double(1.6) * a);
250 }
251 T b = T(num_traits<T>::from_double(12.0) * sig);
252 for (int k = 0; k < 60; ++k) {
253 if (num_traits<T>::to_double(radial_logf(T(t + b), c, N, Z, M)) <
254 num_traits<T>::to_double(fm) - 60.0)
255 break;
256 b = T(num_traits<T>::from_double(1.6) * b);
257 }
258 const T half = num_traits<T>::from_double(0.5);
259 const std::size_t nq = 2 * vg.size();
260 std::vector<T> tt(nq), W(nq), vv(nq), fv(nq);
261 for (std::size_t k = 0; k < vg.size(); ++k) {
262 tt[k] = T(half * a * vg[k] + T(t - half * a));
263 W[k] = T(half * a * wg[k]);
264 tt[vg.size() + k] = T(half * b * vg[k] + T(t + half * b));
265 W[vg.size() + k] = T(half * b * wg[k]);
266 }
267 Matrix<T> D(nq, R);
268 double mx = -1e308;
269 for (std::size_t k = 0; k < nq; ++k) {
270 vv[k] = exp(tt[k]);
271 T f = T(T(zero - vv[k]) + Md * tt[k]);
272 for (std::size_t r = 0; r < R; ++r) {
273 T d = T(Z[r] + vv[k] * c[r]);
274 if (num_traits<T>::to_double(d) < 2.2250738585072014e-308) d = tiny;
275 D(k, r) = d;
276 f += N[r] * log(d);
277 }
278 fv[k] = f;
280 }
281 T mxT = num_traits<T>::from_double(mx);
282 std::vector<T> e(nq);
283 T se = zero;
284 for (std::size_t k = 0; k < nq; ++k) {
285 e[k] = T(W[k] * exp(T(fv[k] - mxT)));
286 se += e[k];
287 }
288 Radial<T> out;
289 out.lJ = T(mxT + log(se));
290 out.G.assign(R, zero);
291 out.vbar = zero;
292 Matrix<T> Tm(nq, R);
293 std::vector<T> p(nq);
294 for (std::size_t k = 0; k < nq; ++k) {
295 p[k] = T(e[k] / se);
296 out.vbar += p[k] * vv[k];
297 for (std::size_t r = 0; r < R; ++r) {
298 Tm(k, r) = T(N[r] * vv[k] / D(k, r));
299 out.G[r] += p[k] * Tm(k, r);
300 }
301 }
302 Matrix<T> et2(R, R);
303 for (std::size_t r = 0; r < R; ++r)
304 for (std::size_t s = 0; s < R; ++s) et2(r, s) = zero;
305 for (std::size_t k = 0; k < nq; ++k)
306 for (std::size_t r = 0; r < R; ++r) {
307 T pt = T(p[k] * Tm(k, r));
308 for (std::size_t s = 0; s < R; ++s) et2(r, s) += pt * Tm(k, s);
309 }
310 out.Lam = Matrix<T>(R, R);
311 for (std::size_t r = 0; r < R; ++r) {
312 for (std::size_t s = 0; s < R; ++s)
313 out.Lam(r, s) = T(et2(r, s) - out.G[r] * out.G[s]);
314 if (num_traits<T>::to_double(N[r]) > 0.0)
315 out.Lam(r, r) = T(out.Lam(r, r) - et2(r, r) / N[r]);
316 }
317 for (std::size_t r = 0; r < R; ++r)
318 for (std::size_t s = r + 1; s < R; ++s) {
319 T m = T(half * T(out.Lam(r, s) + out.Lam(s, r)));
320 out.Lam(r, s) = m;
321 out.Lam(s, r) = m;
322 }
323 return out;
324}
325
326/** Mode, curvature and log-integrand at the mode of the simplex factor. */
327template <class T>
328struct Mode {
329 std::vector<T> x;
331 T ld;
332 T h0;
333};
334
335/**
336 * Mode and curvature of h(w) = log J(L'x(w)) + sum_i log x_i with J the exact radial
337 * integral. The fixed point x = (1 + x.*(L*G))/vbar is the Z > 0 analogue of
338 * pfqn_le_fpi: integrating by parts gives sum_i x_i (L*G)_i = vbar - M, so the update is
339 * normalised by construction, and at Z = 0 it reduces to pfqn_le_fpi. The term in the
340 * second derivative of x(w) drops at the mode against sum_i x_i == 1.
341 */
342template <class T>
343Mode<T> simplex_mode(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
344 using std::abs;
345 using std::log;
346 const std::size_t M = L.rows(), R = L.cols();
347 const T zero = num_traits<T>::from_int(0);
348 const T one = num_traits<T>::from_int(1);
349 std::vector<T> x;
350 T vstart = zero;
351 pfqn_le_fpiZ(L, N, Z, x, vstart); // logistic-expansion mode as a warm start
352 std::vector<T> x1(M, num_traits<T>::from_double(1e300));
353 for (int it = 0; it < 10000; ++it) {
354 double diff = 0.0;
355 for (std::size_t i = 0; i < M; ++i)
356 diff += num_traits<T>::to_double(abs(T(x[i] - x1[i])));
357 if (diff <= 1e-11) break;
358 x1 = x;
359 std::vector<T> c(R, zero);
360 for (std::size_t r = 0; r < R; ++r)
361 for (std::size_t i = 0; i < M; ++i) c[r] += x1[i] * L(i, r);
362 Radial<T> rad = radial(c, N, Z, M);
363 T s = zero;
364 for (std::size_t i = 0; i < M; ++i) {
365 T lg = zero;
366 for (std::size_t r = 0; r < R; ++r) lg += L(i, r) * rad.G[r];
367 x[i] = T(T(one + x1[i] * lg) / rad.vbar);
368 s += x[i];
369 }
370 for (std::size_t i = 0; i < M; ++i) x[i] = T(x[i] / s);
371 }
372 std::vector<T> c(R, zero);
373 for (std::size_t r = 0; r < R; ++r)
374 for (std::size_t i = 0; i < M; ++i) c[r] += x[i] * L(i, r);
375 Radial<T> rad = radial(c, N, Z, M);
376 // P = L*Lam*L' - diag(1/x^2); A = -Jm'*P*Jm with Jm = (diag(x)-x*x')(:,1:M-1).
377 Matrix<T> P(M, M);
378 for (std::size_t i = 0; i < M; ++i) {
379 for (std::size_t j = 0; j < M; ++j) {
380 T acc = zero;
381 for (std::size_t r = 0; r < R; ++r) {
382 T lr = zero;
383 for (std::size_t s = 0; s < R; ++s) lr += rad.Lam(r, s) * L(j, s);
384 acc += L(i, r) * lr;
385 }
386 P(i, j) = acc;
387 }
388 P(i, i) = T(P(i, i) - one / T(x[i] * x[i]));
389 }
390 const std::size_t d = M - 1;
391 Matrix<T> Jm(M, d);
392 for (std::size_t i = 0; i < M; ++i)
393 for (std::size_t aI = 0; aI < d; ++aI)
394 Jm(i, aI) = T((i == aI ? x[i] : zero) - x[i] * x[aI]);
395 Mode<T> out;
396 out.A = Matrix<T>(d, d);
397 for (std::size_t aI = 0; aI < d; ++aI)
398 for (std::size_t bI = 0; bI < d; ++bI) {
399 T acc = zero;
400 for (std::size_t i = 0; i < M; ++i) {
401 T pj = zero;
402 for (std::size_t j = 0; j < M; ++j) pj += P(i, j) * Jm(j, bI);
403 acc += Jm(i, aI) * pj;
404 }
405 out.A(aI, bI) = T(zero - acc);
406 }
407 const T half = num_traits<T>::from_double(0.5);
408 for (std::size_t i = 0; i < d; ++i)
409 for (std::size_t j = i + 1; j < d; ++j) {
410 T m = T(half * T(out.A(i, j) + out.A(j, i)));
411 out.A(i, j) = m;
412 out.A(j, i) = m;
413 }
414 out.x = x;
415 out.ld = (d == 0) ? zero : detail::pfqn_logdet(out.A);
416 T sum_lx = zero;
417 for (std::size_t i = 0; i < M; ++i) sum_lx += log(x[i]);
418 out.h0 = T(rad.lJ + sum_lx);
419 return out;
420}
421
422/** softmax of [w; 0], the logistic parametrisation of the simplex with gauge w_M = 0. */
423template <class T>
424std::vector<T> softmax_gauge(const std::vector<T>& w) {
425 using std::exp;
426 const std::size_t M = w.size() + 1;
427 std::vector<T> a(M, num_traits<T>::from_int(0));
428 double mx = 0.0;
429 for (std::size_t i = 0; i < w.size(); ++i) {
430 a[i] = w[i];
431 if (num_traits<T>::to_double(a[i]) > mx) mx = num_traits<T>::to_double(a[i]);
432 }
433 T mxT = num_traits<T>::from_double(mx);
434 std::vector<T> x(M);
436 for (std::size_t i = 0; i < M; ++i) {
437 x[i] = exp(T(a[i] - mxT));
438 s += x[i];
439 }
440 for (std::size_t i = 0; i < M; ++i) x[i] = T(x[i] / s);
441 return x;
442}
443
444/**
445 * Log of the tensor Gauss-Hermite sum, accumulated with a running maximum; the
446 * det(A)^(-1/2) of the rule is applied by the caller. A tensor rule is NOT invariant to
447 * the choice of A^(-1/2): the principal-axis frame is used, as in the reference results.
448 */
449template <class T, class F>
450T aghq_rule(const F& h, const std::vector<T>& w0, const T& h0, const Matrix<T>& A,
451 std::size_t q, std::size_t d) {
452 using std::exp;
453 using std::log;
454 using std::sqrt;
455 const T zero = num_traits<T>::from_int(0);
456 if (d == 0) return zero;
457 double nodesd = std::pow(static_cast<double>(q), static_cast<double>(d));
458 if (nodesd > 1e7)
459 throw InputError("pfqn_aghq: the tensor rule needs more than 1e7 nodes; reduce q or use pfqn_le");
460 const std::size_t nodes = static_cast<std::size_t>(nodesd);
461 std::vector<T> lam;
462 Matrix<T> V(d, d);
463 sym_eig(A, lam, V);
464 Matrix<T> B(d, d);
465 for (std::size_t j = 0; j < d; ++j) {
466 if (num_traits<T>::to_double(lam[j]) <= 0.0)
467 throw InputError("pfqn_aghq: the curvature at the mode is not positive definite");
468 T sc = T(num_traits<T>::from_int(1) / sqrt(lam[j]));
469 for (std::size_t i = 0; i < d; ++i) B(i, j) = T(V(i, j) * sc);
470 }
471 std::vector<T> z, wt;
472 gauss_hermite<T>(q, z, wt);
473 std::vector<T> lwt(q);
474 for (std::size_t k = 0; k < q; ++k) lwt[k] = log(wt[k]);
475 std::vector<std::size_t> idx(d, 0);
476 double lmax = -1e308;
477 T s = zero;
478 std::vector<T> zz(d), w(d);
479 bool first = true;
480 for (std::size_t k = 0; k < nodes; ++k) {
481 T lw = zero, zsq = zero;
482 for (std::size_t j = 0; j < d; ++j) {
483 zz[j] = z[idx[j]];
484 lw += lwt[idx[j]];
485 zsq += zz[j] * zz[j];
486 }
487 for (std::size_t i = 0; i < d; ++i) {
488 T acc = w0[i];
489 for (std::size_t j = 0; j < d; ++j) acc += B(i, j) * zz[j];
490 w[i] = acc;
491 }
492 T lt = T(lw + h(w) - h0 + num_traits<T>::from_double(0.5) * zsq);
493 double ltd = num_traits<T>::to_double(lt);
494 if (first || ltd > lmax) {
495 T lmaxT = num_traits<T>::from_double(lmax);
496 s = first ? num_traits<T>::from_int(1)
497 : T(s * exp(T(lmaxT - lt)) + num_traits<T>::from_int(1));
498 lmax = ltd;
499 first = false;
500 } else {
501 s += exp(T(lt - num_traits<T>::from_double(lmax)));
502 }
503 for (std::size_t j = d; j-- > 0;) {
504 if (++idx[j] < q) break;
505 idx[j] = 0;
506 }
507 }
508 return T(num_traits<T>::from_double(lmax) + log(s));
509}
510
511} // namespace simplex
512} // namespace pfqn
513} // namespace line
514
515#endif // LINE_API_PFQN_PFQN_SIMPLEX_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
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > softmax_gauge(const std::vector< T > &w)
softmax of [w; 0], the logistic parametrisation of the simplex with gauge w_M = 0.
T aghq_rule(const F &h, const std::vector< T > &w0, const T &h0, const Matrix< T > &A, std::size_t q, std::size_t d)
Log of the tensor Gauss-Hermite sum, accumulated with a running maximum; the det(A)^(-1/2) of the rul...
void golub_welsch(const std::vector< T > &off, const T &mu0, std::vector< T > &x, std::vector< T > &w)
Nodes and weights of the Gauss rule with the given Jacobi off-diagonal.
Mode< T > simplex_mode(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Mode and curvature of h(w) = log J(L'x(w)) + sum_i log x_i with J the exact radial integral.
Radial< T > radial(const std::vector< T > &c, const std::vector< T > &N, const std::vector< T > &Z, std::size_t M)
log J(c) = log int_0^inf exp(-v) v^(M-1) prod_r (Z_r + v c_r)^N_r dv, plus the moments of the tilted ...
T radial_logf(const T &t, const std::vector< T > &c, const std::vector< T > &N, const std::vector< T > &Z, std::size_t M)
Log-integrand of the radial integral in t = log v, Jacobian included.
void gauss_legendre(std::size_t n, std::vector< T > &x, std::vector< T > &w)
N-point Gauss-Legendre rule on [-1,1].
void gauss_hermite(std::size_t q, std::vector< T > &z, std::vector< T > &w)
Q-point Gauss-Hermite rule of the probabilists' weight exp(-z^2/2).
void sym_eig(Matrix< T > A, std::vector< T > &d, Matrix< T > &V)
Eigenvalues and eigenvectors of a symmetric matrix by cyclic Jacobi, ascending.
void pfqn_le_fpiZ(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, std::vector< T > &u, T &v)
Mode of the logistic-transformed integrand, Z > 0 case (pfqn_le_fpiZ).
Definition pfqn_le.h:91
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Logistic expansion (LE) asymptotic approximation of the normalizing constant of a closed product-form...
Mode, curvature and log-integrand at the mode of the simplex factor.
log J(c) and the moments of the tilted law of the radius.