5#ifndef LINE_API_PFQN_PFQN_EXPLICIT_H
6#define LINE_API_PFQN_PFQN_EXPLICIT_H
77T explicit_nchoosekln(
const T& n,
const T& m) {
79 return T(num_lgamma<T>(T(one + n)) - num_lgamma<T>(T(one + n - m)) -
80 num_lgamma<T>(T(one + m)));
92SignedLse<T> explicit_signed_logsumexp(
const std::vector<T>& lterm,
93 const std::vector<double>& sterm) {
96 const double dinf = std::numeric_limits<double>::infinity();
98 out.lS = num_traits<T>::from_double(-dinf);
100 out.lossDigits = 0.0;
101 std::vector<std::size_t> keep;
102 for (std::size_t i = 0; i < lterm.size(); ++i) {
103 const double li = num_traits<T>::to_double(lterm[i]);
104 if (std::isfinite(li) && sterm[i] != 0.0) keep.push_back(i);
106 if (keep.empty())
return out;
107 T a = lterm[keep[0]];
108 for (std::size_t i : keep)
109 if (lterm[i] > a) a = lterm[i];
110 T s = num_traits<T>::from_int(0);
111 for (std::size_t i : keep)
112 s = T(s + num_traits<T>::from_double(sterm[i]) * exp(T(lterm[i] - a)));
113 const T zero = num_traits<T>::from_int(0);
115 out.lossDigits = dinf;
118 out.sgn = (s > zero) ? 1 : -1;
119 const T abss = (s > zero) ? s : T(zero - s);
120 out.lS = T(a + log(abss));
127 std::max(0.0, std::log10(
static_cast<double>(keep.size()) /
128 std::fabs(num_traits<T>::to_double(abss))));
138SignedLse<T> explicit_gdistinct(
const std::vector<T>& th,
const T& Nt, std::size_t K) {
140 const T zero = num_traits<T>::from_int(0);
141 const double dinf = std::numeric_limits<double>::infinity();
142 std::vector<T> lin(K, num_traits<T>::from_double(-dinf));
143 std::vector<double> sgv(K, 0.0);
144 for (std::size_t k = 0; k < K; ++k) {
145 if (!(th[k] > zero))
continue;
146 T acc = T(T(Nt + num_traits<T>::from_int(
static_cast<long>(K) - 1)) * log(th[k]));
148 for (std::size_t i = 0; i < K; ++i) {
149 if (i == k)
continue;
150 const T d = T(th[k] - th[i]);
151 const T ad = (d > zero) ? d : T(zero - d);
152 acc = T(acc - log(ad));
153 sign *= (d > zero) ? 1.0 : ((d < zero) ? -1.0 : 0.0);
158 return explicit_signed_logsumexp(lin, sgv);
162inline void explicit_multichoose(std::size_t Kp,
int k, std::vector<int>& current,
163 std::size_t idx, std::vector<std::vector<int> >& out) {
166 out.push_back(current);
169 for (
int i = 0; i <= k; ++i) {
171 explicit_multichoose(Kp, k - i, current, idx + 1, out);
175inline std::vector<std::vector<int> > explicit_multichoose(std::size_t Kp,
int k) {
176 std::vector<std::vector<int> > out;
177 if (Kp == 0 || k < 0)
return out;
178 std::vector<int> current(Kp, 0);
179 explicit_multichoose(Kp, k, current, 0, out);
189SignedLse<T> explicit_grepeated(
const std::vector<T>& th,
const T& Nt, std::size_t K,
192 const T zero = num_traits<T>::from_int(0);
193 const T one = num_traits<T>::from_int(1);
194 const double dinf = std::numeric_limits<double>::infinity();
195 std::vector<T> ths(th);
196 std::sort(ths.begin(), ths.end(), [](
const T& a,
const T& b) { return a < b; });
197 T scale = ths.back();
198 if (!(scale > zero)) scale = one;
199 const T gap = T(num_traits<T>::from_double(tol) * scale);
203 for (std::size_t i = 0; i < ths.size();) {
204 std::size_t j = i + 1;
205 while (j < ths.size() && !(T(ths[j] - ths[j - 1]) > gap)) ++j;
207 for (std::size_t q = i; q < j; ++q) sum = T(sum + ths[q]);
208 thd.push_back(T(sum / num_traits<T>::from_int(
static_cast<long>(j - i))));
209 m.push_back(
static_cast<int>(j - i));
212 const std::size_t Kp = thd.size();
214 std::vector<double> sgv;
215 for (std::size_t j = 0; j < Kp; ++j) {
216 if (!(thd[j] > zero)) {
222 T(T(Nt + num_traits<T>::from_int(
static_cast<long>(K) - m[j])) * log(thd[j]));
223 const double souter = ((m[j] - 1) % 2 == 0) ? 1.0 : -1.0;
224 const std::vector<std::vector<int> > rs = explicit_multichoose(Kp, m[j] - 1);
225 for (std::size_t i = 0; i < rs.size(); ++i) {
226 const std::vector<int>& r = rs[i];
227 T lval = T(louter + explicit_nchoosekln<T>(T(Nt + num_traits<T>::from_int(r[j])),
228 num_traits<T>::from_int(r[j])));
229 double sval = souter * ((r[j] % 2 == 0) ? 1.0 : -1.0);
230 bool vanished =
false;
231 for (std::size_t k = 0; k < Kp; ++k) {
232 if (k == j)
continue;
233 lval = T(lval + explicit_nchoosekln<T>(
234 num_traits<T>::from_int(m[k] + r[k] - 1),
235 num_traits<T>::from_int(r[k])));
237 if (!(thd[k] > zero)) {
242 lval = T(lval + num_traits<T>::from_int(r[k]) * log(thd[k]));
244 const T dd = T(thd[j] - thd[k]);
245 const T add = (dd > zero) ? dd : T(zero - dd);
246 lval = T(lval - num_traits<T>::from_int(m[k] + r[k]) * log(add));
247 if (!(dd > zero) && ((m[k] + r[k]) % 2 != 0)) sval = -sval;
250 lin.push_back(num_traits<T>::from_double(-dinf));
258 return explicit_signed_logsumexp(lin, sgv);
277 double tol = std::numeric_limits<double>::epsilon(),
278 const std::string& method =
"auto",
279 double maxloss = std::numeric_limits<double>::infinity()) {
281 "pfqn_explicit requires transcendental arithmetic: the alternating sums are "
282 "carried as signed log-sum-exps so that no intermediate can overflow. Use "
283 "pfqn_ca for the same constant in exact arithmetic");
287 const double dinf = std::numeric_limits<double>::infinity();
288 const std::size_t R = N.size();
295 if (method !=
"auto" && method !=
"distinct" && method !=
"repeated")
297 "pfqn_explicit: unrecognized method, use 'auto', 'distinct' (Eq. 15) or 'repeated' "
300 for (
int v : N) Nsum += v;
301 if (Nsum < 0)
return res;
307 if (L.
rows() == 0 || L.
cols() == 0)
return res;
308 if (
static_cast<std::size_t
>(L.
cols()) != R)
309 throw InputError(
"pfqn_explicit: the demand matrix must have one column per class of N");
310 const std::size_t K =
static_cast<std::size_t
>(L.
rows());
311 for (std::size_t i = 0; i < K; ++i)
312 for (std::size_t r = 0; r < R; ++r)
314 throw InputError(
"pfqn_explicit: the demand matrix must be nonnegative");
318 const auto induced = [&](
const std::vector<int>& t) {
319 std::vector<T> th(K, zero);
320 for (std::size_t i = 0; i < K; ++i)
321 for (std::size_t r = 0; r < R; ++r)
325 const auto redundant_at = [&](std::vector<T> th) {
326 std::sort(th.begin(), th.end(), [](
const T& a,
const T& b) { return a < b; });
327 const T scale = th.back();
330 if (!(scale > zero))
return false;
332 for (std::size_t i = 1; i < th.size(); ++i)
333 if (!(T(th[i] - th[i - 1]) > gap))
return true;
336 bool isRedundant =
false;
340 std::vector<T> th(K);
341 for (std::size_t i = 0; i < K; ++i) th[i] = L(i, 0);
342 isRedundant = redundant_at(th);
344 std::vector<int> t(R, 0);
347 for (
int v : t) ts += v;
348 if (ts > 0 && redundant_at(induced(t))) {
353 while (r > 0 && t[r - 1] == N[r - 1]) t[--r] = 0;
358 std::string expr = method;
359 if (expr ==
"auto") {
360 expr = isRedundant ?
"repeated" :
"distinct";
361 }
else if (expr ==
"distinct" && isRedundant) {
363 "pfqn_explicit: Eq. (15) requires pairwise distinct induced demands, but two of them "
364 "agree to within tol. Use 'auto' or 'repeated'");
368 detail::SignedLse<T> total;
371 std::vector<T> th(K);
372 for (std::size_t i = 0; i < K; ++i) th[i] = L(i, 0);
373 total = (expr ==
"distinct") ? detail::explicit_gdistinct(th, Nt, K)
374 : detail::explicit_grepeated(th, Nt, K, tol);
377 std::vector<T> lterm;
378 std::vector<double> sterm;
379 double innerLoss = 0.0;
380 std::vector<int> t(R, 0);
383 for (
int v : t) ts += v;
385 std::vector<T> th = induced(t);
387 for (
const T& v : th)
388 if (v > thmax) thmax = v;
390 const detail::SignedLse<T> g =
391 (expr ==
"distinct") ? detail::explicit_gdistinct(th, Nt, K)
392 : detail::explicit_grepeated(th, Nt, K, tol);
393 innerLoss = std::max(innerLoss, g.lossDigits);
396 for (std::size_t r = 0; r < R; ++r) {
398 l = T(l - detail::num_factln<T>(
402 sterm.push_back(
static_cast<double>(g.sgn) *
403 (((Nsum - ts) % 2 == 0) ? 1.0 : -1.0));
408 while (r > 0 && t[r - 1] == N[r - 1]) t[--r] = 0;
412 total = detail::explicit_signed_logsumexp(lterm, sterm);
413 total.lossDigits = std::max(total.lossDigits, innerLoss);
421 if (std::isfinite(maxloss) && (total.sgn < 0 || total.lossDigits > maxloss)) {
427 if (total.sgn == 0) {
The exception types the port throws.
Dense matrix and non-owning view.
ExplicitResult< T > pfqn_explicit(const Matrix< T > &L, const std::vector< int > &N, double tol=std::numeric_limits< double >::epsilon(), const std::string &method="auto", double maxloss=std::numeric_limits< double >::infinity())
Explicit closed-form normalizing constant of a multiclass closed network.
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Return value of pfqn_explicit, mirroring [lG, G, method, lossDigits].
std::string method
expression used, "distinct" (Eq. 15) or "repeated" (Eq. 16)
T G
the normalizing constant
T lG
logarithm of the normalizing constant
double lossDigits
decimal digits lost to cancellation
bool valid
False when a caller's cancellation budget was exceeded: lG and G are then meaningless and the caller ...