303 const std::size_t M =
sn.nstations, K =
sn.nclasses;
304 const double eps = std::numeric_limits<double>::epsilon();
307 std::vector<std::vector<std::size_t>> start(M, std::vector<std::size_t>(K, 0));
308 std::vector<std::vector<std::size_t>> len(M, std::vector<std::size_t>(K, 0));
310 for (std::size_t i = 0; i < M; ++i)
311 for (std::size_t r = 0; r < K; ++r) {
313 if (detail::fluid_ddpp_active(
sn, i, r)) {
314 len[i][r] =
sn.service[i][r].D0.rows();
318 std::vector<std::size_t> st(n), cl(n);
319 std::vector<double> exitRate(n, 0.0);
320 std::vector<std::vector<std::vector<double>>> pie(M, std::vector<std::vector<double>>(K));
321 for (std::size_t i = 0; i < M; ++i)
322 for (std::size_t r = 0; r < K; ++r) {
323 if (len[i][r] == 0)
continue;
325 pie[i][r] = detail::fluid_pie(d);
326 for (std::size_t k = 0; k < len[i][r]; ++k) {
327 const std::size_t a = start[i][r] + k;
337 const std::size_t S =
sn.nof_stateful();
338 const bool have_rt =
sn.rt.rows() == S * K;
341 std::vector<std::size_t> keep;
343 for (std::size_t i = 0; i < M; ++i) {
344 const std::size_t isf =
sn.stateful_of_station(i + 1) - 1;
345 for (std::size_t r = 0; r < K; ++r) keep.push_back(isf * K + r);
348 for (std::size_t a = 0; a < S * K; ++a)
353 for (std::size_t i = 0; i < M; ++i)
354 for (std::size_t r = 0; r < K; ++r) {
355 if (len[i][r] == 0)
continue;
357 for (std::size_t k = 0; k < len[i][r]; ++k) {
358 const std::size_t a = start[i][r] + k;
359 for (std::size_t kk = 0; kk < len[i][r]; ++kk) {
361 if (kk != k && v > 0.0) Woff(a, start[i][r] + kk) += v;
363 if (exitRate[a] <= 0.0)
continue;
364 for (std::size_t j = 0; j < M; ++j)
365 for (std::size_t s = 0; s < K; ++s) {
366 const double p = P(i * K + r, j * K + s);
367 if (!(p > 0.0) || len[j][s] == 0)
continue;
368 for (std::size_t kk = 0; kk < len[j][s]; ++kk) {
369 const std::size_t b = start[j][s] + kk;
370 const double pj = kk < pie[j][s].size() ? pie[j][s][kk] : 0.0;
371 if (b != a && pj > 0.0) Woff(a, b) += exitRate[a] * p * pj;
377 double rateScale = 1e-12;
378 for (std::size_t a = 0; a < n; ++a) {
380 for (std::size_t b = 0; b < n; ++b) {
381 WdT(b, a) = Woff(a, b);
385 for (std::size_t b = 0; b < n; ++b) rateScale = std::max(rateScale, std::fabs(WdT(b, a)));
388 std::vector<double> c(M);
389 std::vector<std::vector<std::size_t>> members(M);
390 for (std::size_t i = 0; i < M; ++i) c[i] =
sn.stations[i].nservers;
391 for (std::size_t a = 0; a < n; ++a) members[st[a]].push_back(a);
395 for (std::size_t b = 0; b < n; ++b)
396 for (std::size_t a = 0; a < n; ++a)
397 if (Woff(a, b) > 0.0) ++nj;
401 for (std::size_t b = 0; b < n; ++b)
402 for (std::size_t a = 0; a < n; ++a)
403 if (Woff(a, b) > 0.0) {
410 const std::size_t d = Pb.
cols();
414 std::vector<double> x(n, 0.0);
416 const std::vector<double> njobs =
sn.njobs();
417 for (std::size_t r = 0; r < K; ++r) {
418 const double Nr = njobs[r];
419 if (!(std::isfinite(Nr) && Nr > 0.0))
continue;
420 const std::size_t ref =
sn.classes[r].refstat - 1;
421 if (ref >= M || len[ref][r] == 0)
423 "solver_fluid_ddpp: class " + std::to_string(r + 1) +
424 " has no service process at its reference station, so its population "
425 "has no coordinate to start from");
426 for (std::size_t k = 0; k < len[ref][r]; ++k)
427 x[start[ref][r] + k] += Nr * (k < pie[ref][r].size() ? pie[ref][r][k] : 0.0);
432 const std::size_t iterMax = std::max<std::size_t>(100,
opt.iter_max);
433 const double tolF = 1e-12 * rateScale * std::max(1.0, Ntot);
434 double h = 1.0 / rateScale;
435 const double hmax = 1e10 / rateScale;
436 std::vector<double> F =
mulvec(WdT, detail::fluid_ddpp_theta(x, members, c));
437 double nF = detail::fluid_ddpp_norminf(F);
438 std::size_t iters = 0;
439 while (nF > tolF && iters < iterMax) {
442 for (std::size_t a = 0; a < n; ++a)
443 for (std::size_t b = 0; b < n; ++b) A(a, b) = -A(a, b);
444 for (std::size_t a = 0; a < n; ++a) A(a, a) += 1.0 / h;
445 const std::vector<double> dx = detail::fluid_ddpp_solve(A, F);
446 const double alpha = detail::fluid_ddpp_steplimit(x, dx);
447 for (std::size_t a = 0; a < n; ++a) x[a] = std::max(0.0, x[a] + alpha * dx[a]);
448 std::vector<double> Fnew =
mulvec(WdT, detail::fluid_ddpp_theta(x, members, c));
449 const double nFnew = detail::fluid_ddpp_norminf(Fnew);
450 const double ratio = nF / std::max(nFnew, std::numeric_limits<double>::min());
451 h = std::min(hmax, h * std::min(10.0, std::max(0.5, ratio)));
455 for (
int polish = 0; polish < 3 && d > 0; ++polish) {
457 if (detail::fluid_ddpp_rcond(Jr) < eps)
break;
458 const std::vector<double> rdx = detail::fluid_ddpp_solve(Jr,
mulvec(PbT, F));
459 std::vector<double> dx =
mulvec(Pb, rdx);
460 for (
double& z : dx) z = -z;
461 const double alpha = detail::fluid_ddpp_steplimit(x, dx);
462 for (std::size_t a = 0; a < n; ++a) x[a] = std::max(0.0, x[a] + alpha * dx[a]);
463 F =
mulvec(WdT, detail::fluid_ddpp_theta(x, members, c));
465 nF = detail::fluid_ddpp_norminf(F);
466 if (!(nF <= 1e3 * tolF))
468 "solver_fluid_ddpp: the fluid fixed point was not found in " +
469 std::to_string(iters) +
" continuation steps (drift residual " +
470 std::to_string(nF) +
" against a tolerance of " +
471 std::to_string(1e3 * tolF) +
")");
474 for (std::size_t i = 0; i < M; ++i) {
475 if (!std::isfinite(c[i]) || members[i].empty())
continue;
477 for (std::size_t a : members[i]) Xi += x[a];
478 if (std::fabs(Xi - c[i]) <= 1e-6 * std::max(1.0, c[i]))
480 "solver_fluid_ddpp: the fluid fixed point puts " + std::to_string(Xi) +
481 " jobs at station " + std::to_string(i + 1) +
", which has " + std::to_string(c[i]) +
482 " servers: the drift has a kink there, so the 1/N expansion has no Jacobian to expand "
483 "around. Use method 'minnormal' or an exact solver");
485 const Matrix<double> Dth = detail::fluid_ddpp_dtheta(x, members, c);
490 double maxRe = -std::numeric_limits<double>::infinity();
491 for (
const std::complex<double>& z :
eig_values(Jr)) maxRe = std::max(maxRe, z.real());
492 if (maxRe >= -std::sqrt(eps) * rateScale)
494 "solver_fluid_ddpp: the fluid fixed point is not exponentially stable on the reachable "
495 "subspace (largest Jacobian eigenvalue has real part " + std::to_string(maxRe) +
496 "), so the stationary covariance and the 1/N correction do not exist");
501 const std::vector<double> th0 = detail::fluid_ddpp_theta(x, members, c);
503 for (std::size_t a = 0; a < n; ++a)
504 for (std::size_t b = 0; b < n; ++b) {
505 const double rj = th0[a] * Woff(a, b);
506 if (rj == 0.0)
continue;
515 detail::fluid_symmetrize(Qr);
517 for (std::size_t a = 0; a < d; ++a)
518 for (std::size_t b = 0; b < d; ++b) negQr(a, b) = -Qr(a, b);
520 detail::fluid_symmetrize(Wr);
522 detail::fluid_symmetrize(Sigma);
526 const std::vector<double> bh = detail::fluid_ddpp_hesscontract(x, members, c, Sigma);
527 std::vector<double> V(n, 0.0);
529 const std::vector<double> rv = detail::fluid_ddpp_solve(Jr,
mulvec(PbT,
mulvec(WdT, bh)));
530 const std::vector<double> pv =
mulvec(Pb, rv);
531 for (std::size_t a = 0; a < n; ++a) V[a] = -pv[a];
533 const double normV = detail::fluid_ddpp_norm2(V), normX = detail::fluid_ddpp_norm2(x);
534 if (normV > 0.5 * std::max(normX, std::sqrt(eps)))
536 "solver_fluid_ddpp: the 1/N correction (norm " + std::to_string(normV) +
537 ") is not small against the fluid fixed point (norm " + std::to_string(normX) +
538 "), so the expansion is outside its range of validity at this population");
539 const std::vector<double> dthV =
mulvec(Dth, V);
540 std::vector<double> thR(n), xR(n);
541 for (std::size_t a = 0; a < n; ++a) {
542 thR[a] = th0[a] + dthV[a] + bh[a];
547 const auto metrics = [&](
const std::vector<double>& xs,
const std::vector<double>& th,
552 for (std::size_t a = 0; a < n; ++a) {
553 QN(st[a], cl[a]) += xs[a];
554 TN(st[a], cl[a]) += exitRate[a] * th[a];
555 if (std::isfinite(c[st[a]])) UN(st[a], cl[a]) += th[a] / c[st[a]];
557 for (std::size_t i = 0; i < M; ++i)
559 for (std::size_t r = 0; r < K; ++r) UN(i, r) = QN(i, r);
562 metrics(xR, thR, out.
QN, out.
UN, out.
TN);
564 for (std::size_t i = 0; i < M; ++i)
565 for (std::size_t r = 0; r < K; ++r)
566 if (out.
TN(i, r) > 0.0) out.
RN(i, r) = out.
QN(i, r) / out.
TN(i, r);
570 out.
XN.assign(K, 0.0);
571 out.
CN.assign(K, 0.0);
572 for (std::size_t k = 0; k < K; ++k) {
573 const std::size_t rs =
sn.classes[k].refstat;
574 if (rs >= 1 && rs <= M) out.
XN[k] = out.
TN(rs - 1, k);
576 for (std::size_t i = 0; i < M; ++i) q += out.
QN(i, k);
577 if (out.
XN[k] > 0.0) out.
CN[k] = q / out.
XN[k];
582 metrics(x, th0, report->QNfluid, report->UNfluid, report->TNfluid);
584 report->residual = nF;