5#ifndef LINE_API_PFQN_GERASIMOV_H
6#define LINE_API_PFQN_GERASIMOV_H
98T geras_poisw(
const T& Z,
unsigned p) {
99 if (p == 0)
return num_traits<T>::from_int(1);
100 if constexpr (num_traits<T>::has_transcendental) {
103 return T(exp(num_traits<T>::from_int(
static_cast<long>(p)) * log(Z) -
104 num_lgamma<T>(num_traits<T>::from_int(
static_cast<long>(p) + 1))));
115 std::vector<std::vector<T>> F;
121T geras_pow(
const T& base,
int e) {
122 if (e >= 0)
return num_pow_int(base,
static_cast<unsigned>(e));
123 return num_traits<T>::from_int(1) /
num_pow_int(base,
static_cast<unsigned>(-e));
129T geras_binom(
int n,
int k) {
130 if (k < 0 || n < 0 || k > n)
return num_traits<T>::from_int(0);
131 const int kk = std::min(k, n - k);
132 T b = num_traits<T>::from_int(1);
133 for (
int i = 1; i <= kk; ++i) {
134 b *= num_traits<T>::from_int(n - kk + i);
135 b /= num_traits<T>::from_int(i);
137 if constexpr (std::is_same<T, double>::value) {
138 if (b < 9007199254740992.0) b = std::rint(b);
144inline std::vector<std::vector<int>> geras_compositions(
int n,
int k) {
145 std::vector<std::vector<int>> out;
147 if (n == 0) out.push_back(std::vector<int>());
151 out.push_back(std::vector<int>(1, n));
154 for (
int a = 0; a <= n; ++a) {
155 std::vector<std::vector<int>> sub = geras_compositions(n - a, k - 1);
156 for (std::size_t i = 0; i < sub.
size(); ++i) {
157 std::vector<int> row;
160 row.insert(row.end(), sub[i].begin(), sub[i].end());
172void geras_merge(GerasTerm<T>& t,
const T& tol) {
173 const std::size_t nf = t.F.size();
175 const std::size_t w = t.F[0].size();
176 std::vector<bool> keep(nf,
true);
177 const T zero = num_traits<T>::from_int(0);
178 for (std::size_t j = 0; j < nf; ++j) {
179 if (!keep[j])
continue;
181 for (std::size_t s = 1; s < w; ++s)
183 if (t.F[j][pj] == zero)
continue;
184 for (std::size_t k = j + 1; k < nf; ++k) {
185 if (!keep[k])
continue;
186 const T lam = t.F[k][pj] / t.F[j][pj];
187 if (lam == zero)
continue;
188 T dev = zero, scale = zero;
189 for (std::size_t s = 0; s < w; ++s) {
190 dev = std::max(dev,
num_abs(T(t.F[k][s] - lam * t.F[j][s])));
191 scale = std::max(scale, std::max(
num_abs(t.F[k][s]),
num_abs(t.F[j][s])));
193 if (dev <= tol * scale) {
194 t.c *= geras_pow(lam, -t.m[k]);
200 std::vector<std::vector<T>> Fn;
202 for (std::size_t j = 0; j < nf; ++j) {
204 Fn.push_back(t.F[j]);
205 mn.push_back(t.m[j]);
217std::vector<GerasTerm<T>> geras_step(
const std::vector<GerasTerm<T>>& terms,
int r,
int Nr,
218 const T& Zr,
const T& tol, std::size_t maxterms) {
219 const T zero = num_traits<T>::from_int(0);
220 const T one = num_traits<T>::from_int(1);
221 std::vector<GerasTerm<T>> out;
222 for (std::size_t it = 0; it < terms.size(); ++it) {
223 GerasTerm<T> t = terms[it];
225 const std::size_t nf = t.F.size();
226 std::vector<std::vector<T>> A(nf, std::vector<T>(
static_cast<std::size_t
>(r), zero));
227 std::vector<T> B(nf, zero), scale(nf, zero);
228 for (std::size_t j = 0; j < nf; ++j) {
229 for (
int s = 0; s < r; ++s) A[j][static_cast<std::size_t>(s)] = t.F[j][
static_cast<std::size_t
>(s)];
230 B[j] = -t.F[j][
static_cast<std::size_t
>(r)];
231 for (
int s = 0; s <= r; ++s) scale[j] = std::max(scale[j],
num_abs(t.F[j][
static_cast<std::size_t
>(s)]));
235 std::vector<bool> drop(nf,
false);
236 for (std::size_t j = 0; j < nf; ++j) {
238 for (
int s = 0; s < r && mono; ++s)
239 if (
num_abs(A[j][
static_cast<std::size_t
>(s)]) > tol * scale[j]) mono =
false;
241 if (
num_abs(B[j]) <= tol * scale[j])
242 throw InputError(
"pfqn_gerasimov: identically zero factor, impossible after merging proportional ones");
244 c *= geras_pow(T(-B[j]), -t.m[j]);
248 std::vector<std::size_t> S, P;
249 for (std::size_t j = 0; j < nf; ++j) {
250 if (drop[j])
continue;
251 if (B[j] != zero) S.push_back(j);
else P.push_back(j);
253 const int Ntot = Nr + shift;
254 const int pmax = (Zr > zero) ? Ntot : 0;
255 for (
int p = 0; p <= pmax; ++p) {
260 if (p > 0) cz = c * geras_poisw<T>(Zr,
static_cast<unsigned>(p));
261 const int Neff = Ntot - p;
266 for (std::size_t q = 0; q < P.size(); ++q) {
267 nt.F.push_back(A[P[q]]);
268 nt.m.push_back(t.m[P[q]]);
274 for (std::size_t jj = 0; jj < S.size(); ++jj) {
275 const std::size_t j = S[jj];
276 std::vector<std::size_t> oth;
277 for (std::size_t q = 0; q < S.size(); ++q)
278 if (q != jj) oth.push_back(S[q]);
279 const std::size_t no = oth.size();
280 std::vector<std::vector<T>> Cjl(no, std::vector<T>(
static_cast<std::size_t
>(r), zero));
281 for (std::size_t l = 0; l < no; ++l) {
282 const std::size_t k = oth[l];
283 for (
int s = 0; s < r; ++s) {
284 const std::size_t ss =
static_cast<std::size_t
>(s);
285 Cjl[l][ss] = (A[k][ss] * B[j] - B[k] * A[j][ss]) / B[j];
288 for (
int k = 0; k < t.m[j]; ++k) {
289 const std::vector<std::vector<int>> comps = geras_compositions(k,
static_cast<int>(no));
290 for (std::size_t ci = 0; ci < comps.size(); ++ci) {
291 const std::vector<int>& nk = comps[ci];
292 T coef = cz * geras_pow(T(-B[j]), -k) * geras_binom<T>(Neff + t.m[j] - k - 1, Neff) *
293 geras_pow(B[j], Neff);
294 for (std::size_t l = 0; l < no; ++l)
295 coef *= geras_binom<T>(t.m[oth[l]] + nk[l] - 1, nk[l]) * geras_pow(B[oth[l]], nk[l]);
296 if (coef == zero)
continue;
299 nt.F.push_back(A[j]);
300 nt.m.push_back(Neff + t.m[j] - k);
301 for (std::size_t l = 0; l < no; ++l) {
302 nt.F.push_back(Cjl[l]);
303 nt.m.push_back(t.m[oth[l]] + nk[l]);
305 for (std::size_t q = 0; q < P.size(); ++q) {
306 nt.F.push_back(A[P[q]]);
307 nt.m.push_back(t.m[P[q]]);
314 if (out.size() > maxterms)
315 throw InputError(
"pfqn_gerasimov: residue expansion exceeded maxterms; use pfqn_ca or pfqn_nc");
323std::vector<T> geras_conv(
const std::vector<T>& a,
const std::vector<T>& b,
int n) {
324 const T zero = num_traits<T>::from_int(0);
325 std::vector<T> y(
static_cast<std::size_t
>(n) + 1, zero);
326 for (std::size_t i = 0; i < a.size() &&
static_cast<int>(i) <= n; ++i) {
327 if (a[i] == zero)
continue;
328 for (std::size_t j = 0; j < b.size() &&
static_cast<int>(i + j) <= n; ++j)
329 y[i + j] += a[i] * b[j];
340T geras_base(
const std::vector<GerasTerm<T>>& terms,
int N1,
const T& Z1,
const T& tol) {
341 const T zero = num_traits<T>::from_int(0);
343 for (std::size_t it = 0; it < terms.size(); ++it) {
344 GerasTerm<T> t = terms[it];
346 const std::size_t nf = t.F.size();
349 std::vector<bool> drop(nf,
false);
350 for (std::size_t j = 0; j < nf; ++j) {
351 const T Aj = t.F[j][0];
352 const T Bj = -t.F[j][1];
354 if (
num_abs(Aj) > tol * scale)
continue;
355 if (
num_abs(Bj) <= tol * scale)
356 throw InputError(
"pfqn_gerasimov: identically zero factor at the innermost coefficient extraction");
357 c *= geras_pow(T(-Bj), -t.m[j]);
361 const int Ntot = N1 + shift;
362 const std::size_t len =
static_cast<std::size_t
>(Ntot) + 1;
363 std::vector<T> s(len, zero);
364 s[0] = num_traits<T>::from_int(1);
366 std::vector<T> pois(len, zero);
367 for (std::size_t n = 0; n < len; ++n)
368 pois[n] = geras_poisw<T>(Z1,
static_cast<unsigned>(n));
369 s = geras_conv(s, pois, Ntot);
371 for (std::size_t j = 0; j < nf; ++j) {
372 if (drop[j])
continue;
373 const T Aj = t.F[j][0];
374 const T Bj = -t.F[j][1];
375 c *= geras_pow(Aj, -t.m[j]);
376 if (Bj == zero)
continue;
377 const T ratio = Bj / Aj;
378 std::vector<T> seq(len, zero);
379 for (std::size_t n = 0; n < len; ++n)
380 seq[n] = geras_binom<T>(t.m[j] +
static_cast<int>(n) - 1,
static_cast<int>(n)) *
382 s = geras_conv(s, seq, Ntot);
384 G += c * s[
static_cast<std::size_t
>(Ntot)];
409 double tol = 1e-12, std::size_t maxterms = 200000) {
410 const std::size_t R0 = N.size();
412 throw InputError(
"pfqn_gerasimov: L and N disagree on the class count");
414 std::vector<T> Zsum(R0, zero);
416 if (Z.
cols() != R0)
throw InputError(
"pfqn_gerasimov: Z and N disagree on the class count");
417 for (std::size_t k = 0; k < Z.
rows(); ++k)
418 for (std::size_t r = 0; r < R0; ++r) Zsum[r] += Z(k, r);
420 for (std::size_t r = 0; r < R0; ++r) {
421 if (N[r] < 0 || Zsum[r] < zero)
throw InputError(
"pfqn_gerasimov: L, N and Z must be nonnegative");
423 for (std::size_t i = 0; i < L.
rows(); ++i)
424 for (std::size_t r = 0; r < R0; ++r)
425 if (L(i, r) < zero)
throw InputError(
"pfqn_gerasimov: L, N and Z must be nonnegative");
429 std::vector<std::size_t> cls;
430 for (std::size_t r = 0; r < R0; ++r)
431 if (N[r] > 0) cls.push_back(r);
446 std::stable_sort(cls.begin(), cls.end(),
447 [&N](std::size_t a, std::size_t b) { return N[a] < N[b]; });
448 if (cls.size() > 2) std::reverse(cls.begin() + 1, cls.end());
449 const std::size_t R = cls.size();
457 for (std::size_t r = 0; r < R; ++r) {
459 for (std::size_t i = 0; i < L.
rows(); ++i) c = std::max(c, L(i, cls[r]));
460 if (c > zero) cs[r] = c;
464 std::vector<std::vector<T>> rows;
465 for (std::size_t i = 0; i < L.
rows(); ++i) {
466 std::vector<T> row(R + 1, zero);
469 for (std::size_t r = 0; r < R; ++r) {
470 const T v = L(i, cls[r]);
471 row[r + 1] = -(v / cs[r]);
472 if (v > zero) any =
true;
474 if (any) rows.push_back(row);
477 detail::GerasTerm<T> t0;
480 t0.m.assign(rows.size(), 1);
481 std::vector<detail::GerasTerm<T>> terms(1, t0);
485 for (std::size_t r = R; r >= 2; --r) {
486 terms = detail::geras_step(terms,
static_cast<int>(r), N[cls[r - 1]],
487 T(Zsum[cls[r - 1]] / cs[r - 1]), tolT, maxterms);
488 if (terms.empty())
return {zero, -std::numeric_limits<double>::infinity()};
490 const T Gs = detail::geras_base(terms, N[cls[0]], T(Zsum[cls[0]] / cs[0]), tolT);
491 if (Gs == zero)
return {zero, -std::numeric_limits<double>::infinity()};
493 double lGscale = 0.0;
494 for (std::size_t r = 0; r < R; ++r) {
495 unscale *=
num_pow_int(cs[r],
static_cast<unsigned>(N[cls[r]]));
506 return {T(Gs * unscale), lGout};
The exception types the port throws.
Dense matrix and non-owning view.
NcResult< T > pfqn_gerasimov(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, double tol=1e-12, std::size_t maxterms=200000)
Exact normalizing constant of a closed multiclass product-form network by ITERATED RESIDUES of its ra...
T num_factorial(unsigned n)
Factorial as a value of T.
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.