555 return ::line::method_registry::methods(
"LN");
567 opt.layer_ssa.verbose =
false;
569 for (
char& ch : opt.interlock_refpath_scope)
570 ch =
char(std::tolower(
static_cast<unsigned char>(ch)));
571 if (opt.interlock_refpath_scope !=
"merging" && opt.interlock_refpath_scope !=
"all")
572 throw InputError(
"Unknown config.interlock_refpath_scope '" +
573 opt.interlock_refpath_scope +
"', use 'merging' or 'all'.");
574 if (std::isnan(opt.interlock_maxpaths) || opt.interlock_maxpaths < 0.0)
575 throw InputError(
"config.interlock_maxpaths must be a non-negative number.");
576 validate_nlp_options();
583 const std::vector<double>&
nlp_solution()
const {
return nlp_solution_; }
594 o.
alpha = opt.nlp_alpha;
595 o.
fork = opt.nlp_fork;
596 return ::line::lqn::lqn_nlp_program(lqn, o);
605 if (opt.method ==
"mw.upper" || opt.method ==
"mw.lower")
return box_bounds();
607 if (lnmethod ==
"nlp")
return nlp_analyzer();
613 return trapped_overlay(is_ph_encoding() ? aggregate_ph() : aggregate());
623 if (!zerocap.any_trapped())
return s;
625 for (std::size_t idx = 1; idx <= lqn.nidx; ++idx) {
626 if (!zerocap.onroute[idx])
continue;
628 switch (lqn.type[idx]) {
629 case LqnElement::TASK:
631 if (ignore[idx] || !s.
defined_Q[idx]) s.
QN[idx] = Tzero();
632 s.
QN[idx] = T(s.
QN[idx] + held);
634 if (ignore[idx] && !lqn.isref[idx]) {
639 case LqnElement::ENTRY:
640 case LqnElement::ACTIVITY:
645 if (lqn.type[idx] == LqnElement::ACTIVITY) {
670 if (lnmethod ==
"nlp")
672 "method='nlp' solves for means only and builds no layers, so there is no response-time "
673 "distribution to report. Use method='moment3' for getCdfRespT.");
674 if (lqn.nentries == 0)
return entrycdfrespt;
675 if (entrycdfrespt.size() <= lqn.nentries || entrycdfrespt[1].empty()) {
682 if (is_ph_encoding())
684 "getCdfRespT needs the routing encoding of the activity graph, which "
685 "method='srvn.ph' does not build. Rebuild the solver with "
686 "method='srvn.cs' or method='moment3'.");
693 const std::string saved = opt.method;
694 const std::string saved_resolved = lnmethod;
695 opt.method =
"moment3";
696 lnmethod =
"moment3";
700 lnmethod = saved_resolved;
702 return entrycdfrespt;
713 if (lnmethod ==
"nlp")
715 "method='nlp' states the steady state as one program and builds no layers, so there is "
716 "no layer transient to integrate. Use a layered method ('srvn.cs', 'flat.cs') for getTranAvg.");
717 if (opt.ln_transient ==
"decoupled")
return tran_avg_decoupled();
718 if (opt.ln_transient ==
"coupled")
return tran_avg_coupled();
719 throw InputError(
"SolverLN: unknown ln_transient mode '" + opt.ln_transient +
720 "' (use 'coupled' or 'decoupled')");
737 if (lnmethod ==
"nlp")
739 "method='nlp' states the model as one program and builds no layers, so there are no "
740 "per-layer sensitivities to report. Use a layered method ('srvn', 'srvn.ph', 'flat.cs') "
741 "for getSensitivityTable.");
742 if (results.empty()) iterate();
746 for (std::size_t e = 0; e < ensemble.size(); ++e) {
749 const bool exact_available =
750 opt.layer_solver ==
"mva" || opt.layer_solver ==
"nc";
752 ensemble[e], so, exact_available && !fj_tr[e].active(),
753 [
this, e]() {
return this->solve_layer(e); });
757 row.layer = ensemble[e].name;
764 out.
rows.push_back(row);
771 if (m.empty())
continue;
787 const bool upper = opt.method !=
"mw.lower";
792 const std::size_t N = lqn.nidx;
793 auto blank = [&](std::vector<T>& v, std::vector<bool>& d) {
794 v.assign(N + 1, Tzero());
795 d.assign(N + 1,
false);
803 for (std::size_t i = 1; i <= N; ++i) {
804 if (b.defined_T[i]) {
805 s.
TN[i] = upper ? b.TN_up[i] : b.TN_lo[i];
808 if (b.defined_U[i]) {
809 s.
UN[i] = upper ? b.UN_up[i] : b.UN_lo[i];
819 std::size_t
nlayers()
const {
return ensemble.size(); }
820 const std::vector<qn::Layer<T>>&
layers()
const {
return ensemble; }
832 const std::size_t E = ensemble.size();
837 for (std::size_t e = 0; e < E; ++e) {
840 b.
msz[e] = ensemble[e].nstations;
841 b.
ksz[e] = ensemble[e].nclasses;
858 if (results.empty() || results.back().size() < ensemble.size())
859 throw InputError(
"SolverLN::get_block_avg reads the layer averages of the last fixed-point "
860 "iterate; call get_ensemble_avg first");
867 for (std::size_t i = 0; i < b.
msz[e] && i < S.
rows(); ++i)
868 for (std::size_t r = 0; r < b.
ksz[e] && r < S.
cols(); ++r)
870 D(b.
roff[e] + i, b.
coff[e] + r) = S(i, r);
872 for (std::size_t e = 0; e < ensemble.size(); ++e) {
903 const std::size_t E = ensemble.size();
904 layer_tran_init.assign(E, std::vector<double>());
905 if (n.
empty())
return;
909 "SolverLN::init_from_marginal: the marginal is " + std::to_string(n.
rows()) +
"x" +
910 std::to_string(n.
cols()) +
" where the block-diagonal union of the layers is " +
911 std::to_string(b.
M) +
"x" + std::to_string(b.
K) +
912 "; the aggregate view is layerBlocks', not the LQN element count");
913 for (std::size_t e = 0; e < E; ++e) {
916 std::vector<double> y(lay.
nstates, 0.0);
918 for (std::size_t i = 0; i < L.
nstations && i < b.
msz[e]; ++i)
919 for (std::size_t r = 0; r < L.
nclasses && r < b.
ksz[e]; ++r) {
920 if (!lay.
enabled[i][r])
continue;
921 const double q = std::max(0.0, n(b.
roff[e] + i, b.
coff[e] + r));
922 y[lay.
qidx[i][r]] = q;
923 if (q > 0.0) any =
true;
929 if (any) layer_tran_init[e] = y;
976 std::vector<qn::Layer<T>> ensemble;
977 std::vector<long> idxhash;
978 std::vector<bool> ignore;
983 std::size_t idx, aidx, node, cls;
985 std::vector<UpdRow> servt_map, thinkt_map, call_map, actthinkt_map;
995 std::vector<UpdRow> arv_call_map;
998 std::size_t idx, tidx_caller, eidx, nodefrom, nodeto, cfrom, cto;
1000 std::vector<RouteRow> route_map;
1001 std::vector<std::size_t> unique_route_idx;
1002 std::vector<std::size_t> route_reset, svc_reset;
1005 std::string interlockMethod =
"ilrate";
1013 std::size_t idx, node, cls;
1017 std::size_t fromentry;
1019 std::vector<RefPathRow> refpath_map;
1021 std::vector<std::array<std::size_t, 3>> refpath_keys;
1022 std::vector<std::size_t> refpath_group;
1024 std::vector<double> refpath_stage_prev;
1025 std::vector<T> refpath_stage_prev_v;
1031 std::map<std::size_t, std::vector<std::size_t>> layer_normclass;
1033 std::vector<T> entryvisits;
1035 Matrix<double> njobs;
1038 std::vector<T> servt, residt, tput, util, thinkt;
1039 std::vector<T> callservt, callresidt;
1046 bool has_phase2 =
false;
1047 std::vector<T> servt_ph1, servt_ph2, prOvertake;
1049 std::vector<T> entry_servt_last, entry_servt_ph2;
1050 std::vector<T> join_tail;
1052 std::set<std::size_t> single_replica_tasks;
1059 std::map<std::size_t, std::size_t> host_all_replica_tasks;
1060 std::vector<Distrib<T>> servtproc, thinkproc, thinktproc, tputproc, callservtproc;
1066 std::vector<fluid::FluidPassage> servtcdf, callservtcdf;
1067 std::vector<LnCdf> entrycdfrespt;
1068 std::vector<mam::AphPair<T>> entryproc;
1070 std::vector<std::vector<std::vector<fluid::FluidPassage>>> cdf_repo;
1071 std::vector<double> servt_prev, residt_prev, tput_prev, thinkt_prev;
1072 std::vector<double> callservt_prev, callresidt_prev;
1073 std::vector<T> servt_prev_v, residt_prev_v, tput_prev_v, thinkt_prev_v, callservt_prev_v;
1074 Matrix<T> servtmatrix;
1077 Matrix<T> il_all, il_ph1;
1078 std::vector<std::vector<std::size_t>> il_common_entries, il_src_all, il_src_ph2;
1084 std::vector<std::vector<std::vector<double>>> layer_interlock;
1085 std::vector<double> il_num_sources;
1087 std::vector<std::vector<LayerResult<T>>> results;
1097 std::vector<LayerResult<T>> results_raw_last;
1098 bool results_raw_valid =
false;
1099 std::vector<Matrix<T>> layer_init_sol;
1104 std::vector<std::vector<double>> layer_tran_init;
1116 std::vector<mva::FjMmt<T>> fj_tr;
1117 std::vector<std::vector<T>> fj_lambda;
1118 double relax_omega = 1.0;
1120 std::shared_ptr<LnStochController<T>> stoch_ctl;
1121 long averagingstart = -1;
1122 bool hasconverged =
false;
1124 bool warmstarted_ =
false;
1126 std::vector<double> nlp_solution_;
1127 double nlp_objective_ = std::numeric_limits<double>::quiet_NaN();
1129 bool moment_pass_done =
false;
1130 std::vector<double> maxitererr;
1131 int iterations_done = 0;
1132 bool did_converge =
false;
1134 std::size_t NT()
const {
return lqn.tshift + lqn.ntasks; }
1136 static T Tzero() {
return num_traits<T>::from_int(0); }
1137 static T Tone() {
return num_traits<T>::from_int(1); }
1138 static double dbl(
const T& x) {
return num_traits<T>::to_double(x); }
1150 reject_unsupported();
1153 const std::size_t N = lqn.nidx;
1154 ignore.assign(N + 1,
false);
1157 std::vector<long> comp(N + 1, -1);
1159 for (std::size_t v = 1; v <= N; ++v) {
1160 if (comp[v] >= 0)
continue;
1161 std::vector<std::size_t> stack{v};
1163 while (!stack.empty()) {
1164 const std::size_t u = stack.back();
1166 for (std::size_t w : lqn.graph.succ(u))
1171 for (std::size_t w : lqn.graph.pred(u))
1180 std::vector<bool> has_ref(ncomp,
false);
1181 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
1182 const std::size_t tidx = lqn.tshift + t;
1183 if (lqn.sched[tidx] == SchedStrategy::REF) has_ref[comp[tidx]] =
true;
1185 for (std::size_t v = 1; v <= N; ++v)
1186 if (!has_ref[comp[v]]) ignore[v] =
true;
1191 if (!zerocap.reason.empty())
throw UnsupportedError(ErrorKind{
"Structure"}, zerocap.reason);
1192 if (zerocap.any_trapped()) {
1193 if (ln_requested_method(opt.method) ==
"nlp")
1195 "method='nlp' cannot solve a model whose reference task is trapped at a "
1196 "zero-capacity element; use a layered method.");
1197 if (num_traits<T>::is_exact)
1199 "A reference task trapped at a zero-capacity element has infinite response "
1200 "times, which exact arithmetic cannot represent; solve it in double precision.");
1202 for (std::size_t v = 1; v <= N; ++v)
1203 if (zerocap.ignore[v]) ignore[v] =
true;
1204 if (zerocap.any_trapped()) {
1206 std::vector<double> liveMult = lqn.mult;
1207 for (std::size_t i = 1; i < liveMult.size(); ++i)
1208 if (zerocap.trapped[i]) liveMult[i] = 0.0;
1216 for (std::size_t i = 1; i <= N; ++i) {
1217 servtproc[i] = lqn.hostdem[i];
1218 thinkproc[i] = lqn.think[i];
1221 for (std::size_t c = 1; c <= lqn.ncalls; ++c)
1222 callservtproc[c] = lqn.hostdem[lqn.callpair_dst[c]];
1226 entryvisits.assign(N + 1, Tone());
1227 refpath_map.clear();
1228 layer_normclass.clear();
1230 njobs = Matrix<double>(NT() + 1, NT() + 1, 0.0);
1235 if (ln_requested_method(opt.method) ==
"nlp" || !zerocap.live) {
1236 if (zerocap.live) lnmethod =
"nlp";
1242 std::vector<bool> rr(NT() + 1,
false), sr(NT() + 1,
false);
1243 for (
const RouteRow& r : route_map) rr[r.idx] =
true;
1244 for (
const UpdRow& r : thinkt_map) sr[r.idx] =
true;
1245 for (
const UpdRow& r : call_map) sr[r.idx] =
true;
1251 for (
const RefPathRow& r : refpath_map) sr[r.idx] =
true;
1254 for (
const UpdRow& r : arv_call_map) rr[r.idx] =
true;
1255 for (std::size_t i = 1; i <= NT(); ++i) {
1256 if (rr[i] && idxhash[i] >= 0) route_reset.push_back(std::size_t(idxhash[i]));
1257 if (sr[i] && idxhash[i] >= 0) svc_reset.push_back(std::size_t(idxhash[i]));
1259 std::sort(route_reset.begin(), route_reset.end());
1260 route_reset.erase(std::unique(route_reset.begin(), route_reset.end()), route_reset.end());
1261 std::sort(svc_reset.begin(), svc_reset.end());
1262 svc_reset.erase(std::unique(svc_reset.begin(), svc_reset.end()), svc_reset.end());
1265 refpath_keys.clear();
1266 refpath_group.clear();
1267 for (
const RefPathRow& r : refpath_map) refpath_keys.push_back({r.idx, r.node, r.cls});
1268 std::sort(refpath_keys.begin(), refpath_keys.end());
1269 refpath_keys.erase(std::unique(refpath_keys.begin(), refpath_keys.end()),
1270 refpath_keys.end());
1271 for (
const RefPathRow& r : refpath_map) {
1272 const std::array<std::size_t, 3> key{r.idx, r.node, r.cls};
1273 refpath_group.push_back(std::size_t(
1274 std::lower_bound(refpath_keys.begin(), refpath_keys.end(), key) -
1275 refpath_keys.begin()));
1277 if (refpath_map.empty() && interlockMethod ==
"refpath") {
1284 interlockMethod =
"ilrate";
1286 refpath_stage_prev.assign(refpath_keys.size(), std::numeric_limits<double>::quiet_NaN());
1287 refpath_stage_prev_v.assign(refpath_keys.size(), Tzero());
1295 void detect_phase2() {
1297 for (std::size_t a = 1; a <= lqn.nacts; ++a)
1298 if (lqn.actphase[a] > 1) has_phase2 =
true;
1302 for (std::size_t aidx = 1; aidx <= lqn.nidx && aidx < lqn.actquorum.size(); ++aidx) {
1303 if (lqn.actquorum[aidx] < 1)
continue;
1304 std::size_t nbranches = 0;
1305 for (std::size_t tail = 1; tail <= lqn.nidx; ++tail) {
1306 if (tail == aidx || lqn.actpretype[tail] != PrecedenceType::PRE_AND)
continue;
1307 for (std::size_t sx : lqn.graph.succ(tail))
1308 if (sx == aidx) ++nbranches;
1310 if (lqn.actquorum[aidx] < nbranches) has_phase2 =
true;
1323 bool is_fwd_target(std::size_t eidx)
const {
1324 for (std::size_t c = 1; c <= lqn.ncalls; ++c)
1325 if (lqn.calltype[c] == CallType::FWD && lqn.callpair_dst[c] == eidx)
return true;
1342 double open_arrival_rate_of(std::size_t tidx)
const {
1343 if (lqn.isref[tidx])
return 0.0;
1344 for (std::size_t e : lqn.entriesof[tidx])
1345 if (lqn.issynccaller.any_col(e) || lqn.isasynccaller.any_col(e) || is_fwd_target(e))
1348 for (std::size_t e : lqn.entriesof[tidx]) {
1349 if (!lqn.has_arrival[e])
continue;
1350 const double m = dbl(lqn.arrival[e].mean);
1363 T sync_call_residt_of(
const std::vector<std::size_t>& acts)
const {
1365 for (std::size_t aidx : acts)
1366 for (std::size_t cidx : lqn.callsof[aidx])
1367 if (lqn.calltype[cidx] == CallType::SYNC && cidx < callresidt.size() &&
1368 std::isfinite(dbl(callresidt[cidx])))
1369 total += callresidt[cidx];
1374 T open_arrival_call_residt(std::size_t tidx,
double arvrate)
const {
1376 if (!(arvrate > 0.0))
return total;
1377 for (std::size_t e : lqn.entriesof[tidx]) {
1378 if (!lqn.has_arrival[e])
continue;
1379 const double m = dbl(lqn.arrival[e].mean);
1381 total += T(num_traits<T>::from_double(1.0 / m / arvrate) *
1382 sync_call_residt_of(lqn.actsof[e]));
1388 bool has_cache_node(std::size_t e)
const {
1389 for (std::size_t n = 0; n < ensemble[e].nodes.size(); ++n)
1390 if (ensemble[e].nodes[n].nodetype == NodeType::Cache)
return true;
1395 bool async_only_activity(std::size_t aidx)
const {
1396 if (aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts)
return false;
1397 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
1398 const std::size_t eidx = lqn.eshift + e;
1399 if (lqn.graph.get(eidx, aidx) == Tzero())
continue;
1400 return lqn.isasynccaller.any_col(eidx) && !lqn.issynccaller.any_col(eidx);
1415 void reject_unsupported()
const {}
1422 void resolve_interlock_method() {
1423 const LnInterlockChoice ch =
1425 interlockMethod = ch.method;
1426 if (!ch.why.empty()) std::cerr <<
"[LINE] Warning: " << ch.why <<
"\n";
1432 void build_layers() {
1439 const std::string requested = ln_requested_method(opt.method);
1440 assert_call_groups(requested ==
"flat.cs");
1441 if (requested ==
"flat.cs") {
1442 lnmethod =
"flat.cs";
1443 resolve_interlock_method();
1447 if (requested ==
"flat.ph") {
1452 ph_laws_ready =
false;
1453 lnmethod =
"flat.ph";
1454 assert_warmstart_encoding();
1455 build_layers_ph(
true);
1458 if (requested ==
"srvn.ph" || requested ==
"srvn") {
1459 ph_laws_ready =
false;
1461 if (requested ==
"srvn.ph" || (!has_interlock() && probe_srvn_ph())) {
1462 lnmethod =
"srvn.ph";
1463 assert_warmstart_encoding();
1468 lnmethod = (requested ==
"moment3") ?
"moment3" :
"srvn.cs";
1469 resolve_interlock_method();
1470 std::vector<qn::Layer<T>> raw(NT() + 1);
1471 std::vector<bool> present(NT() + 1,
false);
1473 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx) {
1474 if (ignore[hidx])
continue;
1475 std::vector<std::size_t> hcallers;
1476 for (std::size_t c : lqn.tasksof[hidx])
1477 if (!ignore[c]) hcallers.push_back(c);
1478 build_layer(raw[hidx], {hidx}, hcallers,
true,
false);
1479 present[hidx] =
true;
1481 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
1482 const std::size_t tidx = lqn.tshift + t;
1483 if (ignore[tidx] || lqn.isref[tidx])
continue;
1484 bool any_caller = lqn.iscaller.any_row(tidx) || lqn.iscaller.any_col(tidx);
1485 if (!any_caller)
continue;
1487 std::vector<std::size_t> callers;
1488 for (std::size_t ct = 1; ct <= lqn.ntasks; ++ct) {
1489 const std::size_t c = lqn.tshift + ct;
1490 if (ignore[c])
continue;
1492 for (std::size_t e : lqn.entriesof[tidx])
1493 if (lqn.iscaller.get(c, e)) calls =
true;
1494 if (calls) callers.push_back(c);
1496 if (callers.empty())
continue;
1497 build_layer(raw[tidx], {tidx}, callers,
false,
false);
1498 present[tidx] =
true;
1501 idxhash.assign(lqn.nidx + 1, -1);
1503 for (std::size_t i = 1; i <= NT(); ++i)
1505 idxhash[i] = next++;
1506 ensemble.push_back(std::move(raw[i]));
1508 layer_init_sol.assign(ensemble.size(), Matrix<T>());
1527 void assert_call_groups(
bool flat)
const {
1528 if (lqn.callgroups.empty())
return;
1531 "Call groups routed by a routing strategy require the squashed layering; use "
1532 "method='flat'. Under srvn the targets never share a submodel, so the dispatch "
1533 "order cannot be represented.");
1534 if (!layer_solver_is_stochastic())
1536 "Routed call groups need a layer solver that resolves the strategy from the "
1537 "state; set layer_solver='ssa', 'ldes' or 'jmt'. MVA, NC and FLD read the routing matrix, into "
1538 "which refresh_routing has expanded the strategy as a uniform split, and would "
1539 "return that split under a round-robin or JSQ label.");
1551 std::vector<std::size_t> flat_server_set()
const {
1552 std::vector<std::size_t> servers;
1553 for (std::size_t i = 1; i <= NT(); ++i) {
1554 if (lqn.repl[i] > 1.0)
1556 "Flat layering does not support replicated processors or tasks, use the "
1557 "default 'srvn' layering.");
1560 "Flat layering does not support cache tasks, use the default 'srvn' "
1562 if (lqn.hassetup[i])
1564 "Flat layering does not support setup tasks, use the default 'srvn' "
1567 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx)
1568 if (!ignore[hidx] && !lqn.tasksof[hidx].empty()) servers.push_back(hidx);
1569 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
1570 const std::size_t tidx = lqn.tshift + t;
1571 if (ignore[tidx] || lqn.isref[tidx])
continue;
1572 if (!lqn.iscaller.any_row(tidx) && !lqn.iscaller.any_col(tidx))
continue;
1573 bool has_task_caller =
false;
1574 for (std::size_t eidx : lqn.entriesof[tidx])
1575 for (std::size_t c = 1; c <= lqn.ntasks; ++c)
1576 if (lqn.iscaller.get(lqn.tshift + c, eidx)) has_task_caller =
true;
1577 if (has_task_caller) servers.push_back(tidx);
1579 if (servers.empty())
1581 "Flat layering found no server: the model has no processor with tasks.");
1593 void build_flat_layer() {
1594 const std::vector<std::size_t> flat_servers = flat_server_set();
1595 std::vector<std::size_t> flat_callers;
1596 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
1597 const std::size_t tidx = lqn.tshift + t;
1598 if (!ignore[tidx]) flat_callers.push_back(tidx);
1601 build_layer(layer, flat_servers, flat_callers,
false,
true);
1603 ensemble.push_back(std::move(layer));
1604 idxhash.assign(lqn.nidx + 1, -1);
1605 for (std::size_t s : flat_servers) idxhash[s] = 0;
1606 layer_init_sol.assign(ensemble.size(), Matrix<T>());
1614 void build_fork_views() {
1615 fj_tr.assign(ensemble.size(), mva::FjMmt<T>());
1616 fj_lambda.assign(ensemble.size(), {});
1617 for (std::size_t e = 0; e < ensemble.size(); ++e) {
1618 if (!ensemble[e].has_fork())
continue;
1625 if (ensemble[e].sourceIdx != 0)
1627 "SolverLN: layer '" + ensemble[e].name +
1628 "' carries both an AND fork and an open stream (an async call or an entry "
1629 "arrival); the fork-join transform needs a Source of its own");
1631 fj_lambda[e].assign(fj_tr[e].V.classes.size() + 1,
1652 const std::shared_ptr<std::vector<std::vector<std::size_t>>>& chaincols, std::size_t R) {
1653 return [f, cols, chaincols, R](
const std::vector<T>& n) -> std::vector<T> {
1654 const std::size_t L = n.size();
1655 const std::vector<std::vector<std::size_t>>& use =
1656 (L == R || chaincols->empty()) ? cols : *chaincols;
1657 std::vector<T> nop(use.size(), num_traits<T>::from_int(0));
1658 for (std::size_t j = 0; j < use.size(); ++j)
1659 for (std::size_t k = 0; k < use[j].size(); ++k)
1660 if (use[j][k] < L) nop[j] = T(nop[j] + n[use[j][k]]);
1661 const std::vector<T> w = f(nop);
1662 std::vector<T> v(L, num_traits<T>::from_int(1));
1663 if (w.empty())
return v;
1664 for (std::size_t j = 0; j < use.size(); ++j) {
1665 const T wj = w[j < w.size() ? j : w.size() - 1];
1666 for (std::size_t k = 0; k < use[j].size(); ++k)
1667 if (use[j][k] < L) v[use[j][k]] = wj;
1674 static std::vector<T> layer_peak(
const std::vector<T>& peakPerOperand,
1675 const std::vector<std::vector<std::size_t>>& cols,
1677 std::vector<T> peak(R, num_traits<T>::from_int(1));
1678 if (peakPerOperand.empty())
return peak;
1679 for (std::size_t j = 0; j < cols.size(); ++j) {
1680 const T pj = peakPerOperand[j < peakPerOperand.size() ? j : peakPerOperand.size() - 1];
1681 for (std::size_t k = 0; k < cols[j].size(); ++k)
1682 if (cols[j][k] < R) peak[cols[j][k]] = pj;
1696 void apply_host_task_priorities(qn::Layer<T>& m,
const std::vector<std::size_t>& idxSet)
const {
1699 return s == SchedStrategy::HOL || s == SchedStrategy::FCFSPRPRIO ||
1700 s == SchedStrategy::FCFSPIPRIO || s == SchedStrategy::LCFSPRIO ||
1701 s == SchedStrategy::LCFSPRPRIO || s == SchedStrategy::LCFSPIPRIO ||
1702 s == SchedStrategy::PSPRIO || s == SchedStrategy::DPSPRIO ||
1703 s == SchedStrategy::GPSPRIO;
1705 const std::size_t R = m.classes.size();
1706 std::vector<std::size_t> owner(R + 1, 0);
1707 auto own = [&](std::size_t k, std::size_t t) {
1708 if (k >= 1 && k <= R && owner[k] == 0) owner[k] = t;
1710 for (
const auto& a : m.attr_tasks) own(a.first, a.second);
1711 for (
const auto& a : m.attr_entries) own(a.first, lqn.parent[a.second]);
1712 for (
const auto& a : m.attr_activities) own(a.first, lqn.parent[a.second]);
1713 for (
const auto& a : m.attr_calls) own(a[0], lqn.parent[a[2]]);
1714 for (std::size_t s : idxSet) {
1715 if (s < 1 || s > lqn.nhosts || !is_prio(lqn.sched[s]))
continue;
1717 for (std::size_t t = lqn.tshift + 1; t <= lqn.tshift + lqn.ntasks; ++t)
1718 if (lqn.parent[t] == s) pmax = std::max(pmax, lqn.prio[t]);
1719 for (std::size_t k = 1; k <= R; ++k)
1720 if (owner[k] != 0 && lqn.parent[owner[k]] == s)
1721 m.classes[k - 1].prio = pmax - lqn.prio[owner[k]];
1734 void build_layer(qn::Layer<T>& m,
const std::vector<std::size_t>& idxSet,
1735 const std::vector<std::size_t>& callers,
bool ishostlayer,
bool flat) {
1736 const T one = Tone();
1740 const std::size_t idx = idxSet[0];
1741 m.name = flat ? lqn.hashnames[idx] +
".Flat" : lqn.hashnames[idx];
1744 const double rawrepl = lqn.repl[idx];
1745 std::size_t nreplicas = 1;
1746 if (!flat && rawrepl > 1.0 && !callers.empty()) {
1755 for (std::size_t c : callers)
1756 if (lqn.repl[c] != rawrepl) reduce =
false;
1758 for (std::size_t c : callers)
1759 if (lqn.fanout_at(c, idx) < rawrepl) reduce =
false;
1761 nreplicas = reduce ? 1 :
static_cast<std::size_t
>(std::llround(rawrepl));
1762 if (reduce && !ishostlayer) single_replica_tasks.insert(idx);
1764 const bool reduce_fanout = (nreplicas == 1 && rawrepl > 1.0 && !callers.empty());
1765 const std::vector<double>& mult = lqn.maxmult;
1769 m.clientIdx = m.add_station(qn::Station<T>{
"Clients", NodeType::Delay, SchedStrategy::INF,
1770 std::numeric_limits<double>::infinity(),
false, 0});
1771 const std::size_t clientNode = m.node_of_station(m.clientIdx);
1772 m.serverIdx = m.clientIdx + 1;
1776 std::vector<std::vector<std::size_t>> srv(lqn.nidx + 1);
1777 std::vector<std::vector<std::size_t>> srvnode(lqn.nidx + 1);
1778 m.server_idx_of.assign(lqn.nidx + 1, 0);
1779 for (std::size_t sidx : idxSet) {
1780 const bool sishost = sidx <= lqn.nhosts;
1781 srv[sidx].resize(nreplicas);
1782 for (std::size_t r = 0; r < nreplicas; ++r) {
1784 st.name = r == 0 ? lqn.hashnames[sidx]
1785 : lqn.hashnames[sidx] +
"." + std::to_string(r + 1);
1788 lqn.sched[sidx] == SchedStrategy::INF ? NodeType::Delay : NodeType::Queue;
1789 st.sched = lqn.sched[sidx];
1791 st.nservers = lqn.sched[sidx] == SchedStrategy::INF
1792 ? std::numeric_limits<double>::infinity()
1794 st.attr_ishost = flat ? sishost : ishostlayer;
1796 srv[sidx][r] = m.add_station(st);
1797 srvnode[sidx].push_back(m.node_of_station(srv[sidx][r]));
1799 m.server_idx_of[sidx] = srv[sidx][0];
1801 m.host_stations.push_back(srv[sidx][0]);
1803 m.task_stations.push_back(srv[sidx][0]);
1806 const std::vector<std::size_t>& server = srv[idx];
1807 const std::vector<std::size_t>& serverNode = srvnode[idx];
1810 auto servers_for = [&](std::size_t
elem) ->
const std::vector<std::size_t>& {
1811 static const std::vector<std::size_t> none;
1812 if (elem >= 1 && elem <= lqn.nidx && !srv[elem].empty())
return srv[
elem];
1815 auto server_nodes_for = [&](std::size_t
elem) ->
const std::vector<std::size_t>& {
1816 static const std::vector<std::size_t> none;
1817 if (elem >= 1 && elem <= lqn.nidx && !srvnode[elem].empty())
return srvnode[
elem];
1821 auto host_is_server = [&](std::size_t tidx_) {
1822 return !servers_for(lqn.parent[tidx_]).empty();
1825 auto is_layer_client = [&](std::size_t tidx_) {
1826 for (std::size_t s : idxSet)
1827 for (std::size_t e : lqn.entriesof[s])
1828 if (lqn.issynccaller.get(tidx_, e))
return true;
1832 auto set_all_servers = [&](std::size_t cl,
const Distrib<T>& d) {
1833 for (std::size_t s : idxSet)
1834 for (std::size_t st : srv[s]) m.set_service(st, cl, d);
1842 std::vector<std::size_t> group_of_call(lqn.ncalls + 1, 0);
1843 std::vector<std::vector<std::size_t>> group_members(lqn.callgroups.size() + 1);
1844 for (std::size_t g = 0; g < lqn.callgroups.size(); ++g) {
1845 const LqnCallGroup& grp = lqn.callgroups[g];
1846 for (std::size_t tgt : grp.
targets)
1847 for (std::size_t cidx : lqn.callsof[grp.
caller])
1848 if (lqn.callpair_dst[cidx] == tgt && lqn.calltype[cidx] == CallType::SYNC &&
1849 group_of_call[cidx] == 0) {
1850 group_of_call[cidx] = g + 1;
1851 group_members[g + 1].push_back(cidx);
1856 std::vector<std::size_t> grp_router(lqn.callgroups.size() + 1, 0);
1857 std::vector<std::size_t> grp_dispatch(lqn.callgroups.size() + 1, 0);
1858 std::vector<std::size_t> grp_class(lqn.callgroups.size() + 1, 0);
1861 std::vector<std::array<std::size_t, 3>> routed_group_sites;
1864 std::vector<std::size_t> acts_in_caller;
1865 for (std::size_t c : callers)
1866 for (std::size_t a : lqn.actsof[c]) acts_in_caller.push_back(a);
1867 bool hasfork =
false, hasjoin =
false;
1868 std::size_t maxfanout = 1;
1869 for (std::size_t a : acts_in_caller) {
1870 if (lqn.actposttype[a] == PrecedenceType::POST_AND) hasfork =
true;
1871 if (lqn.actpretype[a] == PrecedenceType::PRE_AND) hasjoin =
true;
1872 std::size_t nand = 0;
1873 for (std::size_t sx : lqn.graph.succ(a))
1874 if (lqn.actposttype[sx] == PrecedenceType::POST_AND) ++nand;
1875 if (nand > maxfanout) maxfanout = nand;
1877 std::size_t forkNode = 0, joinNode = 0, joinStation = 0;
1878 std::vector<std::size_t> forkRouter;
1880 forkNode = m.add_node(
"Fork_PostAnd", NodeType::Fork,
false);
1881 for (std::size_t f = 1; f <= maxfanout; ++f)
1882 forkRouter.push_back(
1883 m.add_node(
"Fork_PostAnd_" + std::to_string(f), NodeType::Router,
true));
1887 js.name =
"Join_PreAnd";
1888 js.nodetype = NodeType::Join;
1889 js.sched = SchedStrategy::INF;
1890 js.nservers = std::numeric_limits<double>::infinity();
1891 js.attr_ishost =
false;
1893 joinStation = m.add_station(js);
1894 joinNode = m.node_of_station(joinStation);
1895 if (forkNode) m.fj.emplace_back(forkNode, joinNode);
1902 bool iscachelayer = !flat && ishostlayer && !callers.empty();
1903 for (std::size_t c : callers)
1904 if (!lqn.iscache[c]) iscachelayer =
false;
1913 std::size_t cacheNode = 0;
1914 qn::CacheParam<T> cachepar;
1916 const std::size_t ct = callers[0];
1917 cachepar.nitems = lqn.nitems[ct];
1918 cachepar.itemcap = lqn.itemcap[ct];
1919 cachepar.replacestrat = lqn.replacestrat[ct];
1920 cacheNode = m.add_node(lqn.hashnames[ct], NodeType::Cache,
true);
1930 std::vector<std::size_t> async_here;
1931 for (std::size_t c : callers)
1932 for (std::size_t aidx : lqn.actsof[c])
1933 for (std::size_t cidx : lqn.callsof[aidx])
1934 if (lqn.calltype[cidx] == CallType::ASYNC &&
1935 !servers_for(lqn.parent[lqn.callpair_dst[cidx]]).empty())
1936 async_here.push_back(cidx);
1937 std::vector<std::size_t> open_entries;
1938 for (std::size_t c : callers) {
1946 for (std::size_t eidx : lqn.entriesof[c])
1947 if (lqn.has_arrival[eidx]) open_entries.push_back(eidx);
1950 std::size_t sourceStation = 0, sourceNode = 0, sinkNode = 0;
1951 if (!async_here.empty() || !open_entries.empty()) {
1953 src.name =
"Source";
1954 src.nodetype = NodeType::Source;
1956 src.sched = SchedStrategy::EXT;
1958 src.attr_ishost =
false;
1960 sourceStation = m.add_station(src);
1961 sourceNode = m.node_of_station(sourceStation);
1962 sinkNode = m.add_node(
"Sink", NodeType::Sink,
false);
1963 m.sourceIdx = sourceStation;
1964 m.sinkNode = sinkNode;
1968 std::vector<std::size_t> forkClassStack;
1971 std::vector<std::size_t> cls(lqn.nidx + 1, 0);
1972 std::vector<std::size_t> callcls(lqn.ncalls + 1, 0);
1975 std::vector<std::size_t> auxcallcls(lqn.ncalls + 1, 0);
1983 auto caller_needs_class = [&](std::size_t tidx_caller) {
1984 if (host_is_server(tidx_caller)) {
1985 if (lqn.isref[tidx_caller])
return true;
1986 for (std::size_t e : lqn.entriesof[tidx_caller])
1987 if (lqn.issynccaller.any_col(e) || lqn.isasynccaller.any_col(e) ||
1988 is_fwd_target(e) || lqn.has_arrival[e])
1991 return is_layer_client(tidx_caller);
1993 auto caller_acts_visible = [&](std::size_t tidx_caller) {
1994 return host_is_server(tidx_caller) || is_layer_client(tidx_caller);
2002 auto caller_pool = [&](std::size_t tidx_) ->
double {
2004 const bool single_replica = reduce_fanout || single_replica_tasks.count(tidx_) > 0;
2005 if (ishostlayer && !single_replica && lqn.repl[tidx_] > 1.0)
2006 host_all_replica_tasks[tidx_] = nreplicas;
2007 if (njobs(tidx_, idx) != 0.0)
return njobs(tidx_, idx);
2008 double nj = single_replica ? mult[tidx_] : mult[tidx_] * lqn.repl[tidx_];
2009 if (std::isinf(nj)) {
2011 for (std::size_t c = 1; c <= NT(); ++c)
2012 if (lqn.taskgraph.get(c, tidx_) != Tzero()) s += mult[c];
2014 if (std::isinf(nj)) {
2016 for (std::size_t c = 1; c <= NT(); ++c)
2017 if (std::isfinite(mult[c])) s2 += mult[c] * lqn.repl[c];
2018 nj = std::min(s2, 1000.0);
2034 std::vector<bool> rp_member(lqn.nidx + 1,
false);
2035 std::vector<bool> rp_spliced(lqn.nidx + 1,
false);
2036 std::vector<bool> rp_hop(lqn.nidx + 1,
false);
2037 std::vector<bool> rp_expand(lqn.ncalls + 1,
false);
2038 std::vector<std::size_t> rp_child_task(lqn.ncalls + 1, 0);
2039 std::vector<std::size_t> rp_child_entry(lqn.ncalls + 1, 0);
2040 std::vector<T> rp_ret_share(lqn.ncalls + 1, Tzero());
2041 std::vector<T> rp_call_mean(lqn.ncalls + 1, Tzero());
2042 std::vector<std::vector<std::size_t>> rp_hop_children(lqn.nidx + 1);
2043 std::vector<double> rp_hop_seed(lqn.nidx + 1, 0.0);
2044 std::vector<std::vector<std::size_t>> rp_member_inbound(lqn.nidx + 1);
2045 std::vector<std::vector<std::size_t>> rp_hop_inbound(lqn.nidx + 1);
2046 std::vector<std::vector<std::size_t>> rp_group_roots;
2047 std::vector<std::size_t> rp_group_reftask;
2048 std::vector<bool> rp_group_head_is_caller;
2050 std::vector<std::size_t> rp_ref_stage;
2051 std::vector<std::size_t> rp_hop_cls(lqn.nidx + 1, 0), rp_hop_ret(lqn.nidx + 1, 0),
2052 rp_ret_cls(lqn.nidx + 1, 0);
2053 std::vector<std::size_t> rp_gate(lqn.ncalls + 1, 0), rp_aux(lqn.ncalls + 1, 0),
2054 rp_resume(lqn.ncalls + 1, 0);
2057 auto host_demand_of_entry = [&](std::size_t eidx_) {
2059 for (std::size_t a_ : lqn.actsof[eidx_]) {
2060 if (lqn.hostdem[a_].disabled)
continue;
2061 const double m_ = dbl(lqn.hostdem[a_].mean);
2062 if (std::isfinite(m_)) d += m_;
2066 const auto in_idxset = [&](std::size_t t_) {
2067 return std::find(idxSet.begin(), idxSet.end(), t_) != idxSet.end();
2074 if (interlockMethod !=
"refpath" || flat || iscachelayer || hasfork || hasjoin)
return;
2077 if (reduce_fanout || nreplicas > 1)
return;
2078 for (std::size_t t_ : callers)
2079 if (lqn.repl[t_] > 1.0 || single_replica_tasks.count(t_) > 0)
return;
2080 const lqn::LqnRefRoutes<T> R =
2082 if (!R.why.empty()) {
2084 std::cerr <<
"[LINE] Warning: layer '" << lqn.hashnames[idx]
2085 <<
"' falls back to the 'ilrate' interlock method: " << R.why
2089 std::vector<std::size_t> keep;
2090 for (std::size_t g_ = 0; g_ < R.groups.size(); ++g_) {
2091 const lqn::LqnRefGroup<T>& G = R.groups[g_];
2092 bool wanted = G.members.size() >= 2;
2095 if (!wanted && opt.interlock_refpath_scope ==
"all" && !G.head_is_caller &&
2098 if (!wanted)
continue;
2103 for (std::size_t t_ : G.members) pool += caller_pool(t_);
2104 const double chainPop = mult[G.reftask] * lqn.repl[G.reftask];
2105 if (!(pool > chainPop))
continue;
2108 if (keep.empty())
return;
2111 std::vector<bool> sMember(lqn.nidx + 1,
false), sSpliced(lqn.nidx + 1,
false),
2112 sHop(lqn.nidx + 1,
false), sExpand(lqn.ncalls + 1,
false);
2113 std::vector<std::size_t> sChildTask(lqn.ncalls + 1, 0),
2114 sChildEntry(lqn.ncalls + 1, 0);
2115 std::vector<T> sRetShare(lqn.ncalls + 1, Tzero()), sCallMean(lqn.ncalls + 1, Tzero());
2116 std::vector<std::vector<std::size_t>> sHopChildren(lqn.nidx + 1),
2117 sMemberInbound(lqn.nidx + 1), sHopInbound(lqn.nidx + 1);
2118 std::vector<double> sHopSeed(lqn.nidx + 1, 0.0);
2119 std::vector<std::vector<std::size_t>> sRoots;
2120 std::vector<std::size_t> sRefTask;
2121 std::vector<bool> sHeadIsCaller;
2122 for (std::size_t g_ : keep) {
2123 const lqn::LqnRefGroup<T>& G = R.groups[g_];
2124 const std::size_t nE = G.entries.size();
2126 std::vector<bool> leads(G.ismember.begin(), G.ismember.end());
2127 for (std::size_t i_ = nE; i_-- > 0;)
2128 for (
const lqn::LqnRefCall<T>& c_ : G.calls)
2129 if (c_.from == i_ && leads[c_.to]) leads[i_] =
true;
2130 for (std::size_t t_ : G.members)
2131 if (!caller_needs_class(t_))
return;
2132 if (G.head_is_caller && !caller_needs_class(G.reftask))
return;
2134 if (!G.head_is_caller && !std::isfinite(lqn.maxmult[G.reftask]))
return;
2135 for (std::size_t i_ = 0; i_ < nE; ++i_) {
2136 if (!leads[i_] || G.ismember[i_])
continue;
2138 if (in_idxset(G.etask[i_]) || lqn.repl[G.etask[i_]] > 1.0)
return;
2140 for (
const lqn::LqnRefCall<T>& c_ : G.calls) {
2141 if (!leads[c_.to])
continue;
2142 if (in_idxset(G.etask[c_.to]))
return;
2145 const std::size_t head = G.reftask;
2146 for (std::size_t t_ : G.members) {
2148 sSpliced[t_] = !(G.head_is_caller && t_ == head);
2150 std::vector<T> inTotMember(lqn.nidx + 1, Tzero()), inTotHop(lqn.nidx + 1, Tzero());
2151 for (
const lqn::LqnRefCall<T>& c_ : G.calls) {
2152 if (!leads[c_.to])
continue;
2153 const std::size_t cidx_ = c_.cidx;
2154 const std::size_t fromEidx = G.entries[c_.from];
2155 sExpand[cidx_] =
true;
2156 sCallMean[cidx_] = lqn.callproc_mean[cidx_];
2157 sHopChildren[fromEidx].push_back(cidx_);
2158 if (G.ismember[c_.to]) {
2159 const std::size_t tv = G.etask[c_.to];
2160 sChildTask[cidx_] = tv;
2161 sMemberInbound[tv].push_back(cidx_);
2162 inTotMember[tv] = T(inTotMember[tv] + c_.vcall);
2164 const std::size_t ev = G.entries[c_.to];
2165 sChildEntry[cidx_] = ev;
2166 sHopInbound[ev].push_back(cidx_);
2167 inTotHop[ev] = T(inTotHop[ev] + c_.vcall);
2169 sRetShare[cidx_] = c_.vcall;
2171 for (
const lqn::LqnRefCall<T>& c_ : G.calls) {
2172 const std::size_t cidx_ = c_.cidx;
2173 if (!sExpand[cidx_])
continue;
2174 const T
tot = sChildTask[cidx_] > 0 ? inTotMember[sChildTask[cidx_]]
2175 : inTotHop[sChildEntry[cidx_]];
2177 ? T(sRetShare[cidx_] / tot)
2180 for (std::size_t i_ = 0; i_ < nE; ++i_)
2181 if (leads[i_] && !G.ismember[i_]) {
2182 const std::size_t ev = G.entries[i_];
2184 sHopSeed[ev] = host_demand_of_entry(ev);
2186 if (!G.head_is_caller) {
2190 for (std::size_t ev : lqn.entriesof[G.reftask])
2193 sHopSeed[ev] = host_demand_of_entry(ev);
2196 sRoots.push_back(lqn.entriesof[head]);
2197 sRefTask.push_back(head);
2198 sHeadIsCaller.push_back(G.head_is_caller);
2200 rp_member = sMember;
2201 rp_spliced = sSpliced;
2203 rp_expand = sExpand;
2204 rp_child_task = sChildTask;
2205 rp_child_entry = sChildEntry;
2206 rp_ret_share = sRetShare;
2207 rp_call_mean = sCallMean;
2208 rp_hop_children = sHopChildren;
2209 rp_hop_seed = sHopSeed;
2210 rp_member_inbound = sMemberInbound;
2211 rp_hop_inbound = sHopInbound;
2212 rp_group_roots = sRoots;
2213 rp_group_reftask = sRefTask;
2214 rp_group_head_is_caller = sHeadIsCaller;
2219 for (std::size_t tidx_caller : callers) {
2220 if (caller_needs_class(tidx_caller)) {
2221 double nj = njobs(tidx_caller, idx);
2223 nj = caller_pool(tidx_caller);
2224 njobs(tidx_caller, idx) = nj;
2229 const bool rp_spliced_here = rp_on && rp_spliced[tidx_caller];
2231 jc.name = lqn.hashnames[tidx_caller];
2232 jc.type = JobClassType::CLOSED;
2233 jc.population = rp_spliced_here ? 0.0 : nj;
2234 jc.refstat = m.clientIdx;
2235 jc.completes =
false;
2238 jc.is_ref_class = !rp_spliced_here;
2239 jc.attr_kind = int(LqnElement::TASK);
2240 jc.attr_idx = tidx_caller;
2241 cls[tidx_caller] = m.add_class(jc);
2242 m.attr_tasks.emplace_back(cls[tidx_caller], tidx_caller);
2243 if (lqn.isref[tidx_caller]) {
2244 m.set_service(m.clientIdx, cls[tidx_caller], thinkproc[tidx_caller]);
2245 }
else if (rp_spliced_here) {
2254 thinkt_map.push_back({idx, tidx_caller, m.clientIdx, cls[tidx_caller]});
2257 for (std::size_t eidx : lqn.entriesof[tidx_caller]) {
2259 ec.name = lqn.hashnames[eidx];
2260 ec.type = JobClassType::CLOSED;
2261 ec.population = 0.0;
2262 ec.refstat = m.clientIdx;
2263 ec.completes =
false;
2264 ec.attr_kind = int(LqnElement::ENTRY);
2266 cls[eidx] = m.add_class(ec);
2267 m.attr_entries.emplace_back(cls[eidx], eidx);
2272 for (std::size_t aidx : lqn.actsof[tidx_caller]) {
2273 if (caller_acts_visible(tidx_caller)) {
2275 ac.name = lqn.hashnames[aidx];
2276 ac.type = JobClassType::CLOSED;
2277 ac.population = 0.0;
2278 ac.refstat = m.clientIdx;
2279 ac.completes =
false;
2280 ac.attr_kind = int(LqnElement::ACTIVITY);
2282 cls[aidx] = m.add_class(ac);
2283 m.attr_activities.emplace_back(cls[aidx], aidx);
2287 const std::size_t hidx = lqn.parent[lqn.parent[aidx]];
2288 if (servers_for(hidx).empty())
2289 m.set_service(m.clientIdx, cls[aidx], servtproc[aidx]);
2291 for (std::size_t cidx : lqn.callsof[aidx]) {
2292 if (lqn.calltype[cidx] == CallType::ASYNC) {
2296 const std::size_t adst = lqn.parent[lqn.callpair_dst[cidx]];
2297 if (servers_for(adst).empty())
continue;
2299 oc.name = lqn.callhashnames[cidx];
2300 oc.type = JobClassType::OPEN;
2301 oc.population = std::numeric_limits<double>::infinity();
2302 oc.refstat = sourceStation;
2303 oc.completes =
false;
2304 oc.is_ref_class =
false;
2305 oc.attr_kind = int(LqnElement::CALL);
2307 callcls[cidx] = m.add_class(oc);
2308 m.attr_calls.push_back({callcls[cidx], cidx, lqn.callpair_src[cidx],
2309 lqn.callpair_dst[cidx]});
2317 m.set_service(sourceStation, callcls[cidx],
2320 T minRespTA = Tzero();
2321 for (std::size_t ta : lqn.actsof[adst]) minRespTA += lqn.hostdem[ta].mean;
2322 for (std::size_t st : servers_for(adst)) {
2324 call_map.push_back({idx, cidx, st, callcls[cidx]});
2326 arv_call_map.push_back({idx, cidx, sourceStation, callcls[cidx]});
2329 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
2330 const std::size_t gid = group_of_call[cidx];
2339 if (grp_dispatch[gid] == 0) {
2345 const std::string tag =
2346 lqn.hashnames[aidx] +
".Dispatch" + std::to_string(gid);
2347 grp_router[gid] = m.add_node(tag +
".Router", NodeType::Router,
true);
2350 dc.type = JobClassType::CLOSED;
2351 dc.population = 0.0;
2352 dc.refstat = m.clientIdx;
2353 dc.completes =
false;
2354 dc.attr_kind = int(LqnElement::CALL);
2356 grp_dispatch[gid] = m.add_class(dc);
2359 gc.name = lqn.callhashnames[cidx] +
".Group" + std::to_string(gid);
2360 gc.type = JobClassType::CLOSED;
2361 gc.population = 0.0;
2362 gc.refstat = m.clientIdx;
2363 gc.completes =
false;
2364 gc.attr_kind = int(LqnElement::CALL);
2366 grp_class[gid] = m.add_class(gc);
2368 routed_group_sites.push_back(
2369 {grp_router[gid], grp_dispatch[gid], gid});
2371 callcls[cidx] = grp_dispatch[gid];
2372 m.attr_calls.push_back({callcls[cidx], cidx, lqn.callpair_src[cidx],
2373 lqn.callpair_dst[cidx]});
2374 for (std::size_t st2 : servers_for(lqn.parent[lqn.callpair_dst[cidx]]))
2375 m.set_service(st2, callcls[cidx], callservtproc[cidx]);
2379 cc.name = lqn.callhashnames[cidx];
2380 cc.type = JobClassType::CLOSED;
2381 cc.population = 0.0;
2382 cc.refstat = m.clientIdx;
2383 cc.completes =
false;
2384 cc.attr_kind = int(LqnElement::CALL);
2386 callcls[cidx] = m.add_class(cc);
2387 m.attr_calls.push_back({callcls[cidx], cidx, lqn.callpair_src[cidx],
2388 lqn.callpair_dst[cidx]});
2396 const std::size_t seedidx = flat ? lqn.parent[lqn.callpair_dst[cidx]] : idx;
2397 T minRespT = Tzero();
2398 for (std::size_t ta : lqn.actsof[seedidx]) minRespT += lqn.hostdem[ta].mean;
2399 for (std::size_t st : servers_for(seedidx))
2402 if (lqn.callproc_mean[cidx] !=
2403 num_traits<T>::from_double(
double(nreplicas))) {
2405 xc.name = lqn.callhashnames[cidx] +
".Aux";
2406 xc.type = JobClassType::CLOSED;
2407 xc.population = 0.0;
2408 xc.refstat = m.clientIdx;
2409 xc.completes =
false;
2410 xc.attr_kind = int(LqnElement::CALL);
2412 auxcallcls[cidx] = m.add_class(xc);
2424 auto rp_class = [&](
const std::string& nm,
double pop, std::size_t
attr) {
2427 rc.type = JobClassType::CLOSED;
2428 rc.population = pop;
2429 rc.refstat = m.clientIdx;
2430 rc.completes =
false;
2431 rc.is_ref_class =
false;
2434 const std::size_t k_ = m.add_class(rc);
2439 for (std::size_t g_ = 0; g_ < rp_group_reftask.size(); ++g_) {
2440 if (rp_group_head_is_caller[g_]) {
2441 rp_ref_stage.push_back(0);
2444 const std::size_t rt = rp_group_reftask[g_];
2445 const std::size_t k_ =
2446 rp_class(lqn.hashnames[rt] +
".RefPath", mult[rt] * lqn.repl[rt], rt);
2447 m.classes[k_ - 1].is_ref_class =
true;
2448 m.set_service(m.clientIdx, k_, thinkproc[rt]);
2449 rp_ref_stage.push_back(k_);
2451 for (std::size_t ev = 1; ev <= lqn.nidx; ++ev) {
2452 if (!rp_hop[ev])
continue;
2453 const std::size_t k_ = rp_class(lqn.hashnames[ev] +
".RefHop", 0.0, ev);
2455 m.set_service(m.clientIdx, k_,
2457 rp_hop_cls[ev] = k_;
2460 refpath_map.push_back({idx, m.clientIdx, k_, 1, ev, 1.0, 0});
2461 for (std::size_t c_ : rp_hop_children[ev])
2462 refpath_map.push_back({idx, m.clientIdx, k_, 2, c_, -1.0, ev});
2463 refpath_map.push_back({idx, m.clientIdx, k_, 3, lqn.parent[ev], 1.0, 0});
2464 rp_hop_ret[ev] = rp_class(lqn.hashnames[ev] +
".RefHopRet", 0.0, ev);
2466 for (std::size_t tv = 1; tv <= lqn.nidx; ++tv)
2467 if (rp_spliced[tv]) rp_ret_cls[tv] = rp_class(lqn.hashnames[tv] +
".RefRet", 0.0, tv);
2468 for (std::size_t cidx_ = 1; cidx_ <= lqn.ncalls; ++cidx_) {
2469 if (!rp_expand[cidx_])
continue;
2470 if (callcls[cidx_] != 0) {
2471 rp_gate[cidx_] = callcls[cidx_];
2473 if (auxcallcls[cidx_] != 0) rp_aux[cidx_] = auxcallcls[cidx_];
2475 rp_gate[cidx_] = rp_class(lqn.callhashnames[cidx_] +
".RefGate", 0.0, cidx_);
2476 if (rp_call_mean[cidx_] != Tone())
2478 rp_class(lqn.callhashnames[cidx_] +
".RefGate.Aux", 0.0, cidx_);
2483 if (rp_child_task[cidx_] > 0)
2484 refpath_map.push_back(
2485 {idx, m.clientIdx, rp_gate[cidx_], 3, rp_child_task[cidx_], 1.0, 0});
2486 rp_resume[cidx_] = rp_class(lqn.callhashnames[cidx_] +
".RefResume", 0.0, cidx_);
2492 std::size_t curclass;
2498 std::vector<std::size_t> curnodes;
2500 const int atClient = 1, atServer = 2, atCache = 3;
2501 std::vector<int> jobposkey(lqn.nidx + 1, atClient);
2502 std::vector<std::size_t> curclasskey(lqn.nidx + 1, 0);
2503 std::vector<std::vector<std::size_t>> curnodeskey(lqn.nidx + 1);
2510 auto rp_descent = [&](std::size_t cidx_, Ctx st) -> Ctx {
2511 const std::size_t gate = rp_gate[cidx_], res = rp_resume[cidx_], aux = rp_aux[cidx_];
2512 const T m_ = rp_call_mean[cidx_];
2513 const std::size_t enter = rp_child_task[cidx_] > 0 ? cls[rp_child_task[cidx_]]
2514 : rp_hop_cls[rp_child_entry[cidx_]];
2515 if (st.jobpos == atClient)
2516 m.set_route(st.curclass, gate, clientNode, clientNode, Tone());
2518 for (std::size_t nd : st.curnodes) m.set_route(st.curclass, gate, nd, clientNode, Tone());
2521 m.set_route(gate, enter, clientNode, clientNode, Tone());
2523 }
else if (m_ > Tone()) {
2524 m.set_route(gate, enter, clientNode, clientNode, Tone());
2525 m.set_route(res, enter, clientNode, clientNode, T(Tone() - Tone() / m_));
2526 m.set_route(res, aux, clientNode, clientNode, T(Tone() / m_));
2529 m.set_route(gate, enter, clientNode, clientNode, m_);
2530 m.set_route(gate, aux, clientNode, clientNode, T(Tone() - m_));
2531 m.set_route(res, aux, clientNode, clientNode, Tone());
2534 st.jobpos = atClient;
2535 st.curnodes.clear();
2540 std::function<Ctx(std::size_t, std::size_t, Ctx)> recur =
2541 [&](std::size_t tidx_caller, std::size_t aidx, Ctx st) -> Ctx {
2542 jobposkey[aidx] = st.jobpos;
2543 curclasskey[aidx] = st.curclass;
2544 curnodeskey[aidx] = st.curnodes;
2545 const std::vector<std::size_t> nexts = lqn.graph.succ(aidx);
2546 std::size_t lastEntryClass = st.curclass;
2548 bool next_is_fork =
false;
2549 for (std::size_t sx : nexts)
2550 if (lqn.actposttype[sx] == PrecedenceType::POST_AND) next_is_fork =
true;
2551 const Ctx preFork = st;
2552 std::vector<std::size_t> andSuccs;
2553 for (std::size_t sx : nexts)
2554 if (lqn.actposttype[sx] == PrecedenceType::POST_AND) andSuccs.push_back(sx);
2556 for (std::size_t k = 0; k < nexts.size(); ++k) {
2557 const std::size_t nextaidx = nexts[k];
2558 if (next_is_fork) st = preFork;
2559 bool isLoop = lqn.graph.get(aidx, nextaidx) != lqn.dag.get(aidx, nextaidx);
2560 if (lqn.parent[aidx] != lqn.parent[nextaidx]) {
2562 std::size_t cidx = 0;
2563 for (std::size_t c : lqn.callsof[aidx])
2564 if (lqn.callpair_dst[c] == nextaidx) cidx = c;
2565 if (cidx == 0)
continue;
2567 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
2568 const std::size_t gid = group_of_call[cidx];
2572 if (group_members[gid].empty() || group_members[gid][0] != cidx)
continue;
2579 std::vector<std::size_t> tnode2, tstat2, tcall2;
2580 for (std::size_t mc : group_members[gid]) {
2581 const std::size_t tt = lqn.parent[lqn.callpair_dst[mc]];
2582 if (servers_for(tt).empty())
continue;
2583 tnode2.push_back(server_nodes_for(tt)[0]);
2584 tstat2.push_back(servers_for(tt)[0]);
2585 tcall2.push_back(mc);
2587 if (tnode2.size() < 2)
continue;
2588 const std::size_t fromNode2 =
2589 st.jobpos == atClient ? clientNode : st.curnodes[0];
2590 const std::size_t dc = grp_dispatch[gid], gc = grp_class[gid];
2591 m.set_route(st.curclass, dc, fromNode2, grp_router[gid], Tone());
2593 T(Tone() / num_traits<T>::from_int(
int(tnode2.size())));
2594 for (std::size_t d = 0; d < tnode2.size(); ++d) {
2595 m.set_route(dc, dc, grp_router[gid], tnode2[d], share2);
2596 m.set_route(dc, gc, tnode2[d], clientNode, Tone());
2597 m.set_service(tstat2[d], dc, callservtproc[tcall2[d]]);
2598 call_map.push_back({idx, tcall2[d], tstat2[d], dc});
2601 st.jobpos = atClient;
2602 st.curnodes.clear();
2605 if (rp_on && rp_expand[cidx]) {
2608 st = rp_descent(cidx, st);
2611 const std::size_t ctgt = lqn.parent[lqn.callpair_dst[cidx]];
2612 st = route_sync_call(m, idx, cidx, st, server_nodes_for(ctgt),
2613 servers_for(ctgt), callcls, auxcallcls, clientNode,
2614 atClient, atServer, flat);
2618 bool any_entry_succ =
false;
2619 for (std::size_t sx : nexts)
2620 if (sx > lqn.eshift && sx <= lqn.eshift + lqn.nentries) any_entry_succ =
true;
2621 if (!any_entry_succ) {
2622 st.jobpos = jobposkey[aidx];
2623 st.curclass = curclasskey[aidx];
2624 st.curnodes = curnodeskey[aidx];
2626 if (k > 0 && nexts[k - 1] > lqn.eshift && nexts[k - 1] <= lqn.eshift + lqn.nentries)
2627 lastEntryClass = st.curclass;
2628 st.jobpos = atClient;
2629 st.curclass = lastEntryClass;
2630 st.curnodes.clear();
2632 const T w = lqn.graph.get(aidx, nextaidx);
2640 if (iscachelayer && lqn.nitems[aidx] > 0 && cls[nextaidx] != 0) {
2641 const std::size_t readcls = cls[nextaidx];
2642 m.set_route(st.curclass, readcls, clientNode, cacheNode, w);
2643 if (cachepar.pread.size() < m.classes.size())
2644 cachepar.pread.resize(m.classes.size());
2645 cachepar.pread[readcls - 1] = lqn.itemproc[aidx];
2646 const std::vector<std::size_t> hm = lqn.graph.succ(nextaidx);
2648 throw InputError(
"SolverLN: the cache read '" + lqn.names[nextaidx] +
2649 "' needs exactly one hit and one miss successor");
2650 if (cachepar.hitclass.size() < m.classes.size()) {
2651 cachepar.hitclass.resize(m.classes.size(), 0);
2652 cachepar.missclass.resize(m.classes.size(), 0);
2654 cachepar.hitclass[readcls - 1] = cls[hm[0]];
2655 cachepar.missclass[readcls - 1] = cls[hm[1]];
2656 st.jobpos = atCache;
2657 st.curclass = readcls;
2658 st.curnodes.clear();
2659 st = recur(tidx_caller, nextaidx, st);
2663 const bool is_and_join_tail = lqn.actpretype[aidx] == PrecedenceType::PRE_AND;
2665 std::size_t fbranch = 0;
2667 for (std::size_t q = 0; q < andSuccs.size(); ++q)
2668 if (andSuccs[q] == nextaidx) fbranch = q + 1;
2675 const std::size_t hidxOf = lqn.parent[lqn.parent[nextaidx]];
2676 const std::vector<std::size_t>& actStations = servers_for(hidxOf);
2677 const std::vector<std::size_t>& actNodes = server_nodes_for(hidxOf);
2678 const bool actAtServer = !actStations.empty();
2679 const std::size_t from = st.jobpos == atClient ? clientNode
2680 : st.jobpos == atCache ? cacheNode
2686 const T split = actAtServer ? T(Tone() / num_traits<T>::from_double(
double(nreplicas)))
2688 const bool oneToOne = st.jobpos != atClient && st.jobpos != atCache &&
2689 st.curnodes.size() == nreplicas;
2690 for (std::size_t r = 0; r < nreplicas; ++r) {
2691 const std::size_t fromNode =
2692 st.jobpos == atClient ? clientNode
2693 : st.jobpos == atCache ? cacheNode
2694 : st.curnodes[std::min(r, st.curnodes.size() - 1)];
2695 const std::size_t toNode = actAtServer ? actNodes[r] : clientNode;
2696 if (next_is_fork && fbranch > 0) {
2697 m.set_route(st.curclass, st.curclass, fromNode, forkNode, Tone());
2698 if (r == 0) forkClassStack.push_back(st.curclass);
2699 m.set_route(st.curclass, st.curclass, forkNode, forkRouter[fbranch - 1],
2701 m.set_route(st.curclass, cls[nextaidx], forkRouter[fbranch - 1], toNode,
2703 }
else if (is_and_join_tail) {
2706 if (forkClassStack.empty())
2707 throw InputError(
"SolverLN: an AND join has no matching fork in '" +
2708 lqn.names[aidx] +
"'");
2709 const std::size_t forkClass = forkClassStack.back();
2710 if (r + 1 == nreplicas) forkClassStack.pop_back();
2711 m.set_route(st.curclass, forkClass, fromNode, joinNode, Tone());
2712 m.set_route(forkClass, cls[nextaidx], joinNode, toNode, T(Tone() * split));
2716 m.set_route(st.curclass, cls[nextaidx], fromNode, toNode,
2717 oneToOne ? w : T(w * split));
2720 m.set_service(actStations[r], cls[nextaidx], lqn.hostdem[nextaidx]);
2724 st.jobpos = atServer;
2725 st.curclass = cls[nextaidx];
2726 st.curnodes = actNodes;
2727 servt_map.push_back({idx, nextaidx, actStations[0], cls[nextaidx]});
2729 st.jobpos = atClient;
2730 st.curclass = cls[nextaidx];
2731 st.curnodes.clear();
2732 m.set_service(m.clientIdx, cls[nextaidx], servtproc[nextaidx]);
2733 thinkt_map.push_back({idx, nextaidx, m.clientIdx, cls[nextaidx]});
2735 if (aidx != nextaidx && !isLoop) {
2736 st = recur(tidx_caller, nextaidx, st);
2740 const std::size_t reply =
2741 (rp_on && rp_spliced[tidx_caller]) ? rp_ret_cls[tidx_caller]
2743 if (st.jobpos == atClient) {
2744 m.set_route(st.curclass, reply, clientNode, clientNode, Tone());
2746 for (std::size_t nd : st.curnodes)
2747 m.set_route(st.curclass, reply, nd, clientNode, Tone());
2750 if (!is_aux_class(m.classes[st.curclass - 1].name))
2751 m.classes[st.curclass - 1].completes =
true;
2757 for (std::size_t tidx_caller : callers) {
2758 if (!caller_needs_class(tidx_caller))
continue;
2759 const std::vector<std::size_t>& ents = lqn.entriesof[tidx_caller];
2760 const T share = T(Tone() / num_traits<T>::from_int(
int(ents.size())));
2761 for (std::size_t eidx : ents) {
2762 m.set_route(cls[tidx_caller], cls[eidx], clientNode, clientNode, share);
2763 if (ents.size() > 1)
2764 route_map.push_back({idx, tidx_caller, eidx, m.clientIdx, m.clientIdx,
2765 cls[tidx_caller], cls[eidx]});
2766 Ctx st{cls[eidx], atClient, {}};
2767 recur(tidx_caller, eidx, st);
2775 for (std::size_t g_ = 0; g_ < rp_group_reftask.size(); ++g_) {
2776 if (rp_group_head_is_caller[g_])
continue;
2777 std::vector<std::size_t> roots;
2778 for (std::size_t r_ : rp_group_roots[g_])
2779 if (rp_hop[r_]) roots.push_back(r_);
2780 if (roots.empty())
continue;
2782 const T share = T(Tone() / num_traits<T>::from_int(
int(roots.size())));
2783 for (std::size_t r_ : roots) {
2784 m.set_route(rp_ref_stage[g_], rp_hop_cls[r_], clientNode, clientNode, share);
2785 m.set_route(rp_hop_ret[r_], rp_ref_stage[g_], clientNode, clientNode, Tone());
2789 for (std::size_t ev = 1; ev <= lqn.nidx; ++ev) {
2790 if (!rp_hop[ev])
continue;
2791 Ctx cur{rp_hop_cls[ev], atClient, {}};
2792 for (std::size_t c_ : rp_hop_children[ev]) cur = rp_descent(c_, cur);
2793 m.set_route(cur.curclass, rp_hop_ret[ev], clientNode, clientNode, Tone());
2797 for (std::size_t tv = 1; tv <= lqn.nidx; ++tv) {
2798 if (!rp_spliced[tv])
continue;
2799 for (std::size_t c_ : rp_member_inbound[tv])
2800 if (rp_ret_share[c_] > Tzero())
2801 m.set_route(rp_ret_cls[tv], rp_resume[c_], clientNode, clientNode,
2804 for (std::size_t ev = 1; ev <= lqn.nidx; ++ev) {
2805 if (!rp_hop[ev])
continue;
2806 for (std::size_t c_ : rp_hop_inbound[ev])
2807 if (rp_ret_share[c_] > Tzero())
2808 m.set_route(rp_hop_ret[ev], rp_resume[c_], clientNode, clientNode,
2819 if (sourceStation != 0) {
2820 const T nrep = num_traits<T>::from_int(
int(nreplicas));
2821 for (std::size_t cidx : async_here) {
2822 const std::size_t oc = callcls[cidx];
2823 if (oc == 0)
continue;
2826 const std::vector<std::size_t>& anode =
2827 server_nodes_for(lqn.parent[lqn.callpair_dst[cidx]]);
2828 const T callmean = lqn.callproc_mean[cidx];
2829 if (callmean < Tone()) {
2831 m.set_route(oc, oc, sourceNode, sinkNode, T(Tone() - callmean));
2832 for (std::size_t r = 0; r < anode.size(); ++r) {
2833 m.set_route(oc, oc, sourceNode, anode[r], T(callmean / nrep));
2834 m.set_route(oc, oc, anode[r], sinkNode, Tone());
2838 const T p = T(Tone() / callmean);
2839 for (std::size_t r = 0; r < anode.size(); ++r) {
2840 m.set_route(oc, oc, sourceNode, anode[r], T(Tone() / nrep));
2841 for (std::size_t q = 0; q < anode.size(); ++q)
2842 m.set_route(oc, oc, anode[r], anode[q], T((Tone() - p) / nrep));
2843 m.set_route(oc, oc, anode[r], sinkNode, p);
2847 for (std::size_t eidx : open_entries) {
2849 eo.name = lqn.hashnames[eidx] +
"_Open";
2850 eo.type = JobClassType::OPEN;
2851 eo.population = std::numeric_limits<double>::infinity();
2852 eo.refstat = sourceStation;
2853 eo.completes =
false;
2854 eo.is_ref_class =
false;
2855 eo.attr_kind = int(LqnElement::ENTRY);
2857 const std::size_t ec = m.add_class(eo);
2859 m.set_service(sourceStation, ec, lqn.arrival[eidx]);
2861 std::size_t bound = 0;
2862 for (std::size_t sx : lqn.graph.succ(eidx))
2863 if (bound == 0 && sx > lqn.ashift) bound = sx;
2864 const Distrib<T>& svc = bound != 0 ? servtproc[bound] : servtproc[eidx];
2867 const std::vector<std::size_t>& ostat =
2868 flat ? servers_for(lqn.parent[eidx]) : server;
2869 const std::vector<std::size_t>& onode =
2870 flat ? server_nodes_for(lqn.parent[eidx]) : serverNode;
2871 for (std::size_t r = 0; r < ostat.size(); ++r) {
2872 m.set_service(ostat[r], ec, svc);
2873 m.set_route(ec, ec, sourceNode, onode[r], T(Tone() / nrep));
2874 m.set_route(ec, ec, onode[r], sinkNode, Tone());
2888 std::vector<std::vector<T>> Arows;
2889 std::vector<T> brows;
2890 if (idx < lqn.lincon_A.size() && lqn.lincon_A[idx].rows() > 0) {
2891 const Matrix<T>& Aelem = lqn.lincon_A[idx];
2892 Matrix<T> Alayer(Aelem.rows(), m.classes.size(), Tzero());
2893 const std::vector<std::size_t>& constrained =
2894 ishostlayer ? lqn.tasksof[idx] : lqn.entriesof[idx];
2895 for (std::size_t j = 0; j < constrained.size() && j < Aelem.cols(); ++j) {
2896 std::vector<std::size_t> layerClasses;
2898 for (std::size_t a : lqn.actsof[constrained[j]])
2899 if (cls[a] != 0) layerClasses.push_back(cls[a]);
2901 for (std::size_t c = 1; c <= lqn.ncalls; ++c)
2902 if (lqn.callpair_dst[c] == constrained[j] && callcls[c] != 0)
2903 layerClasses.push_back(callcls[c]);
2905 for (std::size_t k = 0; k < layerClasses.size(); ++k)
2906 for (std::size_t rr = 0; rr < Aelem.rows(); ++rr)
2907 Alayer(rr, layerClasses[k] - 1) =
2908 T(Alayer(rr, layerClasses[k] - 1) + Aelem(rr, j));
2910 for (std::size_t rr = 0; rr < Alayer.rows(); ++rr) {
2911 std::vector<T> row(m.classes.size(), Tzero());
2912 for (std::size_t k = 0; k < m.classes.size(); ++k) row[k] = Alayer(rr, k);
2913 Arows.push_back(row);
2914 brows.push_back(lqn.lincon_b[idx][rr]);
2923 if (rp_on && layer_solver_declares_region()) {
2924 std::vector<std::size_t> srvEntries;
2925 for (std::size_t sidx : idxSet)
2926 for (std::size_t e : lqn.entriesof[sidx]) srvEntries.push_back(e);
2927 for (std::size_t tv = 1; tv <= lqn.nidx; ++tv) {
2928 if (!rp_spliced[tv])
continue;
2929 const double cap = lqn.maxmult[tv];
2930 if (!std::isfinite(cap) || cap <= 0.0)
continue;
2931 std::vector<T> row(m.classes.size(), Tzero());
2935 for (std::size_t a_ : lqn.actsof[tv])
2937 row[cls[a_] - 1] = Tone();
2942 for (std::size_t a_ : lqn.actsof[tv])
2943 for (std::size_t c_ : lqn.callsof[a_])
2944 if (std::find(srvEntries.begin(), srvEntries.end(),
2945 lqn.callpair_dst[c_]) != srvEntries.end() &&
2947 row[callcls[c_] - 1] = Tone();
2952 Arows.push_back(row);
2953 brows.push_back(num_traits<T>::from_double(cap));
2959 for (
const std::vector<T>& row : Arows)
2960 for (
const T& v : row)
2961 if (v != Tzero()) any =
true;
2963 Matrix<T> Alayer(Arows.size(), m.classes.size(), Tzero());
2964 for (std::size_t rr = 0; rr < Arows.size(); ++rr)
2965 for (std::size_t k = 0; k < m.classes.size(); ++k) Alayer(rr, k) = Arows[rr][k];
2969 typename qn::NetworkStruct<T>::Region rg;
2970 const std::size_t M = m.stations.size(), K = m.classes.size();
2971 rg.cap.assign(M, std::vector<double>(K + 1, -1.0));
2972 rg.maxmem.assign(M, -1.0);
2973 rg.members.assign(M,
false);
2975 rg.weight.assign(K, Tone());
2976 rg.size.assign(K, Tone());
2977 for (std::size_t r = 0; r < nreplicas; ++r) rg.members[server[r] - 1] =
true;
2978 rg.lincon_A = Alayer;
2979 rg.lincon_b = brows;
2980 m.regions.push_back(rg);
2987 if (cacheNode != 0) {
2988 cachepar.pread.resize(m.classes.size());
2989 cachepar.hitclass.resize(m.classes.size(), 0);
2990 cachepar.missclass.resize(m.classes.size(), 0);
2991 m.nodeparam[cacheNode] = cachepar;
2999 for (
const std::array<std::size_t, 3>& site : routed_group_sites) {
3000 qn::NodeDef& nd = m.nodes[site[0] - 1];
3001 if (nd.routing.size() < m.classes.size())
3002 nd.routing.resize(m.classes.size(), RoutingStrategy::PROB);
3003 nd.routing[site[1] - 1] = lqn.callgroups[site[2] - 1].strategy;
3015 std::vector<std::pair<std::shared_ptr<std::vector<std::vector<std::size_t>>>,
3016 std::vector<std::vector<std::size_t>>>> deferred_chaincols;
3017 for (std::size_t sidx : idxSet) {
3018 const bool hasld = sidx < lqn.lldscaling.size() && !lqn.lldscaling[sidx].empty();
3019 const bool hascd = sidx < lqn.cdscaling.size() && bool(lqn.cdscaling[sidx]);
3020 const bool hasjd = sidx < lqn.jdscaling.size() && bool(lqn.jdscaling[sidx]);
3021 const bool haspools = sidx < lqn.pools.size() && !lqn.pools[sidx].empty();
3022 if (!(hasld || hascd || hasjd || haspools))
continue;
3023 const bool sishost = sidx <= lqn.nhosts;
3024 const std::vector<std::size_t>& operandIdx =
3025 sishost ? lqn.tasksof[sidx] : lqn.entriesof[sidx];
3026 std::vector<std::vector<std::size_t>> cols(operandIdx.size());
3027 for (std::size_t j = 0; j < operandIdx.size(); ++j) {
3029 for (std::size_t a : lqn.actsof[operandIdx[j]])
3030 if (cls[a] != 0) cols[j].push_back(cls[a] - 1);
3032 for (std::size_t c = 1; c <= lqn.ncalls; ++c)
3033 if (lqn.callpair_dst[c] == operandIdx[j] && callcls[c] != 0)
3034 cols[j].push_back(callcls[c] - 1);
3037 const std::size_t R = m.classes.size();
3038 std::shared_ptr<std::vector<std::vector<std::size_t>>> chaincols =
3039 std::make_shared<std::vector<std::vector<std::size_t>>>();
3040 deferred_chaincols.push_back(std::make_pair(chaincols, cols));
3041 for (std::size_t r = 0; r < nreplicas; ++r) {
3046 qn::Station<T>& stn = m.stations[srv[sidx][r] - 1];
3047 if (hasld) stn.lldscaling = lqn.lldscaling[sidx];
3053 bool one_class_each =
true;
3054 for (std::size_t j = 0; j < cols.size(); ++j)
3055 if (cols[j].size() > 1) one_class_each =
false;
3057 layer_dep_handle(lqn.cdscaling[sidx], cols, chaincols, R);
3058 const std::vector<T> pk = layer_peak(lqn.cdscalingpeak[sidx], cols, R);
3059 if (one_class_each) {
3061 stn.cdscalingpeak = pk;
3064 stn.jdscalingpeak = pk;
3068 stn.jdscaling = layer_dep_handle(lqn.jdscaling[sidx], cols, chaincols, R);
3069 stn.jdscalingpeak = layer_peak(lqn.jdscalingpeak[sidx], cols, R);
3080 const lqn::ServerPools<T>& pl = lqn.pools[sidx];
3081 const Matrix<T> compat = pl.compat;
3082 const std::vector<double> counts = pl.counts;
3083 const std::vector<T> rates = pl.rates;
3085 rates](
const std::vector<T>& nop) {
3086 return std::vector<T>(
3089 stn.jdscaling = layer_dep_handle(etaPool, cols, chaincols, R);
3090 stn.jdscalingpeak = layer_peak(
3091 std::vector<T>(cols.size(), num_traits<T>::from_int(1)), cols, R);
3101 std::vector<std::size_t> nc(m.classes.size(), 0);
3102 for (std::size_t tv = 1; tv <= lqn.nidx; ++tv) {
3103 if (!rp_member[tv] || cls[tv] == 0)
continue;
3104 const std::size_t own = cls[tv];
3105 std::vector<std::size_t> owned{cls[tv]};
3106 for (std::size_t e_ : lqn.entriesof[tv]) owned.push_back(cls[e_]);
3107 for (std::size_t a_ : lqn.actsof[tv]) {
3108 owned.push_back(cls[a_]);
3109 for (std::size_t c_ : lqn.callsof[a_]) {
3110 owned.push_back(callcls[c_]);
3111 owned.push_back(auxcallcls[c_]);
3115 for (std::size_t q_ : owned)
3116 if (q_ != 0 && m.classes[q_ - 1].type == JobClassType::CLOSED) nc[q_ - 1] = own;
3118 layer_normclass[idx] = nc;
3119 ref_path_audit(m, idx, callers, caller_needs_class, rp_spliced, rp_member, rp_hop,
3120 rp_expand, rp_group_head_is_caller, rp_ref_stage, rp_hop_cls,
3121 rp_hop_ret, rp_ret_cls, rp_gate, rp_aux, rp_resume, cls);
3125 apply_host_task_priorities(m, idxSet);
3130 for (std::size_t d = 0; d < deferred_chaincols.size(); ++d) {
3131 const std::vector<std::vector<std::size_t>>& cols = deferred_chaincols[d].second;
3132 std::vector<std::vector<std::size_t>>& out = *deferred_chaincols[d].first;
3133 out.assign(cols.size(), std::vector<std::size_t>());
3134 for (std::size_t j = 0; j < cols.size(); ++j) {
3135 for (std::size_t k = 0; k < cols[j].size(); ++k)
3136 for (std::size_t cc = 0; cc < m.chains.size(); ++cc)
3137 if (cols[j][k] < m.chains[cc].size() && m.chains[cc][cols[j][k]]) {
3139 for (std::size_t q = 0; q < out[j].size(); ++q)
3140 if (out[j][q] == cc) seen =
true;
3141 if (!seen) out[j].push_back(cc);
3162 bool layer_solver_declares_region()
const {
3164 return opt.layer_solver ==
"nc" || opt.layer_solver ==
"fluid" || opt.layer_solver ==
"ssa" ||
3165 opt.layer_solver ==
"ctmc" || opt.layer_solver ==
"ldes" || opt.layer_solver ==
"jmt";
3169 bool layer_solver_is_stochastic()
const {
3170 return opt.layer_solver ==
"ssa" || opt.layer_solver ==
"ldes" || opt.layer_solver ==
"jmt";
3178 long layer_seed(
long base, std::size_t e)
const {
3179 const long it = cur_it > 0 ? cur_it : 1;
3180 return base + (it - 1) *
static_cast<long>(ensemble.size()) +
static_cast<long>(e) + 1;
3189 template <
class NeedsClass>
3190 void ref_path_audit(
const qn::Layer<T>& m, std::size_t idx,
3191 const std::vector<std::size_t>& callers,
const NeedsClass& needs_class,
3192 const std::vector<bool>& spliced,
const std::vector<bool>& member,
3193 const std::vector<bool>& hop,
const std::vector<bool>& expand,
3194 const std::vector<bool>& head_is_caller,
3195 const std::vector<std::size_t>& ref_stage,
3196 const std::vector<std::size_t>& hop_cls,
3197 const std::vector<std::size_t>& hop_ret,
3198 const std::vector<std::size_t>& ret_cls,
3199 const std::vector<std::size_t>& gate,
const std::vector<std::size_t>& aux,
3200 const std::vector<std::size_t>& resume,
3201 const std::vector<std::size_t>& cls)
const {
3202 std::set<std::size_t> watch;
3203 for (std::size_t k : ref_stage)
3204 if (k) watch.insert(k);
3205 for (std::size_t e = 1; e < hop.size(); ++e)
3207 if (hop_cls[e]) watch.insert(hop_cls[e]);
3208 if (hop_ret[e]) watch.insert(hop_ret[e]);
3210 for (std::size_t t = 1; t < member.size(); ++t)
3212 if (ret_cls[t]) watch.insert(ret_cls[t]);
3213 if (cls[t]) watch.insert(cls[t]);
3215 for (std::size_t c = 1; c < expand.size(); ++c)
3217 if (gate[c]) watch.insert(gate[c]);
3218 if (aux[c]) watch.insert(aux[c]);
3219 if (resume[c]) watch.insert(resume[c]);
3221 const std::size_t I = m.nodes.size();
3222 for (std::size_t r : watch) {
3223 for (std::size_t n = 1; n <= I; ++n) {
3226 for (
const auto& kv : m.P) {
3227 if (kv.first.first != r || n > kv.second.rows())
continue;
3228 for (std::size_t j = 0; j < kv.second.cols(); ++j) {
3229 const double v = dbl(kv.second(n - 1, j));
3235 std::cerr <<
"[LINE] Warning: refpath layer '" << lqn.hashnames[idx]
3236 <<
"': class '" << m.classes[r - 1].name <<
"' routes out of node "
3237 << n <<
" with a negative probability.\n";
3239 std::cerr <<
"[LINE] Warning: refpath layer '" << lqn.hashnames[idx]
3240 <<
"': class '" << m.classes[r - 1].name <<
"' leaves node " << n
3241 <<
" with total probability " <<
tot <<
".\n";
3244 std::size_t nref = 0;
3245 for (
const qn::JobClass& jc : m.classes)
3246 if (jc.is_ref_class) ++nref;
3247 std::size_t expect = 0;
3248 for (
bool h : head_is_caller)
3250 for (std::size_t t : callers)
3251 if (!spliced[t] && needs_class(t)) ++expect;
3253 std::cerr <<
"[LINE] Warning: refpath layer '" << lqn.hashnames[idx] <<
"' declares "
3254 << nref <<
" reference classes where " << expect
3255 <<
" were built; a chain holding two makes refresh_chains throw.\n";
3287 std::size_t curclass;
3292 static bool is_aux_class(
const std::string& name) {
3293 return name.size() >= 4 &&
name.compare(
name.size() - 4, 4,
".Aux") == 0;
3295 template <
class Ctx>
3296 Ctx route_sync_call(qn::Layer<T>& m, std::size_t idx, std::size_t cidx, Ctx st,
3297 const std::vector<std::size_t>& tnode,
3298 const std::vector<std::size_t>& tstat,
3299 const std::vector<std::size_t>& callcls,
3300 const std::vector<std::size_t>& auxcallcls, std::size_t clientNode,
3301 int atClient,
int atServer,
bool flat) {
3302 const T one = Tone();
3306 const std::size_t ntgt = tnode.size();
3307 const bool to_this_server = ntgt > 0;
3308 const T nrep = num_traits<T>::from_int(
int(ntgt > 0 ? ntgt : 1));
3309 const T share = T(one / nrep);
3310 const T callmean = lqn.callproc_mean[cidx];
3311 const std::size_t cc = callcls[cidx];
3312 const std::size_t ax = auxcallcls[cidx];
3313 const bool below = callmean < one;
3314 const bool above = callmean > one;
3315 if (st.jobpos == atClient) {
3316 if (to_this_server) {
3318 m.set_route(st.curclass, ax, clientNode, clientNode, T(one - callmean));
3319 for (std::size_t r = 0; r < ntgt; ++r) {
3320 m.set_route(st.curclass, cc, clientNode, tnode[r], T(callmean / nrep));
3321 m.set_route(cc, cc, tnode[r], clientNode, one);
3324 m.set_route(ax, cc, clientNode, clientNode, one);
3326 for (std::size_t r = 0; r < ntgt; ++r) {
3327 m.set_route(st.curclass, cc, clientNode, tnode[r], share);
3328 m.set_route(cc, ax, tnode[r], clientNode, one);
3329 m.set_route(ax, cc, clientNode, tnode[r], T((one - one / callmean) / nrep));
3331 m.set_route(ax, cc, clientNode, clientNode, T(one / callmean));
3333 for (std::size_t r = 0; r < ntgt; ++r) {
3334 m.set_route(st.curclass, cc, clientNode, tnode[r], share);
3335 m.set_route(cc, cc, tnode[r], clientNode, one);
3338 for (std::size_t r = 0; r < ntgt; ++r) {
3339 m.set_service(tstat[r], cc, callservtproc[cidx]);
3340 call_map.push_back({idx, cidx, tstat[r], cc});
3343 st.jobpos = atClient;
3344 st.curnodes.clear();
3347 m.set_route(st.curclass, cc, clientNode, clientNode, one);
3348 if (below || above) {
3349 m.set_route(cc, ax, clientNode, clientNode, one);
3354 st.jobpos = atClient;
3355 st.curnodes.clear();
3356 m.set_service(m.clientIdx, cc, callservtproc[cidx]);
3357 call_map.push_back({idx, cidx, m.clientIdx, cc});
3361 auto fromNode = [&](std::size_t r) {
3362 return st.curnodes[std::min(r, st.curnodes.size() - 1)];
3364 if (to_this_server) {
3373 for (std::size_t r = 0; r < ntgt; ++r) {
3374 m.set_route(st.curclass, ax, fromNode(r), clientNode, T(one - callmean));
3375 m.set_route(st.curclass, cc, fromNode(r), tnode[r], T(callmean / nrep));
3376 m.set_route(cc, cc, tnode[r], clientNode, one);
3378 m.set_route(ax, cc, clientNode, clientNode, one);
3380 st.jobpos = atClient;
3381 st.curnodes.clear();
3387 for (std::size_t r = 0; r < ntgt; ++r) {
3388 m.set_route(st.curclass, cc, fromNode(r), tnode[r], one);
3389 m.set_route(cc, ax, tnode[r], clientNode, one);
3390 m.set_route(ax, cc, clientNode, tnode[r],
3391 T((one - one / callmean) / nrep));
3393 m.set_route(ax, cc, clientNode, clientNode, T(one / callmean));
3397 for (std::size_t r = 0; r < ntgt; ++r) {
3398 m.set_route(st.curclass, cc, fromNode(r), tnode[r], one);
3399 m.set_route(cc, cc, tnode[r], tnode[r], T(one - one / callmean));
3400 m.set_route(cc, ax, tnode[r], clientNode, T(one / callmean));
3404 st.jobpos = atClient;
3405 st.curnodes.clear();
3407 for (std::size_t r = 0; r < ntgt; ++r)
3408 m.set_route(st.curclass, cc, fromNode(r), tnode[r], one);
3412 for (std::size_t r = 0; r < ntgt; ++r)
3413 m.set_route(cc, cc, tnode[r], clientNode, one);
3415 st.jobpos = atClient;
3416 st.curnodes.clear();
3418 st.jobpos = atServer;
3419 st.curnodes = tnode;
3423 for (std::size_t r = 0; r < ntgt; ++r) {
3424 m.set_service(tstat[r], cc, callservtproc[cidx]);
3425 call_map.push_back({idx, cidx, tstat[r], cc});
3428 for (std::size_t nd : st.curnodes)
3429 m.set_route(st.curclass, cc, nd, clientNode, one);
3430 if (below || above) {
3431 m.set_route(cc, ax, clientNode, clientNode, one);
3436 st.jobpos = atClient;
3437 st.curnodes.clear();
3438 m.set_service(m.clientIdx, cc, callservtproc[cidx]);
3439 call_map.push_back({idx, cidx, m.clientIdx, cc});
3454 void build_entry_service_matrix() {
3455 const std::size_t dim = lqn.nidx + lqn.ncalls;
3456 servtmatrix =
Matrix<T>(dim + 1, dim + 1, Tzero());
3457 std::function<void(std::size_t, std::size_t)> rec = [&](std::size_t aidx, std::size_t eidx) {
3458 for (std::size_t nextaidx : lqn.graph.succ(aidx)) {
3459 const bool isLoop = lqn.graph.get(aidx, nextaidx) != lqn.dag.get(aidx, nextaidx);
3460 if (lqn.parent[aidx] != lqn.parent[nextaidx]) {
3461 for (std::size_t cidx : lqn.callsof[aidx])
3462 if (lqn.calltype[cidx] == CallType::SYNC)
3463 servtmatrix(eidx, lqn.nidx + cidx) = Tone();
3464 }
else if (nextaidx != aidx && !isLoop) {
3465 servtmatrix(eidx, nextaidx) = Tone();
3466 rec(nextaidx, eidx);
3470 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
3471 const std::size_t eidx = lqn.eshift + e;
3489 bool has_interlock() {
3490 if (!opt.interlocking)
return false;
3491 if (il_num_sources.empty()) init_interlock();
3492 for (std::size_t s = 0; s < il_num_sources.size(); ++s)
3493 if (il_num_sources[s] > 0.0 && s < il_common_entries.size() &&
3494 !il_common_entries[s].empty())
3499 void init_interlock() {
3500 const std::size_t NE = lqn.nentries;
3501 il_all =
Matrix<T>(NE + 1, NE + 1, Tzero());
3502 il_ph1 =
Matrix<T>(NE + 1, NE + 1, Tzero());
3504 std::function<void(std::size_t, std::size_t, T, T, std::vector<bool>&,
int)> trace =
3505 [&](std::size_t eidx, std::size_t root_e, T pall, T pph1, std::vector<bool>& visited,
3507 if (eidx <= lqn.eshift || eidx > lqn.eshift + NE)
return;
3508 const std::size_t e = eidx - lqn.eshift;
3509 if (visited[e])
return;
3511 il_all(root_e, e) = T(il_all(root_e, e) + pall);
3512 il_ph1(root_e, e) = T(il_ph1(root_e, e) + pph1);
3513 for (std::size_t aidx : lqn.actsof[eidx]) {
3514 if (aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts)
continue;
3515 const std::size_t a = aidx - lqn.ashift;
3516 if (depth > 0 && lqn.actphase[a] > 1)
continue;
3517 const bool is_ph1 = lqn.actphase[a] <= 1;
3518 for (std::size_t cidx : lqn.callsof[aidx]) {
3519 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
3520 if (!(lqn.callproc_mean[cidx] > Tzero()))
continue;
3521 const std::size_t dst = lqn.callpair_dst[cidx];
3522 if (dst <= lqn.eshift || dst > lqn.eshift + NE)
continue;
3523 trace(dst, root_e, T(pall * lqn.callproc_mean[cidx]),
3524 is_ph1 ? T(pph1 * lqn.callproc_mean[cidx]) : Tzero(), visited,
3530 for (std::size_t e = 1; e <= NE; ++e) {
3531 std::vector<bool> visited(NE + 1,
false);
3532 trace(lqn.eshift + e, e, Tone(), Tone(), visited, 0);
3535 il_common_entries.assign(NT() + 1, {});
3536 il_src_all.assign(NT() + 1, {});
3537 il_src_ph2.assign(NT() + 1, {});
3538 il_num_sources.assign(NT() + 1, 0.0);
3540 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
3541 const std::size_t tidx = lqn.tshift + t;
3542 if (lqn.isref[tidx] || lqn.sched[tidx] == SchedStrategy::INF)
continue;
3543 interlock_for_server(tidx);
3545 for (std::size_t h = 1; h <= lqn.nhosts; ++h) {
3546 if (lqn.sched[h] == SchedStrategy::INF)
continue;
3547 interlock_for_server(h);
3551 std::vector<std::size_t> server_entry_nums(std::size_t serverIdx)
const {
3552 std::vector<std::size_t> out;
3553 if (serverIdx <= lqn.nhosts) {
3554 for (std::size_t tidx : lqn.tasksof[serverIdx])
3555 for (std::size_t se : lqn.entriesof[tidx]) out.push_back(se - lqn.eshift);
3557 for (std::size_t se : lqn.entriesof[serverIdx]) out.push_back(se - lqn.eshift);
3562 std::vector<std::size_t> client_tasks(std::size_t serverIdx)
const {
3563 std::vector<std::size_t> out;
3564 if (serverIdx <= lqn.nhosts)
return lqn.tasksof[serverIdx];
3565 for (std::size_t se : lqn.entriesof[serverIdx])
3566 for (std::size_t ci : lqn.iscaller.col(se))
3567 if (ci > lqn.tshift && ci <= NT()) out.push_back(ci);
3568 std::sort(out.begin(), out.end());
3569 out.erase(std::unique(out.begin(), out.end()), out.end());
3573 std::vector<std::size_t> call_dst_tasks(std::size_t src_eidx, std::size_t target_e)
const {
3574 std::vector<std::size_t> out;
3575 for (std::size_t aidx : lqn.actsof[src_eidx]) {
3576 if (aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts)
continue;
3577 for (std::size_t cidx : lqn.callsof[aidx]) {
3578 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
3579 const std::size_t dst = lqn.callpair_dst[cidx];
3580 const std::size_t de = dst - lqn.eshift;
3581 if (de >= 1 && de <= lqn.nentries && il_all(de, target_e) > Tzero())
3582 out.push_back(lqn.parent[dst]);
3585 std::sort(out.begin(), out.end());
3586 out.erase(std::unique(out.begin(), out.end()), out.end());
3590 bool is_branch_point(std::size_t srcX, std::size_t entryA, std::size_t srcY,
3591 std::size_t entryB)
const {
3592 const std::size_t taskA = lqn.parent[entryA], taskB = lqn.parent[entryB];
3593 const std::size_t taskX = lqn.parent[srcX];
3594 if (taskX == taskA && taskX == taskB)
return false;
3595 if (srcX == entryA || srcY == entryB)
return true;
3596 const std::vector<std::size_t> dx = call_dst_tasks(srcX, entryA - lqn.eshift);
3597 const std::vector<std::size_t> dy = call_dst_tasks(srcY, entryB - lqn.eshift);
3598 for (std::size_t a : dx)
3599 for (std::size_t b : dy)
3600 if (a != b)
return true;
3604 void trace_to_server(std::size_t eidx, std::size_t serverIdx, std::vector<bool>& visited,
3605 std::vector<std::size_t>& itasks,
bool isHead)
const {
3606 if (eidx <= lqn.eshift || eidx > lqn.eshift + lqn.nentries)
return;
3607 const std::size_t e = eidx - lqn.eshift;
3608 if (visited[e])
return;
3609 const std::size_t owner = lqn.parent[eidx];
3610 if (owner == serverIdx)
return;
3611 if (serverIdx <= lqn.nhosts && lqn.parent[owner] == serverIdx)
return;
3614 const std::vector<std::size_t> sen = server_entry_nums(serverIdx);
3615 for (std::size_t aidx : lqn.actsof[eidx]) {
3616 if (aidx <= lqn.ashift || aidx > lqn.ashift + lqn.nacts)
continue;
3617 for (std::size_t cidx : lqn.callsof[aidx]) {
3618 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
3619 const std::size_t dst = lqn.callpair_dst[cidx];
3620 const std::size_t dtask = lqn.parent[dst];
3621 bool reaches = dtask == serverIdx ||
3622 (serverIdx <= lqn.nhosts && lqn.parent[dtask] == serverIdx);
3624 const std::size_t de = dst - lqn.eshift;
3625 for (std::size_t sn : sen)
3626 if (il_all(de, sn) > Tzero()) reaches =
true;
3629 trace_to_server(dst, serverIdx, visited, itasks,
false);
3634 if (found && !isHead) {
3635 itasks.push_back(owner);
3636 std::sort(itasks.begin(), itasks.end());
3637 itasks.erase(std::unique(itasks.begin(), itasks.end()), itasks.end());
3642 void interlock_for_server(std::size_t serverIdx) {
3643 const std::vector<std::size_t> sen = server_entry_nums(serverIdx);
3644 if (sen.empty())
return;
3645 const std::vector<std::size_t> cts = client_tasks(serverIdx);
3646 if (cts.empty())
return;
3648 std::vector<std::pair<std::size_t, std::size_t>> pairs;
3649 for (std::size_t ct : cts)
3650 for (std::size_t ce : lqn.entriesof[ct]) {
3651 const std::size_t cen = ce - lqn.eshift;
3652 if (cen < 1 || cen > lqn.nentries)
continue;
3653 for (std::size_t se : sen)
3654 if (il_all(cen, se) > Tzero()) {
3655 pairs.emplace_back(ct, cen);
3659 if (pairs.size() < 2)
return;
3664 std::vector<bool> reach_ph1(lqn.nentries + 1,
false);
3665 for (
const auto& p : pairs)
3666 for (std::size_t se : sen)
3667 if (il_ph1(p.second, se) > Tzero()) {
3668 reach_ph1[p.second] =
true;
3672 std::vector<std::size_t> common;
3673 for (std::size_t i = 0; i < pairs.size(); ++i)
3674 for (std::size_t j = i + 1; j < pairs.size(); ++j) {
3675 if (pairs[i].first == pairs[j].first)
continue;
3676 const std::size_t eA = pairs[i].second, eC = pairs[j].second;
3677 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
3678 const std::size_t tidx = lqn.tshift + t;
3679 for (std::size_t ex : lqn.entriesof[tidx])
3680 for (std::size_t ey : lqn.entriesof[tidx]) {
3681 const std::size_t xn = ex - lqn.eshift, yn = ey - lqn.eshift;
3682 if (xn < 1 || yn < 1 || xn > lqn.nentries || yn > lqn.nentries)
continue;
3683 if (il_all(xn, eA) > Tzero() && il_all(yn, eC) > Tzero() &&
3684 (reach_ph1[eA] || xn == eA) && (reach_ph1[eC] || yn == eC) &&
3685 is_branch_point(ex, eA + lqn.eshift, ey, eC + lqn.eshift))
3686 common.push_back(ex);
3690 std::sort(common.begin(), common.end());
3691 common.erase(std::unique(common.begin(), common.end()), common.end());
3692 if (common.empty())
return;
3694 std::vector<std::size_t> interlocked;
3695 for (std::size_t ce : common) {
3696 std::vector<bool> visited(lqn.nentries + 1,
false);
3697 std::vector<std::size_t> it;
3698 trace_to_server(ce, serverIdx, visited, it,
true);
3699 for (std::size_t x : it) interlocked.push_back(x);
3701 std::sort(interlocked.begin(), interlocked.end());
3702 interlocked.erase(std::unique(interlocked.begin(), interlocked.end()), interlocked.end());
3704 std::vector<std::size_t> src_all;
3705 for (std::size_t ce : common) src_all.push_back(lqn.parent[ce]);
3706 std::sort(src_all.begin(), src_all.end());
3707 src_all.erase(std::unique(src_all.begin(), src_all.end()), src_all.end());
3709 std::vector<std::size_t> diff;
3710 for (std::size_t x : src_all)
3711 if (!std::binary_search(interlocked.begin(), interlocked.end(), x)) diff.push_back(x);
3719 std::vector<std::size_t> src_ph2;
3720 for (std::size_t it : interlocked)
3721 for (std::size_t ie : lqn.entriesof[it])
3722 for (std::size_t ci : lqn.iscaller.col(ie))
3723 if (ci > lqn.tshift && ci <= NT() &&
3724 !std::binary_search(interlocked.begin(), interlocked.end(), ci))
3725 src_all.push_back(ci);
3726 std::sort(src_all.begin(), src_all.end());
3727 src_all.erase(std::unique(src_all.begin(), src_all.end()), src_all.end());
3730 for (std::size_t st : src_all) nsrc += lqn.mult[st];
3732 il_common_entries[serverIdx] = common;
3733 il_src_all[serverIdx] = src_all;
3734 il_src_ph2[serverIdx] = src_ph2;
3735 il_num_sources[serverIdx] = nsrc;
3742 const std::size_t N = lqn.nidx;
3743 tput.assign(N + 1, Tzero());
3744 util.assign(N + 1, Tzero());
3745 servt.assign(N + 1, Tzero());
3746 residt.assign(N + 1, Tzero());
3747 thinkt.assign(N + 1, Tzero());
3748 callservt.assign(lqn.ncalls + 1, Tzero());
3749 callresidt.assign(lqn.ncalls + 1, Tzero());
3751 servt_ph1.assign(N + 1, Tzero());
3752 servt_ph2.assign(N + 1, Tzero());
3753 prOvertake.assign(lqn.nentries + 1, Tzero());
3754 build_entry_service_matrix();
3756 relax_omega = (opt.relax ==
"fixed" || opt.relax ==
"adaptive") ? opt.relax_factor : 1.0;
3758 servt_prev.assign(N + 1, std::numeric_limits<double>::quiet_NaN());
3759 residt_prev.assign(N + 1, std::numeric_limits<double>::quiet_NaN());
3760 tput_prev.assign(N + 1, std::numeric_limits<double>::quiet_NaN());
3761 thinkt_prev.assign(N + 1, std::numeric_limits<double>::quiet_NaN());
3762 callservt_prev.assign(lqn.ncalls + 1, std::numeric_limits<double>::quiet_NaN());
3763 callresidt_prev.assign(lqn.ncalls + 1, std::numeric_limits<double>::quiet_NaN());
3764 servt_prev_v.assign(N + 1, Tzero());
3765 residt_prev_v.assign(N + 1, Tzero());
3766 tput_prev_v.assign(N + 1, Tzero());
3767 thinkt_prev_v.assign(N + 1, Tzero());
3768 callservt_prev_v.assign(lqn.ncalls + 1, Tzero());
3770 unique_route_idx.clear();
3771 for (
const RouteRow& r : route_map) unique_route_idx.push_back(r.idx);
3772 std::sort(unique_route_idx.begin(), unique_route_idx.end());
3773 unique_route_idx.erase(std::unique(unique_route_idx.begin(), unique_route_idx.end()),
3774 unique_route_idx.end());
3777 if (interlockMethod ==
"ilrate") init_interlock();
3779 maxitererr.assign(opt.iter_max + 2, 0.0);
3780 results_raw_last.clear();
3781 results_raw_valid =
false;
3782 averagingstart = -1;
3783 hasconverged =
false;
3784 moment_pass_done =
false;
3787 servtcdf.assign(N + 1, fluid::FluidPassage());
3788 callservtcdf.assign(lqn.ncalls + 1, fluid::FluidPassage());
3789 entrycdfrespt.assign(lqn.nentries + 1, LnCdf());
3790 entryproc.assign(lqn.nentries + 1, mam::AphPair<T>());
3791 cdf_repo.assign(ensemble.size(), std::vector<std::vector<fluid::FluidPassage>>());
3795 warmstarted_ =
false;
3796 if (opt.warmstart ==
"nlp") {
3798 }
else if (opt.warmstart ==
"mol") {
3814 const bool stoch = layer_solver_is_stochastic();
3817 cfg.iter_tol = opt.iter_tol;
3818 cfg.relax_burnin = relax_omega;
3819 stoch_ctl = std::make_shared<LnStochController<T>>(cfg);
3824 bool done = !stoch && converged(it);
3825 while (!done && it < opt.iter_max) {
3826 if (results_raw_valid) {
3827 results.back() = results_raw_last;
3828 results_raw_valid =
false;
3831 results.emplace_back(ensemble.size());
3832 for (std::size_t e = 0; e < ensemble.size(); ++e) analyze(it, e);
3835 std::vector<double> jobs(ensemble.size(), 0.0);
3836 for (std::size_t e = 0; e < ensemble.size(); ++e)
3837 jobs[e] = ensemble[e].total_jobs();
3838 const bool stop = stoch_ctl->update(it, results.back(), jobs, servt, residt);
3841 relax_omega = stoch_ctl->relax_omega();
3843 did_converge =
true;
3844 hasconverged =
true;
3848 done = converged(it);
3851 iterations_done = it;
3854 if (stoch && stoch_ctl && stoch_ctl->averaging_count() > 0) {
3855 const std::vector<LayerResult<T>>& avg = stoch_ctl->averaged_results();
3856 for (std::size_t e = 0; e < results.back().size() && e < avg.size(); ++e)
3857 results.back()[e] = avg[e];
3858 servt = stoch_ctl->averaged_servt();
3859 residt = stoch_ctl->averaged_residt();
3883 Matrix<T> filter_metric(
const qn::Layer<T>& L,
const Matrix<T>& metric,
3884 const std::vector<std::vector<bool>>* zero_mask)
const {
3885 const std::size_t M = L.nstations, K = L.nclasses;
3886 Matrix<T> out(M, K, Tzero());
3887 for (std::size_t i = 0; i < M; ++i)
3888 for (std::size_t k = 0; k < K; ++k)
3889 if (!L.disabled[i][k]) out(i, k) = metric(i, k);
3891 for (std::size_t i = 0; i < M; ++i)
3892 for (std::size_t k = 0; k < K; ++k)
3893 if ((*zero_mask)[i][k]) out(i, k) = Tzero();
3894 for (std::size_t i = 0; i < M; ++i)
3895 for (std::size_t k = 0; k < K; ++k)
3897 for (std::size_t k = 0; k < K; ++k) {
3898 std::size_t c = L.nchains;
3899 for (std::size_t cc = 0; cc < L.nchains; ++cc)
3900 if (L.chains[cc][k]) c = cc;
3901 if (c == L.nchains)
continue;
3902 for (std::size_t i = 0; i < M; ++i)
3903 if (L.visits[c](L.stateful_of_station(i + 1) - 1, k) == Tzero())
3904 out(i, k) = Tzero();
3921 mva::MvaSolution<T> solve_layer_ctmc(std::size_t e) {
3922 refuse_fork_layer(e);
3923 qn::Layer<T>& L = ensemble[e];
3928 const ctmc::CtmcOptions& co = opt.layer_ctmc;
3933 mva::MvaSolution<T> out;
3934 out.method =
"ctmc";
3955 mva::MvaSolution<T> solve_layer_mam(std::size_t e) {
3956 refuse_fork_layer(e);
3957 qn::Layer<T>& L = ensemble[e];
3959 L.refresh_capacity();
3966 void refuse_fork_layer(std::size_t e)
const {
3967 if (fj_tr[e].active())
3969 "SolverLN: layer '" + ensemble[e].name +
3970 "' carries a fork, whose fixed point is driven by MVA in this port; solve this "
3971 "model with layer_solver 'mva'");
3984 mva::MvaSolution<T> solve_layer_nc(std::size_t e) {
3985 if (fj_tr[e].active())
3987 "SolverLN: layer '" + ensemble[e].name +
3988 "' carries a fork, whose fixed point is driven by MVA in this port; solve this "
3989 "model with layer_solver 'mva'");
3990 qn::Layer<T>& L = ensemble[e];
4020 mva::MvaSolution<T> solve_layer_ssa(std::size_t e) {
4021 if (fj_tr[e].active())
4023 "SolverLN: layer '" + ensemble[e].name +
4024 "' carries a fork, whose fixed point is driven by MVA in this port; solve this "
4025 "model with layer_solver 'mva'");
4026 qn::Layer<T>& L = ensemble[e];
4029 mva::MvaSolution<T> out;
4032 out.Q =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4033 out.U =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4034 out.R =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4035 out.Tp =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4036 for (std::size_t i = 0; i < L.nstations; ++i)
4037 for (std::size_t r = 0; r < L.nclasses; ++r) {
4038 out.Q(i, r) = num_traits<T>::from_double(s.QN(i, r));
4039 out.U(i, r) = num_traits<T>::from_double(s.UN(i, r));
4040 out.R(i, r) = num_traits<T>::from_double(s.RN(i, r));
4041 out.Tp(i, r) = num_traits<T>::from_double(s.TN(i, r));
4043 out.C.assign(L.nclasses, Tzero());
4044 out.X.assign(L.nclasses, Tzero());
4045 for (std::size_t r = 0; r < L.nclasses && r < s.XN.size(); ++r) {
4046 out.X[r] = num_traits<T>::from_double(s.XN[r]);
4047 out.C[r] = num_traits<T>::from_double(s.CN[r]);
4063 mva::MvaSolution<T> solve_layer_ldes(std::size_t e) {
4064 refuse_fork_layer(e);
4065 qn::Layer<T>& L = ensemble[e];
4068 if constexpr (!std::is_same<T, double>::value) {
4070 "SolverLN: the ldes layer engine simulates in double precision; "
4071 "rerun with --arith double");
4074 ldes::LdesOptions lo = opt.layer_ldes;
4075 lo.seed = layer_seed(opt.layer_ldes.seed, e);
4077 mva::MvaSolution<T> out = sim_layer_solution(L, s.QN, s.UN, s.RN, s.TN,
"ldes");
4078 for (std::size_t r = 0; r < L.nclasses && r < s.XN.cols(); ++r) {
4079 out.X[r] = s.XN(0, r);
4080 out.C[r] = s.CN(0, r);
4095 mva::MvaSolution<T> solve_layer_jmt(std::size_t e) {
4096 refuse_fork_layer(e);
4097 qn::Layer<T>& L = ensemble[e];
4098 for (std::size_t i = 0; i < L.immfeed.size(); ++i)
4099 for (std::size_t r = 0; r < L.immfeed[i].size(); ++r)
4100 if (L.immfeed[i][r])
4102 "SolverJMT cannot solve LN layer " + std::to_string(e + 1) +
4103 ": the layer carries immediate feedback (sn.immfeed), which SolverJMT does "
4104 "not support, so LN would fail at the first iteration. Use the default layer "
4105 "factory (MVA/NC) or another layer solver that supports immediate feedback.");
4107 jmt::JmtOptions jo = opt.layer_jmt;
4108 jo.seed = layer_seed(opt.layer_jmt.seed, e);
4111 Matrix<double> Q(L.nstations, L.nclasses, 0.0), U = Q, R = Q, X = Q;
4112 for (std::size_t i = 0; i < L.nstations; ++i)
4113 for (std::size_t r = 0; r < L.nclasses; ++r) {
4114 Q(i, r) = num_traits<T>::to_double(s.avg.QN(i, r));
4115 U(i, r) = num_traits<T>::to_double(s.avg.UN(i, r));
4116 R(i, r) = num_traits<T>::to_double(s.avg.RN(i, r));
4117 X(i, r) = num_traits<T>::to_double(s.avg.TN(i, r));
4119 mva::MvaSolution<T> out = sim_layer_solution(L, Q, U, R, X,
"jmt");
4120 for (std::size_t r = 0; r < L.nclasses; ++r) {
4121 if (r < s.avg.XN.size()) out.X[r] = s.avg.XN[r];
4122 if (r < s.avg.CN.size()) out.C[r] = s.avg.CN[r];
4128 static mva::MvaSolution<T> sim_layer_solution(
const qn::Layer<T>& L,
const Matrix<double>& Q,
4129 const Matrix<double>& U,
const Matrix<double>& R,
4130 const Matrix<double>& X,
const char* method) {
4131 mva::MvaSolution<T> out;
4132 out.method = method;
4134 out.Q =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4135 out.U =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4136 out.R =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4137 out.Tp =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4138 for (std::size_t i = 0; i < L.nstations; ++i)
4139 for (std::size_t r = 0; r < L.nclasses; ++r) {
4140 out.Q(i, r) = num_traits<T>::from_double(Q(i, r));
4141 out.U(i, r) = num_traits<T>::from_double(U(i, r));
4142 out.R(i, r) = num_traits<T>::from_double(R(i, r));
4143 out.Tp(i, r) = num_traits<T>::from_double(X(i, r));
4145 out.C.assign(L.nclasses, Tzero());
4146 out.X.assign(L.nclasses, Tzero());
4172 mva::MvaSolution<T> solve_layer_fluid(std::size_t e) {
4173 if (fj_tr[e].active())
4175 "SolverLN: layer '" + std::to_string(e) +
4176 "' carries a fork, whose fixed point is driven by MVA in this port; solve this "
4177 "model with layer_solver 'mva'");
4178 qn::Layer<T>& L = ensemble[e];
4188 mva::MvaSolution<T> out;
4189 out.method =
"fluid";
4191 out.Q =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4192 out.U =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4193 out.R =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4194 out.Tp =
Matrix<T>(L.nstations, L.nclasses, Tzero());
4195 out.C.assign(L.nclasses, Tzero());
4196 out.X.assign(L.nclasses, Tzero());
4197 detail::ln_fluid_solve(L, opt.layer_fluid, out);
4213 mva::MvaSolution<T> solve_layer(std::size_t e) {
4223 ensemble[e].refresh_capacity();
4233 if (!ensemble[e].regions.empty() && opt.layer_solver !=
"ldes" && opt.layer_solver !=
"jmt")
4234 return solve_layer_ctmc(e);
4247 if (has_cache_node(e)) ensemble[e].refresh_rt();
4248 if (opt.layer_solver ==
"fluid")
return solve_layer_fluid(e);
4249 if (opt.layer_solver ==
"nc")
return solve_layer_nc(e);
4250 if (opt.layer_solver ==
"ssa")
return solve_layer_ssa(e);
4251 if (opt.layer_solver ==
"mam")
return solve_layer_mam(e);
4252 if (opt.layer_solver ==
"ctmc")
return solve_layer_ctmc(e);
4253 if (opt.layer_solver ==
"ldes")
return solve_layer_ldes(e);
4254 if (opt.layer_solver ==
"jmt")
return solve_layer_jmt(e);
4255 if (opt.layer_solver !=
"mva")
4256 throw UnsupportedError(ErrorKind{
"Option"},
"SolverLN: layer_solver '" + opt.layer_solver +
4257 "' is not available; use 'mva', 'nc', 'fluid', 'ssa', 'mam', 'ctmc', "
4265 mva::MvaOptions eopt = opt.layer;
4266 if (e < layer_interlock.size()) eopt.interlock = layer_interlock[e];
4267 if (!fj_tr[e].active())
4269 mva::MvaOptions lopt = eopt;
4270 if (lopt.method ==
"default") lopt.method =
"amva";
4272 ensemble[e], fj_tr[e], fj_lambda[e], eopt,
4273 [&lopt](qn::NetworkStruct<T>& V) {
4278 void analyze(
int it, std::size_t e) {
4279 qn::Layer<T>& L = ensemble[e];
4281 const mva::MvaSolution<T> s = solve_layer(e);
4282 LayerResult<T>& r = results[it - 1][e];
4283 r.RN = filter_metric(L, s.R,
nullptr);
4284 std::vector<std::vector<bool>> zmask(L.nstations, std::vector<bool>(L.nclasses,
false));
4285 for (std::size_t i = 0; i < L.nstations; ++i)
4286 for (std::size_t k = 0; k < L.nclasses; ++k)
4288 r.QN = filter_metric(L, s.Q, &zmask);
4289 r.UN = filter_metric(L, s.U, &zmask);
4290 r.TN = filter_metric(L, s.Tp,
nullptr);
4291 r.WN = residence_from_response(L, r.RN);
4301 if (!fj_tr[e].active() && opt.layer_solver ==
"mva") {
4302 Matrix<T> Qch(L.nstations, L.nchains, Tzero());
4303 for (std::size_t c = 0; c < L.nchains; ++c)
4304 for (std::size_t i = 0; i < L.nstations; ++i) {
4306 for (std::size_t k : L.inchain[c]) s2 += s.Q(i, k - 1);
4309 layer_init_sol[e] = Qch;
4314 Matrix<T> residence_from_response(
const qn::Layer<T>& L,
const Matrix<T>& RN)
const {
4315 Matrix<T> V(L.nstations, L.nclasses, Tzero());
4316 for (std::size_t c = 0; c < L.nchains; ++c)
4317 for (std::size_t i = 0; i < L.nstations; ++i) {
4318 const std::size_t sf = L.stateful_of_station(i + 1) - 1;
4319 for (std::size_t k = 0; k < L.nclasses; ++k)
4320 V(i, k) = T(V(i, k) + L.visits[c](sf, k));
4322 Matrix<T> WN(L.nstations, L.nclasses, Tzero());
4323 for (std::size_t i = 0; i < L.nstations; ++i)
4324 for (std::size_t k = 0; k < L.nclasses; ++k) {
4325 if (L.disabled[i][k])
continue;
4326 if (!(RN(i, k) > Tzero()))
continue;
4328 WN(i, k) = RN(i, k);
4332 for (std::size_t cc = 0; cc < L.nchains; ++cc)
4333 if (L.chains[cc][k]) c = cc;
4334 const std::size_t rstat = L.classes[k].refstat;
4336 if (L.refclass[c] > 0) {
4337 den = V(rstat - 1, L.refclass[c] - 1);
4339 for (std::size_t kk : L.inchain[c]) den += V(rstat - 1, kk - 1);
4341 if (den == Tzero())
continue;
4342 WN(i, k) = T(RN(i, k) * V(i, k) / den);
4344 for (std::size_t i = 0; i < L.nstations; ++i)
4345 for (std::size_t k = 0; k < L.nclasses; ++k)
4354 if (interlockMethod ==
"ilrate") update_populations(it);
4355 update_think_times(it);
4357 update_routing_probabilities(it);
4358 for (std::size_t e : route_reset) {
4359 ensemble[e].refresh_chains();
4365 for (std::size_t e : svc_reset) ensemble[e].refresh_rates();
4369 bool converged(
int it) {
4370 const std::size_t E = ensemble.size();
4371 const int iter_min = std::max<int>(2 *
int(E),
int(std::ceil(opt.iter_max / 4.0)));
4372 const int wnd_size = std::max(5,
int(std::ceil(iter_min / 5.0)));
4374 if (it >= iter_min &&
int(results.size()) >= wnd_size) {
4375 results_raw_last = results.back();
4376 results_raw_valid =
true;
4377 const T w = T(Tone() / num_traits<T>::from_int(wnd_size));
4378 for (std::size_t e = 0; e < E; ++e) {
4379 LayerResult<T>& cur = results[results.size() - 1][e];
4380 auto scale = [&](Matrix<T>& A) {
4381 for (std::size_t i = 0; i < A.rows(); ++i)
4382 for (std::size_t j = 0; j < A.cols(); ++j) A(i, j) = T(A(i, j) * w);
4384 Matrix<T> Q = cur.QN, U = cur.UN, R = cur.RN, Tp = cur.TN, W = cur.WN;
4385 scale(Q); scale(U); scale(R); scale(Tp); scale(W);
4386 for (
int k = 1; k < wnd_size; ++k) {
4387 const LayerResult<T>& old = results[results.size() - 1 - k][e];
4388 auto add = [&](Matrix<T>& A,
const Matrix<T>& B) {
4389 for (std::size_t i = 0; i < A.rows(); ++i)
4390 for (std::size_t j = 0; j < A.cols(); ++j) A(i, j) = T(A(i, j) + B(i, j) * w);
4392 add(Q, old.QN); add(U, old.UN); add(R, old.RN); add(Tp, old.TN); add(W, old.WN);
4394 cur.QN = Q; cur.UN = U; cur.RN = R; cur.TN = Tp; cur.WN = W;
4400 for (std::size_t e = 0; e < E; ++e) {
4401 const Matrix<T>& Q = results[results.size() - 1][e].QN;
4402 const Matrix<T>& Q1 = results[results.size() - 2][e].QN;
4403 const double Njobs = ensemble[e].total_jobs();
4404 if (!(Njobs > 0.0))
continue;
4406 for (std::size_t i = 0; i < Q.rows(); ++i)
4407 for (std::size_t j = 0; j < Q.cols(); ++j)
4408 mx = std::max(mx, std::fabs(dbl(Q(i, j)) - dbl(Q1(i, j))));
4411 maxitererr[it] =
err;
4413 static_cast<long>(it),
4414 "layer iteration %zu: max queue-length change %.3e (tolerance %.3e)",
4415 static_cast<std::size_t
>(it), err, opt.iter_tol);
4416 if (it == iter_min) {
4418 averagingstart = it;
4425 const int stop_floor = warmstarted_ ? 3 : iter_min;
4426 if (it > stop_floor && maxitererr[it] < opt.iter_tol && maxitererr[it - 1] < opt.iter_tol &&
4427 maxitererr[it - 2] < opt.iter_tol) {
4428 if (!hasconverged) {
4429 hasconverged =
true;
4431 did_converge =
true;
4435 hasconverged =
false;
4470 std::string lnmethod;
4472 bool ph_laws_ready =
false;
4489 static std::string ln_requested_method(
const std::string& method) {
4491 for (
char c : method) m +=
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
4492 if (m.empty() || m ==
"srvn" || m ==
"default" || m ==
"auto")
return "srvn";
4493 if (m ==
"srvn.ph" || m ==
"ph")
return "srvn.ph";
4494 if (m ==
"srvn.cs" || m ==
"srvncs" || m ==
"cs")
return "srvn.cs";
4495 if (m ==
"flat.cs" || m ==
"flatcs" || m ==
"flat" || m ==
"squashed")
return "flat.cs";
4496 if (m ==
"flat.ph" || m ==
"flatph" || m ==
"squashed.ph")
return "flat.ph";
4497 if (m ==
"moment3")
return "moment3";
4499 if (m ==
"nlp" || m ==
"qdamva" || m ==
"qd-amva")
return "nlp";
4509 void assert_warmstart_encoding()
const {
4510 if (!is_ph_encoding() || opt.warmstart.empty())
return;
4511 throw InputError(
"config['warmstart']='" + opt.warmstart +
"' cannot seed method='" + lnmethod +
4512 "': its layers carry a COMPOSED PHASE-TYPE service law and the seed solves for "
4513 "means, so there is no phase distribution to start it from. Use method='srvn.cs' "
4514 "for the same layering under the routing encoding, which those means do "
4523 void validate_nlp_options()
const {
4524 const bool nlp = ln_requested_method(opt.method) ==
"nlp";
4525 if (!opt.warmstart.empty()) {
4526 if (opt.warmstart !=
"nlp" && opt.warmstart !=
"mol")
4527 throw InputError(
"config['warmstart'] takes 'nlp', 'mol' or None, got '" + opt.warmstart +
4528 "'. 'nlp' seeds the layered fixed point with the QD-AMVA program's solution "
4529 "and 'mol' with the Method of Layers'; None starts it from the model's own "
4532 throw InputError(
"config['warmstart']='" + opt.warmstart +
4533 "' is a starting point for a LAYERED method, and method='nlp' runs no "
4534 "iteration to start. Drop one of the two.");
4536 if ((nlp || !opt.warmstart.empty()) && !std::is_same<T, double>::value)
4538 std::string(nlp ?
"SolverLN: method='nlp'" :
"SolverLN: config.warmstart") +
4539 " roots a double-precision program, so it has no exact answer to give; rerun with "
4544 static T fromd(
double v) {
return num_traits<T>::from_double(v); }
4547 std::vector<double> nlp_root(::line::lqn::NlpProgram& P)
const {
4549 std::vector<double> x =
P.solve(opt.nlp_warm, opt.nlp_maxiter).x;
4550 for (
double& v : x) v = std::max(v, 0.0);
4555 static std::vector<double> nlp_act_util(const ::line::lqn::NlpProgram& P,
const std::vector<double>& Xa) {
4556 std::vector<double> u(
P.NA, 0.0);
4557 for (std::size_t a = 0; a <
P.NA; ++a) {
4558 const std::size_t h =
P.task_host[
P.act_task[a]];
4559 const double busy =
P.act_demand[a] * Xa[a];
4560 u[a] =
P.host_delay[h] ? busy : busy /
P.host_maxmult[h];
4571 LnSolution<T> nlp_analyzer() {
4572 using P_ = ::line::lqn::NlpProgram;
4574 const std::vector<double> x = nlp_root(P);
4575 const double obj =
P.objective(x);
4577 nlp_objective_ = obj;
4578 hasconverged =
P.solved(x);
4579 did_converge = hasconverged;
4582 const std::size_t N = lqn.nidx;
4583 auto blank = [&](std::vector<T>& v, std::vector<bool>& d) {
4584 v.assign(N + 1, Tzero());
4585 d.assign(N + 1,
false);
4587 blank(s.QN, s.defined_Q);
4588 blank(s.UN, s.defined_U);
4589 blank(s.RN, s.defined_R);
4590 blank(s.TN, s.defined_T);
4591 blank(s.AN, s.defined_A);
4592 blank(s.WN, s.defined_W);
4593 auto put = [&](std::vector<T>& v, std::vector<bool>& d, std::size_t i,
double val) {
4597 const std::vector<double> Xe =
P.block(x, P_::XE), Xa =
P.block(x, P_::XA), W =
P.block(x, P_::W),
4598 S =
P.block(x, P_::S), R =
P.block(x, P_::R);
4599 const std::vector<double> util_a = nlp_act_util(P, Xa);
4600 for (std::size_t a = 0; a <
P.NA; ++a) {
4602 for (std::size_t c :
P.calls_of_act[a]) resp +=
P.call_mean[c] * R[c];
4603 const std::size_t aidx = lqn.ashift + a + 1;
4604 put(s.TN, s.defined_T, aidx, Xa[a]);
4605 put(s.RN, s.defined_R, aidx, resp);
4606 put(s.QN, s.defined_Q, aidx, Xa[a] * resp);
4607 put(s.UN, s.defined_U, aidx, util_a[a]);
4608 put(s.WN, s.defined_W, aidx,
P.act_visits[a] * W[a]);
4610 for (std::size_t e = 0; e <
P.NE; ++e) {
4611 const std::size_t eidx = lqn.eshift + e + 1;
4613 for (std::size_t a = 0; a <
P.NA; ++a)
4614 if (
P.act_entry[a] == e) u += util_a[a];
4615 put(s.TN, s.defined_T, eidx, Xe[e]);
4616 put(s.RN, s.defined_R, eidx, S[e]);
4617 put(s.QN, s.defined_Q, eidx, Xe[e] * S[e]);
4618 put(s.UN, s.defined_U, eidx, u);
4620 for (std::size_t t = 0; t <
P.NT; ++t) {
4621 double tn = 0.0, qn = 0.0, un = 0.0, wn = 0.0;
4622 for (std::size_t e = 0; e <
P.NE; ++e)
4623 if (
P.entry_task[e] == t) {
4627 for (std::size_t a = 0; a <
P.NA; ++a)
4628 if (
P.act_task[a] == t) {
4630 wn +=
P.act_visits[a] * W[a];
4632 const std::size_t tidx = lqn.tshift + t + 1;
4633 put(s.TN, s.defined_T, tidx, tn);
4634 put(s.QN, s.defined_Q, tidx, qn);
4635 put(s.UN, s.defined_U, tidx, un);
4636 put(s.WN, s.defined_W, tidx, wn);
4638 for (std::size_t h = 0; h <
P.NH; ++h) {
4640 for (std::size_t a :
P.acts_of_host[h]) un += util_a[a];
4641 put(s.UN, s.defined_U, lqn.hshift + h + 1, un);
4643 for (std::size_t i = 1; i <= N; ++i)
4645 put(s.QN, s.defined_Q, i, 0.0);
4646 put(s.UN, s.defined_U, i, 0.0);
4647 put(s.RN, s.defined_R, i, 0.0);
4648 put(s.TN, s.defined_T, i, 0.0);
4649 put(s.AN, s.defined_A, i, 0.0);
4650 put(s.WN, s.defined_W, i, 0.0);
4653 s.converged = hasconverged;
4655 s.warning =
"method='nlp' did not reach a root of the QD-AMVA laws (objective " +
4657 " residuals); the reported means satisfy the linear relations but not every congestion "
4658 "law. solve() already escalated the warm start to 100 sweeps, so raise "
4659 "config['nlp_warm'] past that to start it further along.";
4664 void seed_think_time(std::size_t tidx,
double X,
double busy,
double z,
bool inf,
double mult) {
4666 for (std::size_t c = 1; c <= NT(); ++c) nj = std::max(nj, njobs(tidx, c));
4667 const double val = inf ? (nj - busy) / X - z : nj * std::fabs(1.0 - busy / mult) / X - z;
4669 thinkt[tidx] = fromd(th);
4674 void seed_processes() {
4675 for (std::size_t i = 1; i <= lqn.nidx; ++i)
4677 for (std::size_t c = 1; c <= lqn.ncalls; ++c)
4682 void seed_layers() {
4684 for (std::size_t e : svc_reset) ensemble[e].refresh_rates();
4685 warmstarted_ =
true;
4693 void nlp_warm_start() {
4694 using P_ = ::line::lqn::NlpProgram;
4696 const std::vector<double> x = nlp_root(P);
4697 const std::vector<double> Xe =
P.block(x, P_::XE), Xa =
P.block(x, P_::XA), W =
P.block(x, P_::W),
4698 S =
P.block(x, P_::S), R =
P.block(x, P_::R), BC =
P.block(x, P_::BC),
4699 BR =
P.block(x, P_::BR);
4700 const std::vector<double> util_a = nlp_act_util(P, Xa);
4701 for (std::size_t a = 0; a <
P.NA; ++a) {
4702 const std::size_t idx = lqn.ashift + a + 1;
4704 for (std::size_t c :
P.calls_of_act[a]) st +=
P.call_mean[c] * R[c];
4705 servt[idx] = fromd(st);
4706 residt[idx] = fromd(
P.act_visits[a] * W[a]);
4707 tput[idx] = fromd(Xa[a]);
4708 util[idx] = fromd(util_a[a]);
4710 for (std::size_t e = 0; e <
P.NE; ++e) {
4711 const std::size_t idx = lqn.eshift + e + 1;
4712 servt[idx] = residt[idx] = fromd(S[e]);
4713 tput[idx] = fromd(Xe[e]);
4715 for (std::size_t t = 0; t <
P.NT; ++t) {
4717 for (std::size_t e = 0; e <
P.NE; ++e)
4718 if (
P.entry_task[e] == t) x_t += Xe[e];
4719 tput[lqn.tshift + t + 1] = fromd(x_t);
4721 for (std::size_t c = 0; c <
P.NC; ++c) {
4722 callservt[c + 1] = fromd(R[c]);
4723 callresidt[c + 1] = fromd(
P.call_mean[c] * R[c]);
4727 for (std::size_t t = 0; t <
P.NT; ++t) {
4728 const std::size_t idx = lqn.tshift + t + 1;
4729 const double X = dbl(tput[idx]);
4731 const auto r = std::find(
P.ref_tasks.begin(),
P.ref_tasks.end(), t);
4733 if (r !=
P.ref_tasks.end()) {
4734 busy = BR[std::size_t(r -
P.ref_tasks.begin())];
4736 for (std::size_t c :
P.calls_into_task[t]) busy += BC[c];
4738 const double mult =
P.task_mult[t];
4739 seed_think_time(idx, X, busy,
P.task_think[t],
4740 lqn.sched[idx] == SchedStrategy::INF || std::isinf(mult), mult);
4750 void mol_warm_start() {
4751 if constexpr (num_traits<T>::has_transcendental) {
4752 ::line::lqn::LqnMolOptions mo;
4757 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
4758 const std::size_t eidx = lqn.eshift + e;
4759 const std::size_t aidx = lqn.actsof[eidx].at(0);
4760 servt[eidx] = residt[eidx] = m.info.servt[eidx];
4761 tput[eidx] = m.TN[eidx];
4762 servt[aidx] = m.info.servt[eidx];
4763 residt[aidx] = m.info.residt[eidx];
4764 tput[aidx] = m.TN[eidx];
4765 util[aidx] = m.UN[eidx];
4767 for (std::size_t t = 1; t <= lqn.ntasks; ++t) tput[lqn.tshift + t] = m.TN[lqn.tshift + t];
4768 for (std::size_t c = 1; c <= lqn.ncalls; ++c) {
4769 double y = dbl(lqn.callproc_mean[c]);
4770 if (std::isnan(y)) y = 0.0;
4771 callservt[c] = m.info.callservt[c];
4772 callresidt[c] = T(fromd(y) * m.info.callservt[c]);
4776 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
4777 const std::size_t tidx = lqn.tshift + t;
4778 const double X = dbl(m.TN[tidx]);
4781 if (tidx < lqn.think.size() && !lqn.think[tidx].disabled) {
4782 z = dbl(lqn.think[tidx].mean);
4783 if (!std::isfinite(z) || z < 0.0) z = 0.0;
4785 double mult = tidx < lqn.mult.size() ? lqn.mult[tidx] : 1.0;
4786 if (mult == 0.0) mult = 1.0;
4787 seed_think_time(tidx, X, dbl(m.QN[tidx]), z,
4788 lqn.sched[tidx] == SchedStrategy::INF || !std::isfinite(mult),
4789 std::max(1.0, mult));
4795 "SolverLN: config.warmstart='mol' needs transcendental arithmetic; rerun with --arith double");
4800 bool is_srvn_ph()
const {
return lnmethod ==
"srvn.ph"; }
4808 bool is_ph_encoding()
const {
return lnmethod ==
"srvn.ph" || lnmethod ==
"flat.ph"; }
4811 bool is_flat_layering()
const {
return lnmethod ==
"flat.cs" || lnmethod ==
"flat.ph"; }
4819 static std::size_t station_idx_of(
const qn::Layer<T>& L, std::size_t elem) {
4820 if (elem >= 1 && elem < L.server_idx_of.size() && L.server_idx_of[elem] != 0)
4821 return L.server_idx_of[
elem];
4829 std::size_t station_idx_of_class(
const qn::Layer<T>& L, std::size_t k)
const {
4830 const int kind = L.classes[k].attr_kind;
4831 const std::size_t a = L.classes[k].attr_idx;
4832 if (kind ==
int(LqnElement::ACTIVITY))
4833 return station_idx_of(L, lqn.parent[lqn.parent[a]]);
4834 if (kind ==
int(LqnElement::CALL))
4835 return station_idx_of(L, lqn.parent[lqn.callpair_dst[a]]);
4847 bool probe_srvn_ph() {
4849 ph_assert_supported();
4851 ph_laws_ready =
true;
4853 }
catch (
const std::exception&) {
4854 ph_laws_ready =
false;
4861 std::size_t idx = 0;
4862 bool ishost =
false;
4863 std::vector<std::size_t> callers;
4865 std::vector<std::size_t> class_of_caller;
4866 std::size_t nreplicas = 1;
4868 std::vector<std::size_t> qstations;
4870 std::vector<T> svcmean_by_class;
4872 std::vector<std::pair<std::size_t, long>> open_arrivals;
4882 std::vector<workflow::Workflow<T>> ph_wf, ph_wfhost;
4883 std::vector<std::unordered_map<std::size_t, T>> ph_execs;
4884 std::vector<bool> ph_has_wf;
4885 std::vector<workflow::PhLaw<T>> ph_hostlaw, ph_entrylaw;
4886 std::vector<T> ph_hostmean, ph_entrymean, ph_entryscv;
4887 std::vector<T> ph_share, ph_overlap, ph_setupshare, ph_xdemand;
4888 Matrix<T> ph_ncalls, ph_calltime;
4889 std::vector<T> ph_procresid, ph_actthinkt, ph_calltotal;
4890 std::vector<T> ph_procsave;
4891 Matrix<T> ph_callsave;
4892 std::vector<PHLayer> ph_layers;
4893 std::vector<bool> ph_has_layer;
4896 T act_think_time(std::size_t aidx)
const {
4897 if (aidx >= lqn.actthink.size() || lqn.actthink[aidx].disabled)
return Tzero();
4898 const double m = dbl(lqn.actthink[aidx].mean);
4900 return lqn.actthink[aidx].mean;
4904 std::unordered_map<std::size_t, double> setupcharge;
4940 double setup_charge(std::size_t tidx,
double S = std::numeric_limits<double>::quiet_NaN()) {
4941 if (tidx >= lqn.hassetup.size() || !lqn.hassetup[tidx])
return 0.0;
4942 const double s = tidx < lqn.setuptime.size() && !lqn.setuptime[tidx].disabled
4943 ? dbl(lqn.setuptime[tidx].mean) : 0.0;
4944 const double d = tidx < lqn.delayofftime.size() && !lqn.delayofftime[tidx].disabled
4945 ? dbl(lqn.delayofftime[tidx].mean) : 0.0;
4947 const double mult = lqn.mult[tidx];
4948 if (!std::isfinite(mult) || mult <= 0.0)
return 0.0;
4949 if (std::isnan(S)) {
4950 const auto it = setupcharge.find(tidx);
4951 return it == setupcharge.end() ? s : it->second;
4953 const double c = setup_charge_exact(tidx,
static_cast<int>(std::llround(mult)), S, s, d);
4954 setupcharge[tidx] = c;
4959 double setup_charge_exact(std::size_t tidx,
int k,
double S,
double s,
double d)
const {
4962 || results.empty() || std::size_t(idxhash[tidx]) >= results.back().size())
4964 const std::size_t e = std::size_t(idxhash[tidx]);
4965 const qn::Layer<T>& L = ensemble[e];
4966 const LayerResult<T>& r = results.back()[e];
4967 if (r.TN.rows() == 0 || r.QN.rows() == 0)
return s;
4968 const std::size_t ts = station_idx_of(L, tidx) - 1;
4970 double X = 0.0, Q = 0.0;
4971 for (std::size_t c = 0; c < L.nclasses && c < r.TN.cols(); ++c) {
4972 const double tn = dbl(r.TN(ts, c)), qn = dbl(r.QN(ts, c));
4973 if (!std::isnan(tn)) X += tn;
4974 if (!std::isnan(qn)) Q += qn;
4981 bool isopen =
false;
4982 for (std::size_t ch = 0; ch < L.inchain.size(); ++ch) {
4983 bool visits =
false;
4985 for (std::size_t c1 : L.inchain[ch]) {
4986 if (c1 < 1 || c1 > L.nclasses || c1 > r.TN.cols())
continue;
4987 if (dbl(r.TN(ts, c1 - 1)) > 0.0) visits =
true;
4988 pop += L.classes[c1 - 1].population;
4990 if (!visits)
continue;
4991 if (std::isfinite(pop)) N += pop;
4994 const double mu = 1.0 / S;
4995 auto scv_of = [](
const std::vector<Distrib<T>>& procs, std::size_t i) {
4996 if (i >= procs.size() || procs[i].disabled)
return 1.0;
4997 const double v = dbl(procs[i].scv);
4998 return (std::isfinite(v) && v > 0.0) ? v : 1.0;
5000 const double ascv = scv_of(lqn.setuptime, tidx);
5001 const double bscv = scv_of(lqn.delayofftime, tidx);
5004 if (isopen || N <= 0.0) {
5005 if (X >= k * mu)
return 0.0;
5007 k, std::numeric_limits<double>::infinity(), X, mu, 1.0 / s, ascv, 1.0 / d, bscv);
5009 return mmk_equiv_service([&](
double Sp) {
return mmk_open_resp(k, X, 1.0 / Sp); }, S,
5010 res.QN / res.XN, k / X) - S;
5012 const int pop =
static_cast<int>(std::llround(N));
5014 const mam::SetupDelayoffMMk<double> res =
5017 return mmk_equiv_service([&](
double Sp) {
return mmk_closed_resp(k, pop, Z, 1.0 / Sp); }, S,
5018 res.QN / res.XN, res.QN / res.XN) - S;
5026 static double mmk_equiv_service(
const F& resp,
double S,
double Rs,
double hi) {
5028 if (resp(lo) >= Rs)
return S;
5029 for (
int it = 0; it < 200; ++it) {
5030 const double mid = 0.5 * (lo + hi);
5031 if (resp(mid) < Rs) lo = mid;
5033 if (hi - lo <= 1e-12 * hi)
break;
5035 return 0.5 * (lo + hi);
5042 static double mmk_closed_resp(
int k,
int N,
double Z,
double mu) {
5043 std::vector<double> p(std::size_t(N) + 1, 0.0);
5045 for (
int n = 1; n <= N; ++n) {
5046 p[n] = p[n - 1] * ((N - n + 1) / Z) / (std::min(n, k) * mu);
5048 for (
int i = 0; i <= n; ++i) p[i] /= p[n];
5050 double tot = 0.0, Qs = 0.0, Xs = 0.0;
5051 for (
int n = 0; n <= N; ++n) tot += p[n];
5052 for (
int n = 0; n <= N; ++n) {
5053 Qs += n * p[n] /
tot;
5054 Xs += (N - n) / Z * p[n] / tot;
5060 static double mmk_open_resp(
int k,
double lam,
double mu) {
5061 const double a = lam / mu, rho = a / k;
5062 double term = 1.0, acc = 1.0;
5063 for (
int i = 1; i < k; ++i) {
5067 const double tk = term * a / k;
5068 const double C = tk / (1.0 - rho) / (acc + tk / (1.0 - rho));
5069 return 1.0 / mu + C / (k * mu - lam);
5078 double ph_setup_prob(std::size_t eidx,
double m1) {
5079 const std::size_t tidx = lqn.parent[eidx];
5080 if (tidx >= lqn.hassetup.size() || !lqn.hassetup[tidx])
return 0.0;
5081 const double s = tidx < lqn.setuptime.size() && !lqn.setuptime[tidx].disabled
5082 ? dbl(lqn.setuptime[tidx].mean) : 0.0;
5083 const double d = tidx < lqn.delayofftime.size() && !lqn.delayofftime[tidx].disabled
5084 ? dbl(lqn.delayofftime[tidx].mean) : 0.0;
5086 return std::min(1.0, std::max(0.0, setup_charge(tidx, m1) / s));
5090 double ph_host_servers(std::size_t hidx)
const {
5091 if (lqn.sched[hidx] == SchedStrategy::INF)
return 1.0;
5092 const double m = lqn.maxmult[hidx];
5093 return (std::isfinite(m) && m > 0.0) ? m : 1.0;
5097 bool ph_any_caller_of(std::size_t eidx)
const {
5098 return lqn.issynccaller.any_col(eidx) || lqn.isasynccaller.any_col(eidx);
5106 bool ph_open_arrival_only(std::size_t tidx)
const {
5107 if (lqn.isref[tidx])
return false;
5108 for (std::size_t eidx : lqn.entriesof[tidx])
5109 if (ph_any_caller_of(eidx))
return false;
5110 for (std::size_t eidx : lqn.entriesof[tidx])
5111 if (lqn.has_arrival[eidx])
return true;
5116 std::vector<std::size_t> ph_async_calls_into(std::size_t tidx)
const {
5117 std::vector<std::size_t> out;
5118 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
5119 if (lqn.calltype[cidx] != CallType::ASYNC)
continue;
5120 for (std::size_t e : lqn.entriesof[tidx])
5121 if (lqn.callpair_dst[cidx] == e) { out.push_back(cidx);
break; }
5130 void ph_assert_supported(
bool flat =
false)
const {
5133 const std::string mname = flat ?
"flat.ph" :
"srvn.ph";
5139 "method='" + mname +
"' does not support second-phase activities: the composed "
5140 "entry law has no reply point. Use method='default'.");
5146 const std::string why = ph_method_refusal(mname);
5165 std::string ph_method_refusal(
const std::string& method)
const {
5166 if (method !=
"srvn.ph" && method !=
"flat.ph")
return std::string();
5169 if (method ==
"flat.ph") {
5170 for (std::size_t i = 1; i <= NT(); ++i) {
5171 if (lqn.repl[i] > 1.0)
5172 return "method='flat.ph' does not support replicated processors or tasks, "
5173 "whose replicas need a submodel each. Use method='srvn.ph'.";
5174 if (lqn.hassetup[i])
5175 return "method='flat.ph' does not support setup tasks, whose powered-down "
5176 "threads are per-layer state. Use method='srvn.ph'.";
5179 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx)
5180 if (lqn.calltype[cidx] == CallType::FWD)
5181 return "method='" + method +
5182 "' does not support forwarding calls, whose target is not part of the "
5183 "caller's activity graph. Use method='default'.";
5184 for (std::size_t i = 1; i < lqn.iscache.size(); ++i)
5186 return "method='" + method +
5187 "' does not support cache tasks. Use method='default'.";
5188 for (std::size_t i = 1; i < lqn.hassetup.size(); ++i) {
5189 if (!lqn.hassetup[i])
continue;
5190 if (lqn.sched[i] == SchedStrategy::INF || !std::isfinite(lqn.mult[i]))
5191 return "method='" + method +
"': task '" + lqn.names[i] +
5192 "' declares a setup time on an infinite-server task, which holds no "
5193 "thread to power down; give it a finite multiplicity.";
5195 for (std::size_t i = 0; i < lqn.lincon_A.size(); ++i)
5196 if (lqn.lincon_A[i].rows() > 0)
5197 return "method='" + method +
5198 "' does not support admission constraints on a layer station. "
5199 "Use method='default'.";
5200 for (std::size_t i = 1; i < lqn.lldscaling.size(); ++i) {
5201 const char* fname =
nullptr;
5202 if (!lqn.lldscaling[i].empty()) fname =
"a load dependence";
5203 else if (lqn.cdscaling[i]) fname =
"a class dependence";
5204 else if (lqn.jdscaling[i]) fname =
"a joint dependence";
5205 else if (!lqn.pools[i].empty()) fname =
"server pools";
5206 if (fname !=
nullptr)
5207 return "method='" + method +
5208 "' does not support queue-dependent service rates on a layer station ('" +
5209 lqn.names[i] +
"' declares " + fname +
"). Use method='srvn.cs'.";
5211 return std::string();
5215 void ph_init_laws() {
5216 const std::size_t N = lqn.nidx;
5217 ph_wf.assign(N + 1, workflow::Workflow<T>(
"empty"));
5218 ph_wfhost.assign(N + 1, workflow::Workflow<T>(
"empty"));
5219 ph_execs.assign(N + 1, {});
5220 ph_has_wf.assign(N + 1,
false);
5221 ph_hostlaw.assign(N + 1, workflow::PhLaw<T>());
5222 ph_entrylaw.assign(N + 1, workflow::PhLaw<T>());
5223 ph_hostmean.assign(N + 1, Tzero());
5224 ph_entrymean.assign(N + 1, Tzero());
5225 ph_entryscv.assign(N + 1, Tone());
5226 ph_share.assign(N + 1, Tzero());
5227 ph_overlap.assign(N + 1, Tone());
5228 ph_setupshare.assign(N + 1, Tzero());
5229 ph_xdemand.assign(N + 1, Tzero());
5230 ph_ncalls =
Matrix<T>(N + 1, N + 1, Tzero());
5231 ph_calltime =
Matrix<T>(N + 1, N + 1, Tzero());
5232 ph_procresid.assign(N + 1, Tzero());
5233 ph_actthinkt.assign(N + 1, Tzero());
5234 ph_calltotal.assign(N + 1, Tzero());
5235 ph_procsave.assign(N + 1, Tzero());
5236 ph_callsave =
Matrix<T>(N + 1, N + 1, Tzero());
5237 ph_layers.assign(NT() + 1, PHLayer());
5238 ph_has_layer.assign(NT() + 1,
false);
5240 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
5241 const std::size_t eidx = lqn.eshift + e;
5242 const std::size_t tidx = lqn.parent[eidx];
5243 if (ignore[tidx])
continue;
5245 ph_wf[eidx] = std::move(ew.wf);
5246 ph_execs[eidx] = ew.execs;
5247 ph_has_wf[eidx] =
true;
5249 ph_wfhost[eidx] = std::move(eh.wf);
5259 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
5260 const std::size_t tidx = lqn.tshift + t;
5261 const std::size_t n = lqn.entriesof[tidx].size();
5262 if (n == 0)
continue;
5263 for (std::size_t eidx : lqn.entriesof[tidx])
5264 ph_share[eidx] = num_traits<T>::from_double(1.0 /
double(n));
5275 Distrib<T> ph_call_burst_law(std::size_t cidx)
const {
5276 const double m = dbl(lqn.callproc_mean[cidx]);
5277 const std::size_t eidx = lqn.callpair_dst[cidx];
5279 const T R = T(callservt[cidx] / lqn.callproc_mean[cidx]);
5280 double scvd = dbl(ph_entryscv[eidx]);
5286 const workflow::PhLaw<T> loop =
5300 Distrib<T> ph_station_law(
const workflow::PhLaw<T>& law)
const {
5308 static workflow::PhLaw<T> ph_immediate_law() {
5309 workflow::PhLaw<T> out;
5310 out.alpha.assign(1, Tone());
5316 bool ph_has_and_fork(std::size_t eidx)
const {
5317 for (std::size_t aidx : lqn.actsof[eidx])
5318 if (aidx < lqn.actposttype.size() && lqn.actposttype[aidx] == PrecedenceType::POST_AND)
return true;
5327 bool ph_single_server_host(std::size_t eidx)
const {
5328 const std::size_t hidx = lqn.parent[lqn.parent[eidx]];
5329 if (lqn.sched[hidx] == SchedStrategy::INF)
return false;
5330 const double m = lqn.mult[hidx];
5331 return std::isfinite(m) && m <= 1.0;
5343 void ph_compose_entry_laws() {
5344 const std::size_t N = lqn.nidx;
5345 std::vector<T> entry_setup_share(N + 1, Tzero());
5346 ph_overlap.assign(N + 1, Tone());
5347 std::vector<bool> hostserial(N + 1,
false);
5349 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
5350 const std::size_t eidx = lqn.eshift + e;
5351 if (!ph_has_wf[eidx])
continue;
5352 workflow::Workflow<T>& w = ph_wf[eidx];
5353 const std::unordered_map<std::size_t, T>& ex = ph_execs[eidx];
5354 T entrysum = Tzero(), procsum = Tzero(), pres = Tzero();
5355 for (std::size_t aidx : lqn.actsof[eidx]) {
5356 T m = T(residt[aidx] + act_think_time(aidx));
5357 const T xa = ex.at(aidx);
5358 procsum += T(xa * m);
5359 pres += T(xa * residt[aidx]);
5360 const double md = dbl(m);
5361 w.set_activity_demand_mean(
5365 for (std::size_t cidx : lqn.callsof[aidx]) {
5366 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
5367 w.set_activity_demand(lqn.callhashnames[cidx], ph_call_burst_law(cidx));
5368 m = T(m + callservt[cidx]);
5370 entrysum += T(xa * m);
5374 T m1 = mm.first, scv = mm.second;
5381 const T f = T(m1 / procsum);
5382 for (std::size_t i = 0; i < law.S.rows(); ++i)
5383 for (std::size_t j = 0; j < law.S.cols(); ++j) law.S(i, j) = T(law.S(i, j) * f);
5390 if (ph_has_and_fork(eidx) && ph_single_server_host(eidx)) {
5391 hostserial[eidx] =
true;
5393 const T add = T((Tone() - num_traits<T>::from_double(
5394 std::min(1.0, dbl(m1) / dbl(entrysum)))) * pres);
5396 const T f = T(m1 / (m1 + add));
5397 for (std::size_t i = 0; i < law.S.rows(); ++i)
5398 for (std::size_t j = 0; j < law.S.cols(); ++j) law.S(i, j) = T(law.S(i, j) * f);
5409 const double p = ph_setup_prob(eidx, dbl(m1));
5411 const std::size_t tidx = lqn.parent[eidx];
5412 const double sm = dbl(lqn.setuptime[tidx].mean);
5413 double sscv = dbl(lqn.setuptime[tidx].scv);
5417 num_traits<T>::from_double(sm), num_traits<T>::from_double(sscv)));
5418 std::vector<workflow::PhLaw<T>> mix;
5421 std::vector<T> probs;
5422 probs.push_back(num_traits<T>::from_double(p));
5423 probs.push_back(num_traits<T>::from_double(1.0 - p));
5433 entry_setup_share[eidx] = num_traits<T>::from_double(p * sm / denom);
5436 ph_entrylaw[eidx] = law;
5437 ph_entrymean[eidx] = m1;
5438 ph_entryscv[eidx] = scv;
5439 if (hostserial[eidx]) {
5441 const double rest = dbl(entrysum) - dbl(pres);
5443 const double r = std::min(1.0, std::max(0.0, (dbl(m1) - dbl(pres)) / rest));
5444 ph_overlap[eidx] = num_traits<T>::from_double(r);
5447 const double r = std::min(1.0, dbl(m1) / dbl(entrysum));
5448 ph_overlap[eidx] = num_traits<T>::from_double(r);
5454 ph_setupshare.assign(N + 1, Tzero());
5455 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
5456 const std::size_t tidx = lqn.tshift + t;
5457 if (ignore[tidx])
continue;
5458 for (std::size_t eidx : lqn.entriesof[tidx])
5459 ph_setupshare[tidx] = T(ph_setupshare[tidx] + ph_share[eidx] * entry_setup_share[eidx]);
5463 ph_ncalls =
Matrix<T>(N + 1, N + 1, Tzero());
5464 ph_calltime =
Matrix<T>(N + 1, N + 1, Tzero());
5465 ph_procresid.assign(N + 1, Tzero());
5466 ph_actthinkt.assign(N + 1, Tzero());
5467 ph_calltotal.assign(N + 1, Tzero());
5473 ph_procsave.assign(N + 1, Tzero());
5474 ph_callsave =
Matrix<T>(N + 1, N + 1, Tzero());
5475 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
5476 const std::size_t tidx = lqn.tshift + t;
5477 if (ignore[tidx])
continue;
5478 for (std::size_t eidx : lqn.entriesof[tidx]) {
5479 if (!ph_has_wf[eidx])
continue;
5480 const T w = ph_share[eidx];
5481 if (!(dbl(w) > 0.0))
continue;
5482 const std::unordered_map<std::size_t, T>& ex = ph_execs[eidx];
5483 const T r = ph_overlap[eidx];
5484 const T sv = ph_has_and_fork(eidx) ? T(Tone() - r) : Tzero();
5486 const T rp = hostserial[eidx] ? Tone() : r;
5487 const T svp = hostserial[eidx] ? Tzero() : sv;
5488 for (std::size_t aidx : lqn.actsof[eidx]) {
5489 const T xa = ex.at(aidx);
5490 ph_procresid[tidx] = T(ph_procresid[tidx] + w * rp * xa * residt[aidx]);
5491 ph_procsave[tidx] = T(ph_procsave[tidx] + w * svp * xa * residt[aidx]);
5492 ph_actthinkt[tidx] = T(ph_actthinkt[tidx] + w * r * xa * act_think_time(aidx));
5493 for (std::size_t cidx : lqn.callsof[aidx]) {
5494 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
5495 const std::size_t tgte = lqn.callpair_dst[cidx];
5496 const std::size_t tgtt = lqn.parent[tgte];
5499 ph_ncalls(tidx, tgte) =
5500 T(ph_ncalls(tidx, tgte) + w * xa * lqn.callproc_mean[cidx]);
5501 ph_calltime(tidx, tgtt) =
5502 T(ph_calltime(tidx, tgtt) + w * r * xa * callservt[cidx]);
5503 ph_calltotal[tidx] = T(ph_calltotal[tidx] + w * r * xa * callservt[cidx]);
5504 ph_callsave(tidx, tgtt) =
5505 T(ph_callsave(tidx, tgtt) + w * sv * xa * callservt[cidx]);
5513 void build_layers_ph(
bool flat =
false) {
5514 if (!ph_laws_ready) ph_assert_supported(flat);
5518 opt.interlocking =
false;
5519 interlockMethod =
"none";
5523 if (!ph_laws_ready) ph_init_laws();
5526 const std::size_t N = lqn.nidx;
5527 residt.assign(N + 1, Tzero());
5528 servt.assign(N + 1, Tzero());
5529 callservt.assign(lqn.ncalls + 1, Tzero());
5530 callresidt.assign(lqn.ncalls + 1, Tzero());
5531 for (std::size_t aidx = lqn.ashift + 1; aidx <= lqn.ashift + lqn.nacts; ++aidx)
5532 residt[aidx] = lqn.hostdem[aidx].disabled ? Tzero() : lqn.hostdem[aidx].mean;
5533 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
5534 if (lqn.calltype[cidx] != CallType::SYNC && lqn.calltype[cidx] != CallType::ASYNC)
5536 const std::size_t eidx = lqn.callpair_dst[cidx];
5537 callservt[cidx] = T(lqn.callproc_mean[cidx] * ph_hostmean[eidx]);
5538 callresidt[cidx] = callservt[cidx];
5540 ph_compose_entry_laws();
5544 build_ph_flat_layer();
5545 tput.assign(N + 1, Tzero());
5546 util.assign(N + 1, Tzero());
5547 thinkt.assign(N + 1, Tzero());
5548 update_layers_ph(0);
5549 for (qn::Layer<T>& e : ensemble) e.refresh_chains();
5553 std::vector<qn::Layer<T>> raw(NT() + 1);
5554 std::vector<bool> present(NT() + 1,
false);
5556 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx) {
5557 if (ignore[hidx])
continue;
5558 std::vector<std::size_t> callers;
5559 for (std::size_t tidx : lqn.tasksof[hidx]) {
5560 if (ignore[tidx])
continue;
5561 if (lqn.isref[tidx]) { callers.push_back(tidx);
continue; }
5562 for (std::size_t eidx : lqn.entriesof[tidx])
5563 if (ph_any_caller_of(eidx) || lqn.has_arrival[eidx]) {
5564 callers.push_back(tidx);
5568 if (callers.empty())
continue;
5569 build_layer_ph(raw[hidx], hidx, callers,
true);
5570 present[hidx] =
true;
5572 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
5573 const std::size_t tidx = lqn.tshift + t;
5574 if (ignore[tidx] || lqn.isref[tidx])
continue;
5575 std::vector<std::size_t> callers;
5576 for (std::size_t ct = 1; ct <= lqn.ntasks; ++ct) {
5577 const std::size_t c = lqn.tshift + ct;
5578 if (c == tidx || ignore[c])
continue;
5579 for (std::size_t e : lqn.entriesof[tidx])
5580 if (lqn.issynccaller.get(c, e)) { callers.push_back(c);
break; }
5582 if (callers.empty() && ph_async_calls_into(tidx).empty())
continue;
5583 build_layer_ph(raw[tidx], tidx, callers,
false);
5584 present[tidx] =
true;
5587 idxhash.assign(lqn.nidx + 1, -1);
5589 for (std::size_t i = 1; i <= NT(); ++i)
5591 idxhash[i] = next++;
5592 ensemble.push_back(std::move(raw[i]));
5594 layer_init_sol.assign(ensemble.size(), Matrix<T>());
5599 tput.assign(N + 1, Tzero());
5600 util.assign(N + 1, Tzero());
5601 thinkt.assign(N + 1, Tzero());
5602 update_layers_ph(0);
5606 for (qn::Layer<T>& e : ensemble) e.refresh_chains();
5622 void build_ph_flat_layer() {
5623 const std::vector<std::size_t> servers = ph_flat_server_set();
5624 const std::size_t nsrv = servers.size();
5628 m.clientIdx = m.add_station(qn::Station<T>{
5629 "Clients", NodeType::Delay, SchedStrategy::INF,
5630 std::numeric_limits<double>::infinity(),
false, 0});
5631 const std::size_t clientNode = m.node_of_station(m.clientIdx);
5632 m.server_idx_of.assign(lqn.nidx + 1, 0);
5634 std::vector<std::size_t> station_of(lqn.nidx + 1, 0);
5635 std::vector<std::size_t> serverNode(nsrv, 0);
5636 std::vector<std::size_t> srvStation(nsrv, 0);
5637 for (std::size_t si = 0; si < nsrv; ++si) {
5638 const std::size_t idx = servers[si];
5639 const bool ishost = idx <= lqn.nhosts;
5641 st.name = lqn.hashnames[idx];
5642 st.nodetype = lqn.sched[idx] == SchedStrategy::INF ? NodeType::Delay : NodeType::Queue;
5643 st.sched = lqn.sched[idx];
5644 st.nservers = lqn.sched[idx] == SchedStrategy::INF
5645 ? std::numeric_limits<double>::infinity()
5647 st.attr_ishost = ishost;
5649 const std::size_t s = m.add_station(st);
5651 station_of[idx] = s;
5652 serverNode[si] = m.node_of_station(s);
5653 m.server_idx_of[idx] = s;
5655 m.host_stations.push_back(s);
5657 m.task_stations.push_back(s);
5660 m.serverIdx = station_of[servers[0]];
5664 std::vector<std::vector<std::size_t>> callers_of(lqn.nidx + 1);
5665 std::vector<std::size_t> all_callers;
5666 for (std::size_t si = 0; si < nsrv; ++si) {
5667 const std::size_t idx = servers[si];
5668 std::vector<std::size_t> cs;
5669 if (idx <= lqn.nhosts) {
5670 for (std::size_t tidx : lqn.tasksof[idx]) {
5671 if (ignore[tidx])
continue;
5672 if (lqn.isref[tidx]) { cs.push_back(tidx);
continue; }
5673 for (std::size_t eidx : lqn.entriesof[tidx])
5674 if (ph_any_caller_of(eidx) || lqn.has_arrival[eidx]) {
5680 for (std::size_t ct = 1; ct <= lqn.ntasks; ++ct) {
5681 const std::size_t c = lqn.tshift + ct;
5682 if (c == idx || ignore[c])
continue;
5683 for (std::size_t e : lqn.entriesof[idx])
5684 if (lqn.issynccaller.get(c, e)) { cs.push_back(c);
break; }
5687 callers_of[idx] = cs;
5688 for (std::size_t c : cs)
5689 if (std::find(all_callers.begin(), all_callers.end(), c) == all_callers.end())
5690 all_callers.push_back(c);
5692 std::sort(all_callers.begin(), all_callers.end());
5695 std::vector<std::size_t> class_of_caller(lqn.nidx + 1, 0);
5697 for (std::size_t c : all_callers) {
5700 double nj = lqn.maxmult[c];
5701 if (std::isinf(nj)) {
5703 for (std::size_t k = 1; k <= NT(); ++k)
5704 if (lqn.taskgraph.get(k, c) != Tzero()) sacc += lqn.maxmult[k];
5706 if (std::isinf(nj) || nj == 0.0) {
5708 for (std::size_t k = 1; k <= NT(); ++k)
5709 if (std::isfinite(lqn.maxmult[k])) s2 += lqn.maxmult[k];
5710 nj = std::min(s2, 1000.0);
5714 jc.name = lqn.hashnames[c];
5715 jc.type = JobClassType::CLOSED;
5717 jc.refstat = m.clientIdx;
5718 jc.is_ref_class =
true;
5719 jc.attr_kind = int(LqnElement::TASK);
5721 const std::size_t k = m.add_class(jc);
5722 class_of_caller[c] = k;
5723 m.attr_tasks.emplace_back(k, c);
5725 const double zt = dbl(ref_think_time(c));
5726 m.set_service(m.clientIdx, k,
5735 for (std::size_t si = 0; si < nsrv; ++si)
5737 for (std::size_t si = 0; si < nsrv; ++si) {
5738 const std::size_t idx = servers[si];
5739 const std::vector<std::size_t>& cs = callers_of[idx];
5740 if (std::find(cs.begin(), cs.end(), c) == cs.end())
continue;
5741 m.set_service(srvStation[si], k,
5745 thinkt_map.push_back({idx, c, m.clientIdx, k});
5746 servt_map.push_back({idx, c, station_of[idx], k});
5751 std::vector<std::vector<std::pair<std::size_t, long>>> open_of(lqn.nidx + 1);
5752 std::size_t sourceStation = 0, sinkNode = 0;
5753 for (std::size_t si = 0; si < nsrv; ++si) {
5754 const std::size_t hidx = servers[si];
5755 if (hidx > lqn.nhosts)
continue;
5756 for (std::size_t c : callers_of[hidx]) {
5762 if (ph_open_arrival_only(c))
continue;
5763 for (std::size_t eidx : lqn.entriesof[c]) {
5764 if (!lqn.has_arrival[eidx])
continue;
5765 if (sourceStation == 0) {
5766 sourceStation = m.add_station(qn::Station<T>{
5767 "Source", NodeType::Source, SchedStrategy::EXT,
5768 std::numeric_limits<double>::infinity(),
false, 0});
5769 m.sourceIdx = sourceStation;
5770 sinkNode = m.add_node(
"Sink", NodeType::Sink,
false);
5771 m.sinkNode = sinkNode;
5774 oc.name = lqn.hashnames[eidx] +
".Open";
5775 oc.type = JobClassType::OPEN;
5776 oc.population = std::numeric_limits<double>::infinity();
5777 oc.refstat = sourceStation;
5778 oc.attr_kind = int(LqnElement::ENTRY);
5780 const std::size_t k = m.add_class(oc);
5781 m.set_service(sourceStation, k, lqn.arrival[eidx]);
5783 for (std::size_t s2 = 0; s2 < nsrv; ++s2)
5786 m.set_service(srvStation[si], k,
5788 open_of[hidx].emplace_back(k,
long(eidx));
5789 m.attr_entries.emplace_back(k, eidx);
5793 for (std::size_t si = 0; si < nsrv; ++si) {
5794 const std::size_t tidx = servers[si];
5795 if (tidx <= lqn.nhosts)
continue;
5796 for (std::size_t cidx : ph_async_calls_into(tidx)) {
5797 if (sourceStation == 0) {
5798 sourceStation = m.add_station(qn::Station<T>{
5799 "Source", NodeType::Source, SchedStrategy::EXT,
5800 std::numeric_limits<double>::infinity(),
false, 0});
5801 m.sourceIdx = sourceStation;
5802 sinkNode = m.add_node(
"Sink", NodeType::Sink,
false);
5803 m.sinkNode = sinkNode;
5805 const std::size_t eidx = lqn.callpair_dst[cidx];
5807 oc.name = lqn.callhashnames[cidx];
5808 oc.type = JobClassType::OPEN;
5809 oc.population = std::numeric_limits<double>::infinity();
5810 oc.refstat = sourceStation;
5811 oc.attr_kind = int(LqnElement::CALL);
5813 const std::size_t k = m.add_class(oc);
5816 for (std::size_t s2 = 0; s2 < nsrv; ++s2)
5819 m.set_service(srvStation[si], k,
5821 open_of[tidx].emplace_back(k, -
long(cidx));
5822 m.attr_calls.push_back({k, cidx, lqn.callpair_src[cidx], eidx});
5823 arv_call_map.push_back({tidx, cidx, sourceStation, k});
5824 call_map.push_back({tidx, cidx, station_of[tidx], k});
5831 for (std::size_t c : all_callers) {
5832 const std::size_t k = class_of_caller[c];
5833 std::size_t prev = clientNode;
5834 bool visited =
false;
5835 for (std::size_t si = 0; si < nsrv; ++si) {
5836 const std::vector<std::size_t>& cs = callers_of[servers[si]];
5837 if (std::find(cs.begin(), cs.end(), c) == cs.end())
continue;
5838 m.set_route(k, k, prev, serverNode[si], Tone());
5839 prev = serverNode[si];
5842 if (visited) m.set_route(k, k, prev, clientNode, Tone());
5844 if (sourceStation != 0) {
5845 const std::size_t srcNode = m.node_of_station(sourceStation);
5846 for (std::size_t si = 0; si < nsrv; ++si)
5847 for (
const auto& oa : open_of[servers[si]]) {
5848 m.set_route(oa.first, oa.first, srcNode, serverNode[si], Tone());
5849 m.set_route(oa.first, oa.first, serverNode[si], sinkNode, Tone());
5852 apply_host_task_priorities(m, servers);
5855 idxhash.assign(lqn.nidx + 1, -1);
5856 for (std::size_t idx : servers) idxhash[idx] = 0;
5858 ensemble.push_back(std::move(m));
5859 layer_init_sol.assign(ensemble.size(), Matrix<T>());
5862 const std::size_t nclasses = ensemble[0].classes.size();
5863 for (std::size_t idx : servers) {
5866 L.ishost = idx <= lqn.nhosts;
5867 L.callers = callers_of[idx];
5868 L.class_of_caller = class_of_caller;
5870 L.qstations.assign(1, station_of[idx]);
5871 L.svcmean_by_class.assign(nclasses + 1, Tzero());
5872 L.open_arrivals = open_of[idx];
5873 L.npop = npop < 1.0 ? 1.0 : npop;
5875 ph_has_layer[idx] =
true;
5888 std::vector<std::size_t> ph_flat_server_set()
const {
5889 for (std::size_t i = 1; i <= NT(); ++i) {
5890 if (lqn.repl[i] > 1.0)
5892 "method='flat.ph' does not support replicated processors or tasks, whose "
5893 "replicas need a submodel each. Use method='srvn.ph'.");
5896 "method='flat.ph' does not support cache tasks. Use method='default'.");
5897 if (lqn.hassetup[i])
5899 "method='flat.ph' does not support setup tasks, whose powered-down threads "
5900 "are per-layer state. Use method='srvn.ph'.");
5902 std::vector<std::size_t> servers;
5903 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx) {
5904 if (ignore[hidx] || lqn.tasksof[hidx].empty())
continue;
5906 for (std::size_t tidx : lqn.tasksof[hidx]) {
5907 if (ignore[tidx])
continue;
5908 if (lqn.isref[tidx]) { any =
true;
break; }
5909 for (std::size_t eidx : lqn.entriesof[tidx])
5910 if (ph_any_caller_of(eidx) || lqn.has_arrival[eidx]) { any =
true;
break; }
5913 if (any) servers.push_back(hidx);
5915 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
5916 const std::size_t tidx = lqn.tshift + t;
5917 if (ignore[tidx] || lqn.isref[tidx])
continue;
5918 bool has_caller =
false;
5919 for (std::size_t ct = 1; ct <= lqn.ntasks && !has_caller; ++ct) {
5920 const std::size_t c = lqn.tshift + ct;
5921 if (c == tidx || ignore[c])
continue;
5922 for (std::size_t e : lqn.entriesof[tidx])
5923 if (lqn.issynccaller.get(c, e)) { has_caller =
true;
break; }
5925 if (!has_caller && ph_async_calls_into(tidx).empty())
continue;
5926 servers.push_back(tidx);
5928 if (servers.empty())
5930 "method='flat.ph' found no server: the model has no processor with tasks.");
5935 void build_layer_ph(qn::Layer<T>& m, std::size_t idx,
5936 const std::vector<std::size_t>& callers,
bool ishost) {
5937 m.name = lqn.hashnames[idx];
5942 const double rawrepl = lqn.repl[idx];
5943 std::size_t nreplicas = 1;
5944 if (rawrepl > 1.0 && !callers.empty()) {
5947 for (std::size_t c : callers)
5948 if (lqn.repl[c] != rawrepl) reduce =
false;
5950 for (std::size_t c : callers)
5951 if (lqn.fanout_at(c, idx) < rawrepl) reduce =
false;
5953 nreplicas = reduce ? 1 :
static_cast<std::size_t
>(std::llround(rawrepl));
5954 if (reduce && !ishost) single_replica_tasks.insert(idx);
5956 const bool reduce_fanout = (nreplicas == 1 && rawrepl > 1.0 && !callers.empty());
5958 m.clientIdx = m.add_station(qn::Station<T>{
5959 "Clients", NodeType::Delay, SchedStrategy::INF,
5960 std::numeric_limits<double>::infinity(),
false, 0});
5961 const std::size_t clientNode = m.node_of_station(m.clientIdx);
5962 m.serverIdx = m.clientIdx + 1;
5966 L.callers = callers;
5967 L.nreplicas = nreplicas;
5968 L.class_of_caller.assign(lqn.nidx + 1, 0);
5969 std::vector<std::size_t> serverNode(nreplicas);
5970 for (std::size_t r = 0; r < nreplicas; ++r) {
5972 st.name = r == 0 ? lqn.hashnames[idx] : lqn.hashnames[idx] +
"." + std::to_string(r + 1);
5973 st.nodetype = lqn.sched[idx] == SchedStrategy::INF ? NodeType::Delay : NodeType::Queue;
5974 st.sched = lqn.sched[idx];
5975 st.nservers = lqn.sched[idx] == SchedStrategy::INF
5976 ? std::numeric_limits<double>::infinity()
5978 st.attr_ishost = ishost;
5980 const std::size_t s = m.add_station(st);
5981 L.qstations.push_back(s);
5982 serverNode[r] = m.node_of_station(s);
5986 for (std::size_t c : callers) {
5987 double nj = njobs(c, idx);
5989 const bool caller_single_replica =
5990 reduce_fanout || single_replica_tasks.count(c) > 0;
5991 nj = caller_single_replica ? lqn.maxmult[c] : lqn.maxmult[c] * lqn.repl[c];
5992 if (std::isinf(nj)) {
5994 for (std::size_t k = 1; k <= NT(); ++k)
5995 if (lqn.taskgraph.get(k, c) != Tzero()) s += lqn.maxmult[k];
5997 if (std::isinf(nj) || nj == 0.0) {
5999 for (std::size_t k = 1; k <= NT(); ++k)
6000 if (std::isfinite(lqn.maxmult[k])) s2 += lqn.maxmult[k] * lqn.repl[k];
6001 nj = std::min(s2, 1000.0);
6007 jc.name = lqn.hashnames[c];
6008 jc.type = JobClassType::CLOSED;
6010 jc.refstat = m.clientIdx;
6011 jc.is_ref_class =
true;
6012 jc.attr_kind = int(LqnElement::TASK);
6014 const std::size_t k = m.add_class(jc);
6015 L.class_of_caller[c] = k;
6016 m.attr_tasks.emplace_back(k, c);
6017 const double zt = dbl(ref_think_time(c));
6018 m.set_service(m.clientIdx, k,
6021 for (std::size_t s : L.qstations)
6026 thinkt_map.push_back({idx, c, m.clientIdx, k});
6027 servt_map.push_back({idx, c, L.qstations[0], k});
6031 std::size_t sourceStation = 0, sinkNode = 0;
6033 for (std::size_t c : callers) {
6040 if (ph_open_arrival_only(c))
continue;
6041 for (std::size_t eidx : lqn.entriesof[c]) {
6042 if (!lqn.has_arrival[eidx])
continue;
6043 if (sourceStation == 0) {
6044 sourceStation = m.add_station(qn::Station<T>{
6045 "Source", NodeType::Source, SchedStrategy::EXT,
6046 std::numeric_limits<double>::infinity(),
false, 0});
6047 m.sourceIdx = sourceStation;
6048 sinkNode = m.add_node(
"Sink", NodeType::Sink,
false);
6049 m.sinkNode = sinkNode;
6052 oc.name = lqn.hashnames[eidx] +
".Open";
6053 oc.type = JobClassType::OPEN;
6054 oc.population = std::numeric_limits<double>::infinity();
6055 oc.refstat = sourceStation;
6056 oc.attr_kind = int(LqnElement::ENTRY);
6058 const std::size_t k = m.add_class(oc);
6059 m.set_service(sourceStation, k, lqn.arrival[eidx]);
6060 for (std::size_t s : L.qstations) {
6064 L.open_arrivals.emplace_back(k,
long(eidx));
6065 m.attr_entries.emplace_back(k, eidx);
6069 for (std::size_t cidx : ph_async_calls_into(idx)) {
6070 if (sourceStation == 0) {
6071 sourceStation = m.add_station(qn::Station<T>{
6072 "Source", NodeType::Source, SchedStrategy::EXT,
6073 std::numeric_limits<double>::infinity(),
false, 0});
6074 m.sourceIdx = sourceStation;
6075 sinkNode = m.add_node(
"Sink", NodeType::Sink,
false);
6076 m.sinkNode = sinkNode;
6078 const std::size_t eidx = lqn.callpair_dst[cidx];
6080 oc.name = lqn.callhashnames[cidx];
6081 oc.type = JobClassType::OPEN;
6082 oc.population = std::numeric_limits<double>::infinity();
6083 oc.refstat = sourceStation;
6084 oc.attr_kind = int(LqnElement::CALL);
6086 const std::size_t k = m.add_class(oc);
6088 for (std::size_t s : L.qstations) {
6092 L.open_arrivals.emplace_back(k, -
long(cidx));
6093 m.attr_calls.push_back({k, cidx, lqn.callpair_src[cidx], eidx});
6094 arv_call_map.push_back({idx, cidx, sourceStation, k});
6095 call_map.push_back({idx, cidx, L.qstations[0], k});
6101 const T share = num_traits<T>::from_double(1.0 /
double(nreplicas));
6102 for (std::size_t c : callers) {
6103 const std::size_t k = L.class_of_caller[c];
6104 for (std::size_t r = 0; r < nreplicas; ++r) {
6105 m.set_route(k, k, clientNode, serverNode[r], share);
6106 m.set_route(k, k, serverNode[r], clientNode, Tone());
6109 for (
const auto& oa : L.open_arrivals) {
6110 const std::size_t k = oa.first;
6111 const std::size_t srcNode = m.node_of_station(sourceStation);
6112 for (std::size_t r = 0; r < nreplicas; ++r) {
6113 m.set_route(k, k, srcNode, serverNode[r], share);
6114 m.set_route(k, k, serverNode[r], sinkNode, Tone());
6117 L.svcmean_by_class.assign(m.classes.size() + 1, Tzero());
6119 for (std::size_t c : callers) {
6120 const double v = njobs(c, idx);
6121 if (std::isfinite(v) && v > 0.0) np += v;
6123 L.npop = np < 1.0 ? 1.0 : np;
6124 apply_host_task_priorities(m, std::vector<std::size_t>{idx});
6127 ph_has_layer[idx] =
true;
6131 workflow::PhLaw<T> ph_service_law(std::size_t idx,
bool ishost, std::size_t c)
const {
6134 std::vector<workflow::PhLaw<T>> laws;
6135 std::vector<T> probs;
6137 for (std::size_t eidx : lqn.entriesof[c]) {
6138 if (ph_hostlaw[eidx].S.rows() == 0 || !(dbl(ph_share[eidx]) > 0.0))
continue;
6139 laws.push_back(ph_hostlaw[eidx]);
6140 probs.push_back(ph_share[eidx]);
6141 tot += dbl(ph_share[eidx]);
6143 if (laws.empty())
return ph_immediate_law();
6144 for (T& p : probs) p = T(p / num_traits<T>::from_double(tot));
6150 bool started =
false;
6151 workflow::PhLaw<T> out;
6152 for (std::size_t eidx : lqn.entriesof[idx]) {
6153 const T n = ph_ncalls(c, eidx);
6155 const workflow::PhLaw<T> lp =
6160 if (!started)
return ph_immediate_law();
6168 T ph_delay_mean(std::size_t idx, std::size_t c)
const {
6178 T z = T(thinkt[c] + ref_think_time(c));
6179 if (!std::isfinite(dbl(z)) || dbl(z) < 0.0) z = Tzero();
6180 z = T(z + ph_actthinkt[c]);
6181 const std::size_t hidx = lqn.parent[c];
6186 if (!ph_served_here(idx, hidx)) z = T(z + ph_procresid[c]);
6187 else z = T(z - ph_procsave[c]);
6188 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
6189 const std::size_t tidx = lqn.tshift + t;
6190 if (!ph_served_here(idx, tidx)) z = T(z + ph_calltime(c, tidx));
6191 else z = T(z - ph_callsave(c, tidx));
6193 if (!std::isfinite(dbl(z)) || dbl(z) < 0.0)
6203 bool ph_served_here(std::size_t idx, std::size_t elem)
const {
6204 if (elem < 1 || elem >= idxhash.size() || idx < 1 || idx >= idxhash.size())
6206 return idxhash[
elem] >= 0 && idxhash[
elem] == idxhash[idx];
6218 void update_layers_ph(
int) {
6219 for (std::size_t idx = 1; idx <= NT(); ++idx) {
6220 if (idxhash[idx] < 0 || !ph_has_layer[idx])
continue;
6221 PHLayer& L = ph_layers[idx];
6222 qn::Layer<T>& m = ensemble[std::size_t(idxhash[idx])];
6223 for (std::size_t c : L.callers) {
6224 const std::size_t k = L.class_of_caller[c];
6225 const workflow::PhLaw<T> sl = ph_service_law(idx, L.ishost, c);
6227 const Distrib<T> law = ph_station_law(sl);
6228 for (std::size_t s : L.qstations) m.set_service(s, k, law);
6229 const double zd = std::max(dbl(ph_delay_mean(idx, c)),
6231 m.set_service(m.clientIdx, k,
6234 for (
const auto& oa : L.open_arrivals) {
6235 const std::size_t k = oa.first;
6236 if (oa.second > 0) {
6238 L.svcmean_by_class[k] = ph_hostmean[std::size_t(oa.second)];
6241 const std::size_t cidx = std::size_t(-oa.second);
6242 const std::size_t eidx = lqn.callpair_dst[cidx];
6243 L.svcmean_by_class[k] = ph_entrymean[eidx];
6244 const Distrib<T> law = ph_station_law(ph_entrylaw[eidx]);
6245 for (std::size_t s : L.qstations) m.set_service(s, k, law);
6246 const std::size_t aidx = lqn.callpair_src[cidx];
6247 double rate = dbl(tput[aidx]) * dbl(lqn.callproc_mean[cidx]);
6250 m.set_service(m.sourceIdx, k,
6263 static T ph_residence(
const T& Q,
const T& X,
const T& RN) {
6264 const double q = num_traits<T>::to_double(Q), x = num_traits<T>::to_double(X);
6275 static T ph_inflation_of(
const T& R,
const T& S,
double npop) {
6277 const double s = num_traits<T>::to_double(S), r = num_traits<T>::to_double(R);
6279 if (!std::isfinite(f) || f < 1.0) f = 1.0;
6280 if (std::isfinite(npop) && npop >= 1.0 && f > npop) f = npop;
6281 return num_traits<T>::from_double(f);
6285 double ph_layer_pop(
const PHLayer& L, std::size_t idx)
const {
6288 if (L.npop >= 1.0)
return L.npop;
6290 for (std::size_t c : L.callers) {
6291 const double v = njobs(c, idx);
6292 if (std::isfinite(v) && v > 0.0) n += v;
6294 return n < 1.0 ? 1.0 : n;
6310 void update_metrics_ph(
int it) {
6311 const std::size_t N = lqn.nidx;
6312 servt.assign(N + 1, Tzero());
6313 residt.assign(N + 1, Tzero());
6314 callservt.assign(lqn.ncalls + 1, Tzero());
6315 callresidt.assign(lqn.ncalls + 1, Tzero());
6317 std::vector<T> inflNum(N + 1, Tzero()), inflDen(N + 1, Tzero());
6318 std::vector<T> taskTput(N + 1, Tzero()), openTput(N + 1, Tzero());
6321 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx) {
6322 if (idxhash[hidx] < 0 || !ph_has_layer[hidx])
continue;
6323 const PHLayer& L = ph_layers[hidx];
6324 const LayerResult<T>& res = results.back()[std::size_t(idxhash[hidx])];
6325 const double npop = ph_layer_pop(L, hidx);
6326 for (std::size_t c : L.callers) {
6327 const std::size_t k = L.class_of_caller[c];
6328 T X = Tzero(), Q = Tzero();
6329 for (std::size_t s : L.qstations) {
6330 X = T(X + res.TN(s - 1, k - 1));
6331 Q = T(Q + res.QN(s - 1, k - 1));
6333 const T R = ph_residence(Q, X, res.RN(L.qstations[0] - 1, k - 1));
6334 const T f = ph_inflation_of(R, L.svcmean_by_class[k], npop);
6335 if (!std::isfinite(dbl(X)) || dbl(X) < 0.0) X = Tzero();
6343 double rc = lqn.repl[c];
6344 if (rc > 1.0 && njobs(c, hidx) >= lqn.maxmult[c] * rc) rc = 1.0;
6345 taskTput[c] = T(taskTput[c] + num_traits<T>::from_double(rc) * X);
6346 for (std::size_t eidx : lqn.entriesof[c]) {
6347 const T sh = dbl(ph_share[eidx]) > 0.0 ? ph_share[eidx] : Tzero();
6348 const T w = T(sh * X);
6349 inflNum[eidx] = T(inflNum[eidx] + w * f);
6350 inflDen[eidx] = T(inflDen[eidx] + w);
6353 for (
const auto& oa : L.open_arrivals) {
6354 if (oa.second <= 0)
continue;
6355 const std::size_t k = oa.first;
6356 const std::size_t eidx = std::size_t(oa.second);
6357 T X = Tzero(), Q = Tzero();
6358 for (std::size_t s : L.qstations) {
6359 X = T(X + res.TN(s - 1, k - 1));
6360 Q = T(Q + res.QN(s - 1, k - 1));
6362 if (!std::isfinite(dbl(X)) || dbl(X) <= 0.0)
continue;
6363 const T f = ph_inflation_of(ph_residence(Q, X, res.RN(L.qstations[0] - 1, k - 1)),
6364 L.svcmean_by_class[k], npop);
6365 inflNum[eidx] = T(inflNum[eidx] + X * f);
6366 inflDen[eidx] = T(inflDen[eidx] + X);
6367 openTput[eidx] = T(openTput[eidx] + X);
6368 taskTput[lqn.parent[eidx]] = T(taskTput[lqn.parent[eidx]] + X);
6372 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
6373 const std::size_t eidx = lqn.eshift + e;
6376 f = dbl(inflNum[eidx]) / dbl(inflDen[eidx]);
6377 if (!std::isfinite(f) || f < 1.0) f = 1.0;
6378 const T fv = num_traits<T>::from_double(f);
6379 for (std::size_t aidx : lqn.actsof[eidx])
6380 residt[aidx] = T(fv * (lqn.hostdem[aidx].disabled ? Tzero()
6381 : lqn.hostdem[aidx].mean));
6385 std::vector<T> relw(N + 1, Tzero());
6386 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
6387 const std::size_t tidx = lqn.tshift + t;
6388 if (idxhash[tidx] < 0 || !ph_has_layer[tidx])
continue;
6389 const PHLayer& L = ph_layers[tidx];
6390 const LayerResult<T>& res = results.back()[std::size_t(idxhash[tidx])];
6391 const double npop = ph_layer_pop(L, tidx);
6392 for (std::size_t c : L.callers) {
6393 const std::size_t k = L.class_of_caller[c];
6394 T X = Tzero(), Q = Tzero();
6395 for (std::size_t s : L.qstations) {
6396 X = T(X + res.TN(s - 1, k - 1));
6397 Q = T(Q + res.QN(s - 1, k - 1));
6399 if (!std::isfinite(dbl(X)) || dbl(X) < 0.0) X = Tzero();
6400 const T g = ph_inflation_of(ph_residence(Q, X, res.RN(L.qstations[0] - 1, k - 1)),
6401 L.svcmean_by_class[k], npop);
6402 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
6403 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
6404 if (lqn.parent[lqn.callpair_src[cidx]] != c)
continue;
6405 if (lqn.parent[lqn.callpair_dst[cidx]] != tidx)
continue;
6406 const std::size_t eidx = lqn.callpair_dst[cidx];
6407 callservt[cidx] = T(lqn.callproc_mean[cidx] * g * ph_entrymean[eidx]);
6408 callresidt[cidx] = callservt[cidx];
6410 for (std::size_t eidx : lqn.entriesof[tidx])
6411 relw[eidx] = T(relw[eidx] + X * ph_ncalls(c, eidx));
6413 for (
const auto& oa : L.open_arrivals) {
6414 if (oa.second >= 0)
continue;
6415 const std::size_t k = oa.first;
6416 const std::size_t cidx = std::size_t(-oa.second);
6417 const std::size_t eidx = lqn.callpair_dst[cidx];
6419 for (std::size_t s : L.qstations) X = T(X + res.TN(s - 1, k - 1));
6420 const T R = res.RN(L.qstations[0] - 1, k - 1);
6421 if (std::isfinite(dbl(R)) && dbl(R) > 0.0) {
6422 callservt[cidx] = T(R * lqn.callproc_mean[cidx]);
6423 callresidt[cidx] = callservt[cidx];
6425 if (std::isfinite(dbl(X)) && dbl(X) > 0.0) relw[eidx] = T(relw[eidx] + X);
6430 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
6431 const std::size_t tidx = lqn.tshift + t;
6432 const std::vector<std::size_t>& entries = lqn.entriesof[tidx];
6433 if (entries.empty())
continue;
6440 for (std::size_t eidx : entries)
tot = T(tot + relw[eidx] + openTput[eidx]);
6442 for (std::size_t eidx : entries)
6443 ph_share[eidx] = T((relw[eidx] + openTput[eidx]) / tot);
6445 for (std::size_t eidx : entries)
6446 ph_share[eidx] = num_traits<T>::from_double(1.0 /
double(entries.size()));
6454 for (std::size_t eidx : entries) tput[eidx] = T(tput[tidx] * ph_share[eidx]);
6460 const T nrep = num_traits<T>::from_double(std::max(1.0, lqn.repl[tidx]));
6462 : T(tput[tidx] / nrep);
6466 for (std::size_t aidx = lqn.ashift + 1; aidx <= lqn.ashift + lqn.nacts; ++aidx) {
6468 if (!std::isfinite(dbl(v)) && it > 1 && !std::isnan(residt_prev[aidx]))
6469 v = residt_prev_v[aidx];
6470 if (relax_omega < 1.0 && it > 1 && !std::isnan(residt_prev[aidx])) {
6471 const T om = num_traits<T>::from_double(relax_omega);
6472 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6473 v = T(om * v + om1 * residt_prev_v[aidx]);
6476 residt_prev[aidx] = dbl(v);
6477 residt_prev_v[aidx] = v;
6479 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
6480 T v = callservt[cidx];
6481 if (!std::isfinite(dbl(v)))
6482 v = (it > 1 && std::isfinite(callservt_prev[cidx])) ? callservt_prev_v[cidx]
6484 if (relax_omega < 1.0 && it > 1 && !std::isnan(callservt_prev[cidx])) {
6485 const T om = num_traits<T>::from_double(relax_omega);
6486 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6487 v = T(om * v + om1 * callservt_prev_v[cidx]);
6489 callservt[cidx] = v;
6490 callresidt[cidx] = v;
6491 callservt_prev[cidx] = dbl(v);
6492 callresidt_prev[cidx] = dbl(v);
6493 callservt_prev_v[cidx] = v;
6501 ph_compose_entry_laws();
6503 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
6504 const std::size_t eidx = lqn.eshift + e;
6505 if (!ph_has_wf[eidx])
continue;
6506 const std::unordered_map<std::size_t, T>& ex = ph_execs[eidx];
6507 for (std::size_t aidx : lqn.actsof[eidx]) {
6508 T sa = T(residt[aidx] + act_think_time(aidx));
6509 for (std::size_t cidx : lqn.callsof[aidx])
6510 if (lqn.calltype[cidx] == CallType::SYNC) sa = T(sa + callservt[cidx]);
6512 servt_prev[aidx] = dbl(sa);
6513 servt_prev_v[aidx] = sa;
6514 tput[aidx] = T(tput[eidx] * ex.at(aidx));
6515 tput_prev[aidx] = dbl(tput[aidx]);
6516 tput_prev_v[aidx] = tput[aidx];
6519 : Distrib<T>::disabled_dist();
6522 servt[eidx] = ph_entrymean[eidx];
6523 residt[eidx] = ph_entrymean[eidx];
6541 T thread_busy_fraction(
const qn::Layer<T>& L, std::size_t ts,
const LayerResult<T>& r,
6542 bool skipnan)
const {
6543 const qn::Station<T>& st = L.stations[ts - 1];
6544 const double c = st.nservers;
6545 const bool hasdep = !st.lldscaling.empty() || bool(st.cdscaling) || bool(st.jdscaling);
6547 if (!hasdep || std::isinf(c) || c <= 0.0) {
6548 for (std::size_t k = 0; k < L.nclasses; ++k) {
6549 const T v = r.UN(ts - 1, k);
6550 if (!skipnan || !std::isnan(dbl(v))) u = T(u + v);
6554 for (std::size_t k = 0; k < L.nclasses; ++k) {
6555 const T x = r.TN(ts - 1, k);
6556 if (std::isnan(dbl(x)) || !(dbl(x) > 0.0))
continue;
6557 const Distrib<T>& d = L.service[ts - 1][k];
6558 if (d.disabled)
continue;
6559 if (std::isfinite(dbl(d.mean))) u = T(u + x * d.mean);
6561 return T(u / num_traits<T>::from_double(c));
6575 void update_think_times_ph(
int it) {
6578 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
6579 const std::size_t tidx = lqn.tshift + t;
6580 if (ignore[tidx])
continue;
6581 const T ztask = ref_think_time(tidx);
6582 if (idxhash[tidx] < 0) {
6587 const double arvrate = open_arrival_rate_of(tidx);
6589 double nja = lqn.maxmult[tidx];
6590 if (!std::isfinite(nja) || nja <= 0.0) {
6592 for (std::size_t c = 1; c <= NT(); ++c) nja = std::max(nja, njobs(tidx, c));
6595 const std::size_t hidx = lqn.parent[tidx];
6596 if (hidx >= 1 && hidx < idxhash.size() && idxhash[hidx] >= 0 &&
6597 ph_has_layer[hidx] && ph_layers[hidx].class_of_caller[tidx] > 0) {
6598 const PHLayer& HL = ph_layers[hidx];
6599 const LayerResult<T>& hr = results.back()[std::size_t(idxhash[hidx])];
6600 const T rr = hr.RN(HL.qstations[0] - 1, HL.class_of_caller[tidx] - 1);
6601 if (!std::isnan(dbl(rr))) hres = rr;
6603 hres += open_arrival_call_residt(tidx, arvrate);
6604 T za = T(num_traits<T>::from_double(nja / arvrate) - hres - ztask);
6605 if (za < floorv) za = floorv;
6606 if (relax_omega < 1.0 && it > 1 && !std::isnan(thinkt_prev[tidx])) {
6607 const T om = num_traits<T>::from_double(relax_omega);
6608 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6609 za = T(om * za + om1 * thinkt_prev_v[tidx]);
6611 tput[tidx] = num_traits<T>::from_double(arvrate);
6613 thinkt_prev[tidx] = dbl(za);
6614 thinkt_prev_v[tidx] = za;
6624 const PHLayer& L = ph_layers[tidx];
6625 const qn::Layer<T>& m = ensemble[std::size_t(idxhash[tidx])];
6626 const LayerResult<T>& r = results.back()[std::size_t(idxhash[tidx])];
6628 T U = thread_busy_fraction(m, L.qstations[0], r,
true);
6635 if (dbl(ph_setupshare[tidx]) > 0.0)
6636 U = T(U * (Tone() - ph_setupshare[tidx]));
6639 T X = ph_xdemand[tidx];
6642 double nj = lqn.maxmult[tidx];
6643 if (!std::isfinite(nj) || nj <= 0.0) {
6645 for (std::size_t c = 1; c <= NT(); ++c) nj = std::max(nj, njobs(tidx, c));
6649 if (lqn.sched[tidx] == SchedStrategy::INF) {
6651 z = T((num_traits<T>::from_double(nj) - U) / X - ztask);
6653 const T om = U > Tone() ? T(U - Tone()) : T(Tone() - U);
6654 z = T(num_traits<T>::from_double(nj) * om / X - ztask);
6659 if (z < floorv) z = floorv;
6660 if (it > 1 && !std::isnan(thinkt_prev[tidx]) && !std::isfinite(dbl(z)))
6661 z = thinkt_prev_v[tidx];
6662 if (relax_omega < 1.0 && it > 1 && !std::isnan(thinkt_prev[tidx])) {
6663 const T om = num_traits<T>::from_double(relax_omega);
6664 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6665 z = T(om * z + om1 * thinkt_prev_v[tidx]);
6668 thinkt_prev[tidx] = dbl(z);
6669 thinkt_prev_v[tidx] = z;
6683 LnSolution<T> aggregate_ph() {
6684 const std::size_t N = lqn.nidx;
6686 out.QN.assign(N + 1, Tzero());
6687 out.UN.assign(N + 1, Tzero());
6688 out.RN.assign(N + 1, Tzero());
6689 out.TN.assign(N + 1, Tzero());
6690 out.AN.assign(N + 1, Tzero());
6691 out.WN.assign(N + 1, Tzero());
6692 out.defined_Q.assign(N + 1,
false);
6693 out.defined_U.assign(N + 1,
false);
6694 out.defined_R.assign(N + 1,
false);
6695 out.defined_T.assign(N + 1,
false);
6696 out.defined_A.assign(N + 1,
false);
6697 out.defined_W.assign(N + 1,
false);
6698 out.iterations = iterations_done;
6699 out.converged = did_converge;
6701 std::vector<T> PN(N + 1, Tzero()), UT(N + 1, Tzero());
6702 std::vector<bool> hasPN(N + 1,
false), hasUT(N + 1,
false);
6704 for (std::size_t a = 1; a <= lqn.nacts; ++a) {
6705 const std::size_t aidx = lqn.ashift + a;
6706 const std::size_t tidx = lqn.parent[aidx];
6707 if (ignore[tidx])
continue;
6708 const std::size_t hidx = lqn.parent[tidx];
6709 out.TN[aidx] = tput[aidx];
6710 out.defined_T[aidx] =
true;
6711 out.RN[aidx] = servt[aidx];
6712 out.defined_R[aidx] =
true;
6713 UT[aidx] = T(tput[aidx] * servt[aidx]);
6719 const T hd = lqn.hostdem[aidx].disabled ? Tzero() : lqn.hostdem[aidx].mean;
6720 PN[aidx] = T(tput[aidx] * hd / num_traits<T>::from_double(ph_host_servers(hidx)));
6722 PN[hidx] = T(PN[hidx] + PN[aidx]);
6726 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
6727 const std::size_t eidx = lqn.eshift + e;
6728 const std::size_t tidx = lqn.parent[eidx];
6729 if (ignore[tidx])
continue;
6730 out.TN[eidx] = tput[eidx];
6731 out.defined_T[eidx] =
true;
6732 out.RN[eidx] = servt[eidx];
6733 out.defined_R[eidx] =
true;
6734 UT[eidx] = T(tput[eidx] * servt[eidx]);
6736 for (std::size_t aidx : lqn.actsof[eidx]) {
6737 PN[eidx] = T(PN[eidx] + PN[aidx]);
6744 if (ph_has_wf[eidx]) {
6745 const std::unordered_map<std::size_t, T>& ex = ph_execs[eidx];
6746 for (std::size_t aidx : lqn.actsof[eidx]) {
6747 out.WN[aidx] = T(ph_share[eidx] * ex.at(aidx) * residt[aidx]);
6748 out.defined_W[aidx] =
true;
6751 UT[tidx] = T(UT[tidx] + UT[eidx]);
6755 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
6756 const std::size_t tidx = lqn.tshift + t;
6757 if (ignore[tidx])
continue;
6758 out.TN[tidx] = tput[tidx];
6759 out.defined_T[tidx] =
true;
6762 for (std::size_t aidx : lqn.actsof[tidx]) {
6763 PN[tidx] = T(PN[tidx] + PN[aidx]);
6765 if (out.defined_W[aidx]) { w = T(w + out.WN[aidx]); anyw =
true; }
6767 if (anyw) { out.WN[tidx] = w; out.defined_W[tidx] =
true; }
6770 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx)
6771 out.defined_T[hidx] =
false;
6773 for (std::size_t idx = 1; idx <= N; ++idx) {
6774 out.QN[idx] = UT[idx];
6775 out.defined_Q[idx] = hasUT[idx];
6776 out.UN[idx] = PN[idx];
6777 out.defined_U[idx] = hasPN[idx];
6783 out.UN[idx] = Tzero();
6784 out.defined_U[idx] =
true;
6785 out.defined_A[idx] =
false;
6786 const bool host = lqn.type[idx] == LqnElement::HOST;
6787 const bool task = lqn.type[idx] == LqnElement::TASK;
6788 const bool entry = lqn.type[idx] == LqnElement::ENTRY;
6789 out.QN[idx] = Tzero();
6790 out.defined_Q[idx] = !host;
6791 out.RN[idx] = Tzero();
6792 out.defined_R[idx] = !host && !task;
6793 out.WN[idx] = Tzero();
6794 out.defined_W[idx] = !host && !entry;
6795 out.TN[idx] = Tzero();
6796 out.defined_T[idx] = !host;
6802 void update_metrics(
int it) {
6803 if (is_ph_encoding()) {
6804 update_metrics_ph(it);
6807 if (lnmethod ==
"moment3") {
6808 update_metrics_moment_based(it);
6811 update_metrics_default(it);
6822 std::size_t layer_refclass(std::size_t idx,
const qn::Layer<T>& L, std::size_t k)
const {
6823 const auto it = layer_normclass.find(idx);
6824 if (it != layer_normclass.end() && k < it->second.size() && it->second[k] > 0)
6825 return it->second[k];
6827 for (std::size_t cc = 0; cc < L.nchains; ++cc)
6828 if (L.chains[cc][k]) c = cc;
6829 return L.refclass[c];
6832 void update_metrics_default(
int it) {
6833 const std::size_t N = lqn.nidx;
6834 servt.assign(N + 1, Tzero());
6835 residt.assign(N + 1, Tzero());
6836 const int iter_min = std::min(30,
int(std::ceil(opt.iter_max / 4.0)));
6837 const bool averaging = averagingstart >= 0 && it >= iter_min;
6838 const int wnd = averaging ? int(it - averagingstart + 1) : 1;
6840 for (
const UpdRow& row : servt_map) {
6841 const std::size_t e = std::size_t(idxhash[row.idx]);
6842 const qn::Layer<T>& L = ensemble[e];
6843 const std::size_t k = row.cls - 1;
6844 const std::size_t refclass_c = layer_refclass(row.idx, L, k);
6845 const std::size_t refstat_k = L.classes[k].refstat;
6847 T sv = Tzero(), rs = Tzero(), tp = Tzero();
6848 const T wT = T(Tone() / num_traits<T>::from_int(wnd));
6849 for (
int w = 0; w < wnd; ++w) {
6850 const LayerResult<T>& r = results[results.size() - 1 - w][e];
6851 sv += r.RN(row.node - 1, k) * wT;
6852 const T TN_ref = (refclass_c > 0 && refstat_k > 0) ? r.TN(refstat_k - 1, refclass_c - 1) : Tzero();
6854 rs += r.QN(row.node - 1, k) / TN_ref * wT;
6856 rs += r.WN(row.node - 1, k) * wT;
6857 tp += r.TN(row.node - 1, k) * wT;
6859 servt[row.aidx] = sv;
6860 residt[row.aidx] = rs;
6861 tput[row.aidx] = tp;
6866 if (lqn.parent[lqn.parent[row.aidx]] == row.idx) {
6867 const auto hr = host_all_replica_tasks.find(lqn.parent[row.aidx]);
6868 if (hr != host_all_replica_tasks.end() && hr->second > 1) {
6869 const T h = num_traits<T>::from_double(
double(hr->second));
6870 residt[row.aidx] = T(h * residt[row.aidx]);
6871 tput[row.aidx] = T(h * tput[row.aidx]);
6876 const Distrib<T>& at = lqn.actthink[row.aidx];
6878 servt[row.aidx] = T(servt[row.aidx] + at.mean);
6879 residt[row.aidx] = T(residt[row.aidx] + at.mean);
6887 if (async_only_activity(row.aidx)) residt[row.aidx] = servt[row.aidx];
6889 if (relax_omega < 1.0 && it > 1) {
6890 const T om = num_traits<T>::from_double(relax_omega);
6891 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6892 if (!std::isnan(servt_prev[row.aidx]))
6893 servt[row.aidx] = T(om * servt[row.aidx] + om1 * servt_prev_v[row.aidx]);
6894 if (!std::isnan(residt_prev[row.aidx]))
6895 residt[row.aidx] = T(om * residt[row.aidx] + om1 * residt_prev_v[row.aidx]);
6896 if (!std::isnan(tput_prev[row.aidx]))
6897 tput[row.aidx] = T(om * tput[row.aidx] + om1 * tput_prev_v[row.aidx]);
6899 servt_prev[row.aidx] = dbl(servt[row.aidx]);
6900 residt_prev[row.aidx] = dbl(residt[row.aidx]);
6901 tput_prev[row.aidx] = dbl(tput[row.aidx]);
6902 servt_prev_v[row.aidx] = servt[row.aidx];
6903 residt_prev_v[row.aidx] = residt[row.aidx];
6904 tput_prev_v[row.aidx] = tput[row.aidx];
6906 if (servt[row.aidx] > Tzero() && dbl(servt[row.aidx]) <= 1e10)
6916 servt_ph1.assign(N + 1, Tzero());
6917 servt_ph2.assign(N + 1, Tzero());
6918 for (std::size_t a = 1; a <= lqn.nacts; ++a) {
6919 const std::size_t aidx = lqn.ashift + a;
6920 if (lqn.actphase[a] == 1)
6921 servt_ph1[aidx] = servt[aidx];
6923 servt_ph2[aidx] = servt[aidx];
6930 for (
const UpdRow& row : thinkt_map) {
6931 if (!tputproc[row.aidx].disabled)
continue;
6932 const std::size_t e = std::size_t(idxhash[row.idx]);
6934 const T wT = T(Tone() / num_traits<T>::from_int(wnd));
6935 for (
int w = 0; w < wnd; ++w)
6936 tp += results[results.size() - 1 - w][e].TN(row.node - 1, row.cls - 1) * wT;
6937 tput[row.aidx] = tp;
6942 callservt.assign(lqn.ncalls + 1, Tzero());
6943 callresidt.assign(lqn.ncalls + 1, Tzero());
6950 std::vector<std::size_t> call_rows(call_map.size(), 1);
6951 for (std::size_t q = 0; q < call_map.size(); ++q) {
6952 std::vector<std::size_t> st;
6953 for (
const UpdRow& o : call_map)
6954 if (o.idx == call_map[q].idx && o.aidx == call_map[q].aidx && o.node > 1 &&
6955 std::find(st.begin(), st.end(), o.node) == st.end())
6956 st.push_back(o.node);
6957 call_rows[q] = std::max<std::size_t>(1, st.size());
6959 for (std::size_t q = 0; q < call_map.size(); ++q) {
6960 const UpdRow& row = call_map[q];
6961 if (row.node <= 1)
continue;
6962 const std::size_t e = std::size_t(idxhash[row.idx]);
6963 const LayerResult<T>& r = results.back()[e];
6964 callservt[row.aidx] =
6965 T(r.RN(row.node - 1, row.cls - 1) * lqn.callproc_mean[row.aidx]);
6971 const qn::Layer<T>& L = ensemble[e];
6972 const std::size_t k = row.cls - 1;
6973 const std::size_t refclass_c = layer_refclass(row.idx, L, k);
6974 const std::size_t refstat_k = L.classes[k].refstat;
6975 const T TN_ref = (refclass_c > 0 && refstat_k > 0) ? r.TN(refstat_k - 1, refclass_c - 1) : Tzero();
6977 ? T(num_traits<T>::from_int(
int(call_rows[q])) *
6978 r.QN(row.node - 1, k) / TN_ref)
6979 : r.WN(row.node - 1, k);
6981 const T rw = region_wait(e, row.cls);
6983 callservt[row.aidx] = T(callservt[row.aidx] + rw);
6984 callresidt[row.aidx] = T(callresidt[row.aidx] + rw);
6986 if (relax_omega < 1.0 && it > 1 && !std::isnan(callservt_prev[row.aidx])) {
6987 const T om = num_traits<T>::from_double(relax_omega);
6988 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
6989 callservt[row.aidx] =
6990 T(om * callservt[row.aidx] + om1 * callservt_prev_v[row.aidx]);
6992 callservt_prev[row.aidx] = dbl(callservt[row.aidx]);
6993 callservt_prev_v[row.aidx] = callservt[row.aidx];
6994 callresidt_prev[row.aidx] = dbl(callresidt[row.aidx]);
6997 resolve_entry_service();
7009 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7010 const std::size_t eidx = lqn.eshift + e;
7011 servt_ph2[eidx] = (eidx < entry_servt_last.size() && entry_servt_last[eidx] > Tzero())
7012 ? T(entry_servt_ph2[eidx] * servt[eidx] / entry_servt_last[eidx])
7014 servt_ph1[eidx] = T(servt[eidx] - servt_ph2[eidx]);
7016 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7017 const std::size_t eidx = lqn.eshift + e;
7018 const std::size_t tidx = lqn.parent[eidx];
7020 if (lqn.isref[tidx] || !lqn.issynccaller.any_col(eidx)) {
7021 residt[eidx] = servt[eidx];
7024 T entry_tput = Tzero();
7026 entry_tput = tput[eidx];
7028 entry_tput = tput[tidx];
7034 residt[eidx] = T(servt_ph1[eidx] + prOvertake[e] * servt_ph2[eidx]);
7038 for (
const UpdRow& row : call_map) {
7039 if (row.node <= 1)
continue;
7040 const std::size_t eidx = lqn.callpair_dst[row.aidx];
7043 for (
const UpdRow& row : call_map) {
7044 if (row.node <= 1)
continue;
7045 const std::size_t eidx = lqn.callpair_dst[row.aidx];
7047 callservt[row.aidx] = servt[eidx];
7048 callservtproc[row.aidx] = servtproc[eidx];
7049 }
else if (callservt[row.aidx] > Tzero()) {
7062 if (has_phase2 && (is_flat_layering() || it == 1)) {
7063 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
7064 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
7065 const std::size_t target = lqn.callpair_dst[cidx];
7066 if (target <= lqn.eshift || target > lqn.eshift + lqn.nentries)
continue;
7068 const T eff = residt[target];
7069 if (!(eff > Tzero()))
continue;
7070 const T w = T(eff * lqn.callproc_mean[cidx]);
7071 callservt[cidx] = w;
7072 callresidt[cidx] = w;
7092 static void cdf_moments(
const fluid::FluidPassage& c,
double& m1,
double& m2,
double& m3) {
7094 for (std::size_t i = 0; i + 1 < c.t.size(); ++i) {
7095 const double x = 0.5 * (c.t[i + 1] + c.t[i]);
7096 const double w = c.cdf[i + 1] - c.cdf[i];
7099 m3 += x * x * x * w;
7104 const std::vector<std::vector<fluid::FluidPassage>>& layer_cdf(std::size_t e) {
7105 if (cdf_repo[e].empty()) {
7112 ensemble[e].refresh_rt();
7114 cdf_repo[e] = detail::ln_fluid_cdf_respt(ensemble[e], opt.layer_fluid);
7115 }
catch (
const std::exception&) {
7122 cdf_repo[e] = layer_cdf_exp_fallback(e);
7130 std::vector<std::vector<fluid::FluidPassage>> layer_cdf_exp_fallback(std::size_t e) {
7131 const qn::NetworkStruct<T>& L = ensemble[e];
7132 std::vector<std::vector<fluid::FluidPassage>> out(
7133 L.nstations, std::vector<fluid::FluidPassage>(L.nclasses));
7134 if (results.empty() || e >= results.back().size())
return out;
7135 const Matrix<T>& RN = results.back()[e].RN;
7136 const std::size_t npts = 100;
7137 for (std::size_t i = 0; i < L.nstations; ++i) {
7138 if (L.stations[i].nodetype == qn::NodeType::Source)
continue;
7139 for (std::size_t r = 0; r < L.nclasses; ++r) {
7140 if (L.disabled[i][r])
continue;
7141 const double rn = (i < static_cast<std::size_t>(RN.rows()) &&
7142 r < static_cast<std::size_t>(RN.cols()))
7145 if (!(std::isfinite(rn) && rn > 0.0))
continue;
7146 fluid::FluidPassage& cell = out[i][r];
7147 cell.t.reserve(npts);
7148 cell.cdf.reserve(npts);
7149 for (std::size_t j = 0; j < npts; ++j) {
7151 0.001 + (0.999 - 0.001) *
static_cast<double>(j) / (npts - 1);
7152 cell.cdf.push_back(q);
7153 cell.t.push_back(-std::log(1.0 - q) * rn);
7174 int entry_visit_ratio(std::size_t eidx, T& ratio)
const {
7175 const std::size_t tidx = lqn.parent[eidx];
7176 const std::size_t hidx = lqn.parent[tidx];
7177 if (ignore[tidx] || ignore[hidx] || idxhash[hidx] < 0)
return 0;
7178 if (!lqn.issynccaller.any_col(eidx))
return 1;
7179 const std::size_t hl = std::size_t(idxhash[hidx]);
7180 const qn::Layer<T>& L = ensemble[hl];
7181 const LayerResult<T>& r = results.back()[hl];
7182 T task_tput = Tzero(), entry_tput = Tzero();
7183 for (
const auto& kv : L.attr_tasks)
7184 if (kv.second == tidx) task_tput += r.TN(L.clientIdx - 1, kv.first - 1);
7185 for (
const auto& kv : L.attr_entries)
7186 if (kv.second == eidx) entry_tput += r.TN(L.clientIdx - 1, kv.first - 1);
7188 ratio = T(task_tput / (entry_tput > floor ? entry_tput : floor));
7193 Matrix<T> entry_service_resolvent()
const {
7194 const std::size_t dim = lqn.nidx + lqn.ncalls;
7195 Matrix<T> A(dim + 1, dim + 1, Tzero());
7196 for (std::size_t i = 0; i <= dim; ++i) {
7198 for (std::size_t j = 0; j <= dim; ++j) A(i, j) = T(A(i, j) - servtmatrix(i, j));
7200 return ::line::inverse(A);
7226 void update_metrics_moment_based(
int it) {
7227 const std::size_t N = lqn.nidx;
7228 servt.assign(N + 1, Tzero());
7229 residt.assign(N + 1, Tzero());
7230 callservt.assign(lqn.ncalls + 1, Tzero());
7231 callresidt.assign(lqn.ncalls + 1, Tzero());
7234 for (
const UpdRow& row : servt_map) {
7235 const std::size_t e = std::size_t(idxhash[row.idx]);
7236 const qn::Layer<T>& L = ensemble[e];
7237 const LayerResult<T>& r = results.back()[e];
7238 const std::size_t k = row.cls - 1;
7240 for (std::size_t cc = 0; cc < L.nchains; ++cc)
7241 if (L.chains[cc][k]) c = cc;
7242 const std::size_t refclass_c = L.refclass[c];
7243 const std::size_t refstat_k = L.classes[k].refstat;
7244 const T TN_ref = (refclass_c > 0 && refstat_k > 0) ? r.TN(refstat_k - 1, refclass_c - 1) : Tzero();
7246 tput[row.aidx] = r.TN(row.node - 1, k);
7248 ? T(r.QN(row.node - 1, k) / TN_ref)
7249 : r.WN(row.node - 1, k);
7250 if (!hasconverged) {
7251 servt[row.aidx] = r.RN(row.node - 1, k);
7253 const Distrib<T>& at = lqn.actthink[row.aidx];
7255 servt[row.aidx] = T(servt[row.aidx] + at.mean);
7256 residt[row.aidx] = T(residt[row.aidx] + at.mean);
7261 if (async_only_activity(row.aidx)) residt[row.aidx] = servt[row.aidx];
7263 servtcdf[row.aidx] = layer_cdf(e)[row.node - 1][k];
7267 for (
const UpdRow& row : call_map) {
7268 if (row.node <= 1)
continue;
7269 const std::size_t e = std::size_t(idxhash[row.idx]);
7270 const LayerResult<T>& r = results.back()[e];
7271 callresidt[row.aidx] = r.WN(row.node - 1, row.cls - 1);
7273 callservt[row.aidx] =
7274 T(r.RN(row.node - 1, row.cls - 1) * lqn.callproc_mean[row.aidx]);
7276 callservtcdf[row.aidx] = layer_cdf(e)[row.node - 1][row.cls - 1];
7280 moment3_means_pass(it);
7282 moment3_distribution_pass(it);
7286 void moment3_means_pass(
int it) {
7287 const std::size_t dim = lqn.nidx + lqn.ncalls;
7288 std::vector<T> x(dim + 1, Tzero());
7289 for (std::size_t i = 1; i <= lqn.nidx; ++i) x[i] = residt[i];
7290 for (std::size_t c = 1; c <= lqn.ncalls; ++c) x[lqn.nidx + c] = callresidt[c];
7293 const Matrix<T> Rinv = entry_service_resolvent();
7294 std::vector<T> entry_servt(dim + 1, Tzero());
7295 for (std::size_t i = 1; i <= dim; ++i) {
7297 for (std::size_t j = 1; j <= dim; ++j)
7298 if (Rinv(i, j) != Tzero()) s += Rinv(i, j) * x[j];
7301 for (std::size_t i = 1; i <= lqn.eshift; ++i) entry_servt[i] = Tzero();
7312 for (std::size_t e = 1; e <= lqn.nentries; ++e)
7313 servt[lqn.eshift + e] = entry_servt[lqn.eshift + e];
7316 std::vector<T> entry_residt(dim + 1, Tzero());
7317 for (std::size_t i = lqn.eshift + 1; i <= lqn.eshift + lqn.nentries; ++i) {
7319 for (std::size_t j = 1; j <= dim; ++j)
7320 if (servtmatrix(i, j) != Tzero()) s += servtmatrix(i, j) * x[j];
7321 entry_residt[i] = s;
7324 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7325 const std::size_t eidx = lqn.eshift + e;
7327 const int state = entry_visit_ratio(eidx, ratio);
7328 if (state == 0)
continue;
7330 residt[eidx] = entry_residt[eidx];
7333 servt[eidx] = T(entry_servt[eidx] * ratio);
7334 residt[eidx] = T(entry_residt[eidx] * ratio);
7337 for (
const UpdRow& row : call_map) {
7338 if (row.node <= 1)
continue;
7339 const std::size_t eidx = lqn.callpair_dst[row.aidx];
7342 for (
const UpdRow& row : call_map) {
7343 if (row.node <= 1)
continue;
7344 const std::size_t eidx = lqn.callpair_dst[row.aidx];
7347 callservt[row.aidx] = servt[eidx];
7348 callservtproc[row.aidx] = servtproc[eidx];
7349 }
else if (callservt[row.aidx] > Tzero()) {
7365 void moment3_distribution_pass(
int it) {
7366 if constexpr (!num_traits<T>::has_transcendental) {
7368 "SolverLN: the 'moment3' method fits an APH to a response-time CDF, which needs "
7369 "square roots and a matrix exponential; rerun with --arith double or real");
7371 const std::size_t dim = lqn.nidx + lqn.ncalls;
7372 const Matrix<T> Rinv = entry_service_resolvent();
7375 mam::AphPair<T> zero_law;
7376 zero_law.alpha.push_back(Tone());
7379 for (std::size_t en = 1; en <= lqn.nentries; ++en) {
7380 const std::size_t eidx = lqn.eshift + en;
7381 std::vector<mam::AphPair<T>> seq;
7382 for (std::size_t fitidx = 1; fitidx <= dim; ++fitidx) {
7383 if (!(Rinv(eidx, fitidx) > Tzero()))
continue;
7387 if (fitidx <= lqn.eshift + lqn.nentries)
continue;
7388 const bool is_call = fitidx > lqn.nidx;
7389 const fluid::FluidPassage& curve =
7390 is_call ? callservtcdf[fitidx - lqn.nidx] : servtcdf[fitidx];
7391 double m1 = 0.0, m2 = 0.0, m3 = 0.0;
7392 cdf_moments(curve, m1, m2, m3);
7396 if (!is_call && !lqn.actthink[fitidx].disabled &&
7398 const Distrib<T>& zt = lqn.actthink[fitidx];
7402 m3 = m3 + 3.0 * m2 * t1 + 3.0 * m1 * t2 + t3;
7403 m2 = m2 + 2.0 * m1 * t1 + t2;
7414 num_traits<T>::from_double(m1), num_traits<T>::from_double(m2),
7415 num_traits<T>::from_double(m3), 10u,
7417 const mam::AphPair<T> law = aph_pair_of_map(fit.aph);
7419 double reps = dbl(Rinv(eidx, fitidx));
7422 if (is_call) reps *= dbl(lqn.callproc_mean[fitidx - lqn.nidx]);
7423 const long whole =
static_cast<long>(std::floor(reps));
7424 const double frac = reps -
static_cast<double>(whole);
7425 for (
long q = 0; q < whole; ++q) seq.push_back(law);
7428 num_traits<T>::from_double(frac),
7429 num_traits<T>::from_double(1.0 - frac),
7433 const std::size_t cidx = fitidx - lqn.nidx;
7434 callservt[cidx] = num_traits<T>::from_double(m1);
7437 servt[fitidx] = num_traits<T>::from_double(m1);
7443 servt[eidx] = Tzero();
7447 entryproc[en] = entry_law;
7450 entrycdfrespt[en] = aph_eval_cdf(entry_law);
7458 std::vector<T> x(dim + 1, Tzero());
7459 for (std::size_t i = 1; i <= lqn.nidx; ++i) x[i] = residt[i];
7460 for (std::size_t c = 1; c <= lqn.ncalls; ++c) x[lqn.nidx + c] = callresidt[c];
7461 for (std::size_t en = 1; en <= lqn.nentries; ++en) {
7462 const std::size_t eidx = lqn.eshift + en;
7464 for (std::size_t j = 1; j <= dim; ++j)
7465 if (servtmatrix(eidx, j) != Tzero()) s += servtmatrix(eidx, j) * x[j];
7467 const int state = entry_visit_ratio(eidx, ratio);
7468 if (state == 0)
continue;
7469 residt[eidx] = state == 2 ? T(s * ratio) : s;
7473 for (
const UpdRow& row : call_map) {
7474 if (row.node <= 1)
continue;
7475 const std::size_t eidx = lqn.callpair_dst[row.aidx];
7476 callservt[row.aidx] = servt[eidx];
7483 moment_pass_done =
true;
7495 static mam::AphPair<T> aph_pair_of_map(
const mam::Map<T>& m) {
7496 mam::AphPair<T> out;
7497 const std::size_t n = m.D0.rows();
7499 out.alpha.assign(n, Tzero());
7500 for (std::size_t i = 0; i < n; ++i) {
7502 for (std::size_t j = 0; j < n; ++j) rowsum += m.D1(i, j);
7504 for (std::size_t j = 0; j < n; ++j) out.alpha[j] = T(m.D1(i, j) / rowsum);
7509 out.alpha[0] = Tone();
7517 static LnCdf aph_eval_cdf(
const mam::AphPair<T>& law) {
7521 const double var = m2 - m1 * m1;
7522 const double sigma = var > 0.0 ? std::sqrt(var) : 0.0;
7523 const double tmax = m1 + 10.0 * sigma;
7524 const std::size_t
P = 500;
7527 for (std::size_t k = 0; k <
P; ++k) {
7528 const double t = tmax *
static_cast<double>(k) /
static_cast<double>(P - 1);
7530 Matrix<T> St(law.S.rows(), law.S.cols(), Tzero());
7531 for (std::size_t i = 0; i < law.S.rows(); ++i)
7532 for (std::size_t j = 0; j < law.S.cols(); ++j)
7533 St(i, j) = T(law.S(i, j) * num_traits<T>::from_double(t));
7536 for (std::size_t i = 0; i < E.rows(); ++i)
7537 for (std::size_t j = 0; j < E.cols(); ++j) surv += law.alpha[i] * E(i, j);
7538 out.cdf[k] = 1.0 - dbl(surv);
7557 std::vector<T> join_excess() {
7558 std::vector<T> excess(lqn.nidx + 1, Tzero());
7559 join_tail.assign(lqn.nidx + 1, Tzero());
7560 std::vector<std::size_t> joined;
7561 for (std::size_t tail = 1; tail <= lqn.nidx; ++tail) {
7562 if (lqn.actpretype[tail] != PrecedenceType::PRE_AND)
continue;
7563 for (std::size_t sx : lqn.graph.succ(tail))
7564 if (sx > lqn.ashift && sx <= lqn.ashift + lqn.nacts) joined.push_back(sx);
7566 std::sort(joined.begin(), joined.end());
7567 joined.erase(std::unique(joined.begin(), joined.end()), joined.end());
7568 if (joined.empty())
return excess;
7570 fj::LqnBranchView<T> view;
7571 view.graph =
Matrix<T>(lqn.nidx, lqn.nidx, Tzero());
7572 for (std::size_t i = 1; i <= lqn.nidx; ++i)
7573 for (
const auto& e : lqn.graph.row[i]) view.graph(i - 1, e.first - 1) = e.second;
7574 view.ashift = lqn.ashift;
7575 view.nacts = lqn.nacts;
7576 view.actposttype.assign(lqn.nidx, 0);
7577 for (std::size_t i = 1; i <= lqn.nidx; ++i)
7578 view.actposttype[i - 1] =
static_cast<int>(lqn.actposttype[i]);
7580 for (std::size_t aidx : joined) {
7581 const std::vector<std::vector<std::size_t>> branches =
7583 if (branches.empty())
continue;
7584 std::vector<T> means;
7585 for (
const auto& br : branches) {
7587 for (std::size_t a : br) {
7591 if (a < lqn.callsof.size())
7592 for (std::size_t cidx : lqn.callsof[a])
7593 if (cidx < lqn.calltype.size() && lqn.calltype[cidx] == CallType::SYNC
7594 && cidx < callresidt.size())
7595 s += callresidt[cidx];
7599 if (means.size() == 1)
continue;
7600 std::size_t quorum = means.size();
7601 if (lqn.actquorum[aidx] >= 1 && lqn.actquorum[aidx] <= means.size())
7602 quorum = lqn.actquorum[aidx];
7603 std::vector<T> vars;
7604 for (
const T& m2 : means) vars.push_back(T(m2 * m2));
7606 for (
const T& m2 : means) serial += m2;
7607 if constexpr (num_traits<T>::has_transcendental) {
7609 excess[aidx] = T(q.m - serial);
7610 if (quorum < means.size()) {
7613 if (all.m > q.m) join_tail[aidx] = T(all.m - q.m);
7617 "SolverLN: the AND-join completion time is a k-th order statistic fitted "
7618 "through a three-point distribution, which needs a square root; use the "
7619 "double or real backend for a model with an AND join");
7626 void resolve_entry_service() {
7627 const std::size_t dim = lqn.nidx + lqn.ncalls;
7628 std::vector<T> x(dim + 1, Tzero());
7629 for (std::size_t i = 1; i <= lqn.nidx; ++i) x[i] = residt[i];
7630 for (std::size_t c = 1; c <= lqn.ncalls; ++c) x[lqn.nidx + c] = callresidt[c];
7631 std::vector<T> entry_servt(dim + 1, Tzero());
7632 for (std::size_t i = 1; i <= dim; ++i) {
7634 for (std::size_t j = 1; j <= dim; ++j)
7635 if (servtmatrix(i, j) != Tzero()) s += servtmatrix(i, j) * x[j];
7641 const std::vector<T> excess = join_excess();
7642 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7643 const std::size_t eidx = lqn.eshift + e;
7644 for (std::size_t aidx = 1; aidx <= lqn.nidx; ++aidx) {
7645 if (excess[aidx] == Tzero())
continue;
7646 if (servtmatrix(eidx, aidx) > Tzero())
7647 entry_servt[eidx] = T(entry_servt[eidx] + excess[aidx]);
7649 if (entry_servt[eidx] < Tzero()) entry_servt[eidx] = Tzero();
7651 for (std::size_t aidx = 1; aidx <= lqn.nidx; ++aidx)
7652 if (join_tail[aidx] > Tzero() && servtmatrix(eidx, aidx) > Tzero())
7653 entry_servt[eidx] = T(entry_servt[eidx] + join_tail[aidx]);
7663 std::unordered_map<std::size_t, double> charge_su;
7664 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7665 const std::size_t tidx = lqn.parent[lqn.eshift + e];
7666 if (charge_su.count(tidx))
continue;
7668 for (std::size_t e2 = 1; e2 <= lqn.nentries; ++e2)
7669 if (lqn.parent[lqn.eshift + e2] == tidx) S += dbl(entry_servt[lqn.eshift + e2]);
7670 charge_su[tidx] = setup_charge(tidx, S);
7672 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7673 const std::size_t eidx = lqn.eshift + e;
7674 const double c = charge_su[lqn.parent[eidx]];
7676 entry_servt[eidx] = T(entry_servt[eidx] + num_traits<T>::from_double(c));
7682 std::vector<T> x2(dim + 1, Tzero());
7683 for (std::size_t a = 1; a <= lqn.nacts; ++a)
7684 if (lqn.actphase[a] > 1) x2[lqn.ashift + a] = x[lqn.ashift + a];
7685 for (std::size_t c = 1; c <= lqn.ncalls; ++c) {
7686 const std::size_t src = lqn.callpair_src[c];
7687 if (src > lqn.ashift && src <= lqn.ashift + lqn.nacts && lqn.actphase[src - lqn.ashift] > 1)
7688 x2[lqn.nidx + c] = x[lqn.nidx + c];
7690 entry_servt_ph2.assign(dim + 1, Tzero());
7691 for (std::size_t i = lqn.eshift + 1; i <= dim; ++i) {
7693 for (std::size_t j = 1; j <= dim; ++j)
7694 if (servtmatrix(i, j) != Tzero()) s2 += servtmatrix(i, j) * x2[j];
7695 entry_servt_ph2[i] = s2;
7698 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7699 const std::size_t eidx = lqn.eshift + e;
7700 for (std::size_t aidx = 1; aidx <= lqn.nidx; ++aidx)
7701 if (join_tail[aidx] > Tzero() && servtmatrix(eidx, aidx) > Tzero())
7702 entry_servt_ph2[eidx] = T(entry_servt_ph2[eidx] + join_tail[aidx]);
7704 entry_servt_last = entry_servt;
7709 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
7710 const std::size_t eidx = lqn.eshift + e;
7711 const std::size_t tidx = lqn.parent[eidx];
7712 const std::size_t hidx = lqn.parent[tidx];
7713 if (ignore[tidx] || ignore[hidx])
continue;
7714 if (idxhash[hidx] < 0)
continue;
7715 const bool has_sync = lqn.issynccaller.any_col(eidx);
7717 servt[eidx] = entry_servt[eidx];
7718 residt[eidx] = entry_servt[eidx];
7721 const std::size_t hl = std::size_t(idxhash[hidx]);
7722 const qn::Layer<T>& L = ensemble[hl];
7723 const LayerResult<T>& r = results.back()[hl];
7724 T task_tput = Tzero(), entry_tput = Tzero();
7725 for (
const auto& kv : L.attr_tasks)
7726 if (kv.second == tidx) task_tput += r.TN(L.clientIdx - 1, kv.first - 1);
7727 for (
const auto& kv : L.attr_entries)
7728 if (kv.second == eidx) entry_tput += r.TN(L.clientIdx - 1, kv.first - 1);
7730 servt[eidx] = T(entry_servt[eidx] * task_tput / entry_tput);
7731 residt[eidx] = servt[eidx];
7735 entryvisits[eidx] = T(entry_tput / task_tput);
7737 servt[eidx] = entry_servt[eidx];
7738 residt[eidx] = entry_servt[eidx];
7754 bool layer_takes_interlock(std::size_t e)
const {
7755 return opt.layer_solver ==
"mva" && e < ensemble.size() &&
7771 std::vector<std::vector<double>> build_layer_interlock(std::size_t e,
7772 const std::vector<std::size_t>& hts,
7773 const std::vector<T>& prIL,
7774 const std::vector<T>& PrIL)
const {
7775 const qn::Layer<T>& L = ensemble[e];
7776 const std::size_t R = L.nclasses;
7777 std::vector<double> cls_ir(R, 0.0);
7778 std::vector<double> cls_pr(R, 0.0);
7779 auto stamp = [&](std::size_t classIdx, std::size_t tidx) {
7780 if (classIdx < 1 || classIdx > R)
return;
7781 for (std::size_t i = 0; i < hts.size(); ++i)
7782 if (hts[i] == tidx) {
7783 cls_ir[classIdx - 1] = dbl(prIL[i]);
7784 cls_pr[classIdx - 1] = dbl(PrIL[i]);
7787 for (
const auto& a : L.attr_tasks) stamp(a.first, a.second);
7788 for (
const auto& a : L.attr_entries) stamp(a.first, lqn.parent[a.second]);
7789 for (
const auto& a : L.attr_activities) stamp(a.first, lqn.parent[a.second]);
7790 for (
const auto& a : L.attr_calls) stamp(a[0], lqn.parent[a[2]]);
7792 std::vector<std::vector<double>> IL(R, std::vector<double>(R, 0.0));
7794 for (std::size_t r = 0; r < R; ++r) {
7796 for (std::size_t sIl = 0; sIl < R; ++sIl) {
7798 IL[r][sIl] = cls_pr[sIl] * cls_ir[sIl] * cls_ir[r];
7802 return any ? IL : std::vector<std::vector<double>>();
7815 std::pair<T, T> interlock_prob(std::size_t client_tidx, std::size_t server_idx,
7816 bool isProcessorHost)
const {
7817 const std::vector<std::size_t>& common = il_common_entries[server_idx];
7818 const double nsrc = il_num_sources[server_idx];
7819 if (nsrc == 0.0 || common.empty())
return std::make_pair(Tzero(), Tzero());
7820 const std::vector<std::size_t>& allsrc = il_src_all[server_idx];
7821 T sum_flow = Tzero();
7822 T sum_pril = Tzero();
7823 for (std::size_t ce : common) {
7824 const std::size_t srcTask = lqn.parent[ce];
7825 const std::size_t cen = ce - lqn.eshift;
7827 double m_src = lqn.mult[srcTask];
7828 if (!std::isfinite(m_src) || m_src < 1.0) m_src = 1.0;
7829 double m_eff = m_src;
7830 if (isProcessorHost) m_eff = (m_src > 3.0) ? (m_src + m_src) : (m_src * m_src);
7831 for (std::size_t dst : lqn.entriesof[client_tidx]) {
7832 const std::size_t dn = dst - lqn.eshift;
7833 if (dn < 1 || dn > lqn.nentries)
continue;
7834 if (!(il_all(cen, dn) > Tzero()))
continue;
7835 T ce_tput = tput[ce];
7837 ce_tput = tput[lqn.actsof[ce][0]];
7841 if (std::find(allsrc.begin(), allsrc.end(), srcTask) != allsrc.end()) {
7842 const T contrib = T(ce_tput * il_all(cen, dn));
7843 sum_flow += contrib;
7844 sum_pril += T(contrib / num_traits<T>::from_double(m_eff));
7848 T client_tput = tput[client_tidx];
7850 for (std::size_t e : lqn.entriesof[client_tidx]) {
7853 et = tput[lqn.actsof[e][0]];
7854 client_tput = T(client_tput + et);
7858 return std::make_pair(Tzero(), Tzero());
7859 const T flow = sum_flow < client_tput ? sum_flow : client_tput;
7860 T IR = T(flow / client_tput);
7861 if (IR > Tone()) IR = Tone();
7862 if (dbl(IR) < 0.0) IR = Tzero();
7865 if (pr > Tone()) pr = Tone();
7866 if (dbl(pr) < 0.0) pr = Tzero();
7867 return std::make_pair(IR, pr);
7870 void update_populations(
int) {
7871 const std::vector<T> residt_orig = residt;
7872 const std::vector<T> callresidt_orig = callresidt;
7873 bool adjusted =
false;
7877 layer_interlock.assign(ensemble.size(), {});
7884 std::vector<bool> il_in_layer(lqn.nidx + 1,
false);
7885 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
7886 const std::size_t server_tidx = lqn.tshift + t;
7887 if (server_tidx >= il_common_entries.size() || il_common_entries[server_tidx].empty())
continue;
7888 if (!(idxhash[server_tidx] >= 0) || !layer_takes_interlock(std::size_t(idxhash[server_tidx])))
7890 const std::size_t e = std::size_t(idxhash[server_tidx]);
7891 std::vector<std::size_t> cts;
7892 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
7893 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
7894 if (lqn.parent[lqn.callpair_dst[cidx]] != server_tidx)
continue;
7895 const std::size_t ctidx = lqn.parent[lqn.callpair_src[cidx]];
7896 if (std::find(cts.begin(), cts.end(), ctidx) == cts.end()) cts.push_back(ctidx);
7898 std::vector<T> prIL(cts.size(), Tzero()), PrIL(cts.size(), Tzero());
7899 for (std::size_t i = 0; i < cts.size(); ++i) {
7900 const std::pair<T, T> ilp = interlock_prob(cts[i], server_tidx,
false);
7901 prIL[i] = ilp.first;
7902 PrIL[i] = ilp.second;
7904 il_in_layer[server_tidx] =
true;
7905 layer_interlock[e] = build_layer_interlock(e, cts, prIL, PrIL);
7908 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
7909 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
7910 const std::size_t dst = lqn.callpair_dst[cidx];
7911 const std::size_t server_tidx = lqn.parent[dst];
7912 std::size_t server = 0;
7913 if (server_tidx <= NT() && !il_common_entries[server_tidx].empty()) {
7914 server = server_tidx;
7915 }
else if (server_tidx > lqn.tshift) {
7916 const std::size_t h = lqn.parent[server_tidx];
7917 if (h >= 1 && h <= NT() && !il_common_entries[h].empty()) server = h;
7919 if (server == 0 || il_in_layer[server])
continue;
7925 if (server <= lqn.nhosts && idxhash[server] >= 0 &&
7926 layer_takes_interlock(std::size_t(idxhash[server])))
7928 const std::size_t client_tidx = lqn.parent[lqn.callpair_src[cidx]];
7932 const std::pair<T, T> ilp = interlock_prob(client_tidx, server,
false);
7933 const T prIL = T(ilp.first * ilp.second);
7935 const T S = servt[dst];
7936 const T cm = lqn.callproc_mean[cidx];
7937 if (!(cm > Tzero()) || !(callservt[cidx] > Tzero()))
continue;
7938 const T RN = T(callservt[cidx] / cm);
7939 const T W = RN > S ? T(RN - S) : Tzero();
7941 const T RN_adj = T(S + (Tone() - prIL) * W);
7942 const T scale = T(RN_adj / RN);
7943 callservt[cidx] = T(callservt[cidx] * scale);
7944 callresidt[cidx] = T(callresidt[cidx] * scale);
7945 if (callservt[cidx] > Tzero())
7950 for (std::size_t h = 1; h <= lqn.nhosts; ++h) {
7951 if (il_common_entries[h].empty())
continue;
7952 const std::vector<std::size_t>& hts = lqn.tasksof[h];
7953 std::vector<T> prIL(hts.size(), Tzero()), PrIL(hts.size(), Tzero()),
7954 tutil(hts.size(), Tzero());
7955 for (std::size_t i = 0; i < hts.size(); ++i) {
7957 const std::pair<T, T> ilp = interlock_prob(hts[i], h,
true);
7958 prIL[i] = ilp.first;
7959 PrIL[i] = ilp.second;
7960 for (std::size_t e : lqn.entriesof[hts[i]])
7961 for (std::size_t a : lqn.actsof[e]) tutil[i] += tput[a] * lqn.hostdem[a].mean;
7963 T Utot = Tzero(), Uil = Tzero();
7964 for (std::size_t i = 0; i < hts.size(); ++i) {
7970 const T frac = T(Uil / Utot);
7978 if (idxhash[h] >= 0 && layer_takes_interlock(std::size_t(idxhash[h]))) {
7979 const std::size_t e = std::size_t(idxhash[h]);
7980 layer_interlock[e] = build_layer_interlock(e, hts, prIL, PrIL);
7984 for (std::size_t i = 0; i < hts.size(); ++i) {
7992 const T eff = T(prIL[i] * PrIL[i] * frac);
7993 for (std::size_t e : lqn.entriesof[hts[i]])
7994 for (std::size_t a : lqn.actsof[e]) {
7998 const T D = lqn.hostdem[a].mean;
7999 const T R = servt[a];
8001 residt[a] = T(residt[a] * (D + (Tone() - eff) * (R - D)) / R);
8008 if (!adjusted)
return;
8009 const std::size_t dim = lqn.nidx + lqn.ncalls;
8010 auto esum = [&](
const std::vector<T>& rs,
const std::vector<T>& cr, std::size_t i) {
8012 for (std::size_t j = 1; j <= dim; ++j) {
8013 if (servtmatrix(i, j) == Tzero())
continue;
8014 s += servtmatrix(i, j) * (j <= lqn.nidx ? rs[j] : cr[j - lqn.nidx]);
8022 std::vector<bool> isph2(dim + 1,
false);
8024 for (std::size_t a = 1; a <= lqn.nacts; ++a)
8025 if (lqn.actphase[a] > 1) isph2[lqn.ashift + a] =
true;
8026 for (std::size_t c = 1; c <= lqn.ncalls; ++c) {
8027 const std::size_t src = lqn.callpair_src[c];
8028 if (src > lqn.ashift && src <= lqn.ashift + lqn.nacts && lqn.actphase[src - lqn.ashift] > 1)
8029 isph2[lqn.nidx + c] =
true;
8032 auto esum2 = [&](
const std::vector<T>& rs,
const std::vector<T>& cr, std::size_t i) {
8034 for (std::size_t j = 1; j <= dim; ++j) {
8035 if (!isph2[j] || servtmatrix(i, j) == Tzero())
continue;
8036 s += servtmatrix(i, j) * (j <= lqn.nidx ? rs[j] : cr[j - lqn.nidx]);
8040 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
8041 const std::size_t eidx = lqn.eshift + e;
8042 const T oldv = esum(residt_orig, callresidt_orig, eidx);
8044 const T ratio = T(esum(residt, callresidt, eidx) / oldv);
8046 const T old2 = esum2(residt_orig, callresidt_orig, eidx);
8047 const T new2 = esum2(residt, callresidt, eidx);
8048 const T old1 = T(oldv - old2);
8049 const T new1 = T(esum(residt, callresidt, eidx) - new2);
8052 servt_ph1[eidx] = T(servt_ph1[eidx] * r1);
8053 servt_ph2[eidx] = T(servt_ph2[eidx] * r2);
8054 servt[eidx] = T(servt_ph1[eidx] + servt_ph2[eidx]);
8058 const std::size_t tidx = lqn.parent[eidx];
8059 if (lqn.isref[tidx] || !lqn.issynccaller.any_col(eidx))
8060 residt[eidx] = servt[eidx];
8062 residt[eidx] = T(servt_ph1[eidx] + prOvertake[e] * servt_ph2[eidx]);
8073 if (!moment_pass_done) {
8074 servt[eidx] = T(servt[eidx] * ratio);
8077 residt[eidx] = T(residt[eidx] * ratio);
8093 T ref_think_time(std::size_t tidx)
const {
8094 if (tidx >= lqn.isref.size() || !lqn.isref[tidx])
return Tzero();
8095 if (lqn.think[tidx].disabled)
return Tzero();
8096 return lqn.think[tidx].mean;
8105 T phase2_tail(std::size_t tidx)
const {
8107 if (!has_phase2)
return tail;
8110 for (std::size_t eidx : lqn.entriesof[tidx]) {
8111 if (servt_ph2[eidx] <= fine)
continue;
8113 if (w <= fine) w = Tone();
8114 const T d = T(servt[eidx] - residt[eidx]);
8115 tail += T(w * (d > Tzero() ? d : Tzero()));
8118 if (wtot > fine) tail = T(tail / wtot);
8122 void update_think_times(
int it) {
8125 if (is_ph_encoding()) {
8126 update_think_times_ph(it);
8130 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
8131 const std::size_t tidx = lqn.tshift + t;
8133 const T ztask = ref_think_time(tidx);
8134 if (idxhash[tidx] < 0) {
8138 const double arvrate = open_arrival_rate_of(tidx);
8141 for (std::size_t c = 1; c <= NT(); ++c) nja = std::max(nja, njobs(tidx, c));
8142 if (!(nja > 0.0)) nja = lqn.maxmult[tidx];
8144 for (std::size_t eidx : lqn.entriesof[tidx]) {
8145 for (std::size_t aidx : lqn.actsof[eidx])
8146 if (!std::isnan(dbl(residt[aidx]))) hres += residt[aidx];
8147 hres += sync_call_residt_of(lqn.actsof[eidx]);
8150 T za = T(num_traits<T>::from_double(nja / arvrate) - hres - ztask);
8151 if (za < floora) za = floora;
8152 if (relax_omega < 1.0 && it > 1 && !std::isnan(thinkt_prev[tidx])) {
8153 const T om = num_traits<T>::from_double(relax_omega);
8154 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
8155 za = T(om * za + om1 * thinkt_prev_v[tidx]);
8157 tput[tidx] = num_traits<T>::from_double(arvrate);
8159 thinkt_prev[tidx] = dbl(za);
8160 thinkt_prev_v[tidx] = za;
8168 const std::size_t e = std::size_t(idxhash[tidx]);
8169 const qn::Layer<T>& L = ensemble[e];
8170 const LayerResult<T>& r = results.back()[e];
8172 for (std::size_t c = 1; c <= NT(); ++c) nj = std::max(nj, njobs(tidx, c));
8174 const std::size_t ts = station_idx_of(L, tidx);
8176 for (std::size_t k = 0; k < L.nclasses; ++k) tp += r.TN(ts - 1, k);
8177 tput[tidx] = T(num_traits<T>::from_double(lqn.repl[tidx]) * tp);
8179 util[tidx] = thread_busy_fraction(L, ts, r,
false);
8183 const double nrep = std::max(1.0, lqn.repl[tidx]);
8184 const T xcyc = T(tput[tidx] / num_traits<T>::from_double(nrep));
8185 if (nrep > 1.0 && std::isfinite(lqn.maxmult[tidx]) && lqn.maxmult[tidx] > 0.0) nj = lqn.maxmult[tidx];
8187 if (lqn.sched[tidx] == SchedStrategy::INF) {
8189 raw = xcyc == Tzero()
8191 : T((num_traits<T>::from_double(nj) - util[tidx]) / xcyc - ztask);
8193 const T om = util[tidx] > Tone() ? T(util[tidx] - Tone()) : T(Tone() - util[tidx]);
8194 raw = xcyc == Tzero()
8196 : T(num_traits<T>::from_double(nj) * om / xcyc - ztask);
8199 thinkt[tidx] = raw < floorv ? floorv : raw;
8203 if (lnmethod !=
"flat.cs") {
8204 const T lessz = T(thinkt[tidx] - phase2_tail(tidx));
8205 thinkt[tidx] = lessz < floorv ? floorv : lessz;
8217 const double c = setup_charge(tidx);
8219 const T withc = T(thinkt[tidx] + num_traits<T>::from_double(c));
8220 thinkt[tidx] = withc < floorv ? floorv : withc;
8224 if (relax_omega < 1.0 && it > 1 && !std::isnan(thinkt_prev[tidx])) {
8225 const double rawd = dbl(thinkt[tidx]);
8227 thinkt_prev[tidx] = rawd;
8228 thinkt_prev_v[tidx] = thinkt[tidx];
8230 const T om = num_traits<T>::from_double(relax_omega);
8231 const T om1 = num_traits<T>::from_double(1.0 - relax_omega);
8232 thinkt[tidx] = T(om * thinkt[tidx] + om1 * thinkt_prev_v[tidx]);
8234 thinkt_prev[tidx] = dbl(thinkt[tidx]);
8235 thinkt_prev_v[tidx] = thinkt[tidx];
8243 void update_layers(
int it) {
8246 if (is_ph_encoding()) {
8247 update_layers_ph(it);
8250 const bool elevator = (it % 2) == 1;
8251 const std::size_t nt = thinkt_map.size();
8252 for (std::size_t r = 0; r < nt; ++r) {
8253 const UpdRow& row = thinkt_map[elevator ? nt - 1 - r : r];
8254 qn::Layer<T>& L = ensemble[std::size_t(idxhash[row.idx])];
8255 if (row.aidx <= NT() && opt.interlocking &&
8256 L.classes[row.cls - 1].type == JobClassType::CLOSED)
8257 L.classes[row.cls - 1].population = njobs(row.aidx, row.idx);
8258 if (row.node == L.clientIdx) {
8259 if (lqn.type[row.aidx] == LqnElement::TASK) {
8260 if (lqn.sched[row.aidx] != SchedStrategy::REF) {
8261 if (!thinktproc[row.aidx].disabled)
8262 L.set_service(row.node, row.cls, thinktproc[row.aidx]);
8264 L.set_service(row.node, row.cls, servtproc[row.aidx]);
8267 L.set_service(row.node, row.cls, servtproc[row.aidx]);
8270 L.set_service(row.node, row.cls, servtproc[row.aidx]);
8273 const std::size_t nc = call_map.size();
8274 for (std::size_t r = 0; r < nc; ++r) {
8275 const UpdRow& row = call_map[elevator ? nc - 1 - r : r];
8276 qn::Layer<T>& L = ensemble[std::size_t(idxhash[row.idx])];
8277 if (row.node == L.clientIdx) {
8278 L.set_service(row.node, row.cls, callservtproc[row.aidx]);
8280 const std::size_t eidx = lqn.callpair_dst[row.aidx];
8287 L.set_service(row.node, row.cls, servtproc[eidx]);
8298 if (interlockMethod ==
"refpath") update_ref_path_stages(it);
8299 for (
const UpdRow& row : arv_call_map) {
8300 qn::Layer<T>& L = ensemble[std::size_t(idxhash[row.idx])];
8301 const Distrib<T>& d = tputproc[lqn.callpair_src[row.aidx]];
8302 if (!d.disabled) L.set_service(row.node, row.cls, d);
8316 void update_ref_path_stages(
int it) {
8317 if (refpath_map.empty())
return;
8318 const std::size_t nkeys = refpath_keys.size();
8319 std::vector<T> stage(nkeys, Tzero());
8320 for (std::size_t r = 0; r < refpath_map.size(); ++r) {
8321 const RefPathRow& row = refpath_map[r];
8325 term = residt[row.elem];
8330 v = entryvisits[row.fromentry];
8331 term = T(callresidt[row.elem] / v);
8335 term = thread_wait(row.elem);
8338 if (!std::isfinite(dbl(term))) term = Tzero();
8339 stage[refpath_group[r]] = T(stage[refpath_group[r]] + term * num_traits<T>::from_double(row.sign));
8341 const double omega = relax_omega;
8342 for (std::size_t k = 0; k < nkeys; ++k) {
8343 if (stage[k] < Tzero()) stage[k] = Tzero();
8345 if (omega < 1.0 && it > 1 && !std::isnan(refpath_stage_prev[k])) {
8346 T prevS = refpath_stage_prev_v[k];
8349 stage[k] = T(num_traits<T>::from_double(omega) * stage[k] +
8350 num_traits<T>::from_double(1.0 - omega) * prevS);
8352 refpath_stage_prev[k] = dbl(stage[k]);
8353 refpath_stage_prev_v[k] = stage[k];
8354 qn::Layer<T>& L = ensemble[std::size_t(idxhash[refpath_keys[k][0]])];
8369 T thread_wait(std::size_t tidx)
const {
8370 if (tidx < 1 || tidx > lqn.nidx)
return Tzero();
8371 if (lqn.sched[tidx] == SchedStrategy::INF || !std::isfinite(lqn.maxmult[tidx]))
return Tzero();
8372 if (idxhash[tidx] < 0 || results.empty())
return Tzero();
8373 const std::size_t e = std::size_t(idxhash[tidx]);
8374 const qn::Layer<T>& L = ensemble[e];
8375 const LayerResult<T>& r = results.back()[e];
8376 const std::size_t ks = station_idx_of(L, tidx);
8378 for (std::size_t c = 0; c < L.nclasses; ++c) Qtot += r.QN(ks - 1, c);
8379 const T Btot = T(num_traits<T>::from_double(lqn.maxmult[tidx]) * util[tidx]);
8380 const T Xtot = T(tput[tidx] / num_traits<T>::from_double(std::max(1.0, lqn.repl[tidx])));
8382 T d = T(Qtot - Btot);
8401 T region_wait(std::size_t e, std::size_t cls)
const {
8402 const qn::Layer<T>& L = ensemble[e];
8403 if (L.regions.empty() || results.empty())
return Tzero();
8404 const LayerResult<T>& r = results.back()[e];
8405 const std::size_t k = cls - 1;
8406 std::size_t c = L.nchains;
8407 for (std::size_t cc = 0; cc < L.nchains; ++cc)
8408 if (L.chains[cc][k]) c = cc;
8409 if (c == L.nchains)
return Tzero();
8413 std::size_t rs = L.serverIdx;
8414 for (std::size_t i = 0; i < L.regions[0].members.size(); ++i)
8415 if (L.regions[0].members[i]) { rs = i + 1;
break; }
8418 T inside = Tzero(), tput_srv = Tzero();
8419 for (std::size_t j = 0; j < L.nclasses; ++j) {
8420 if (!L.chains[c][j])
continue;
8421 const double p = L.classes[j].population;
8422 if (!std::isfinite(p))
return Tzero();
8424 for (std::size_t i = 0; i < L.nstations; ++i) inside += r.QN(i, j);
8425 tput_srv += r.TN(rs - 1, j);
8427 const T deficit = T(num_traits<T>::from_double(pop) - inside);
8428 if (!(deficit > Tzero()) || !(tput_srv > Tzero()))
return Tzero();
8429 return T(deficit / tput_srv);
8433 void update_routing_probabilities(
int) {
8434 for (std::size_t u = 0; u < unique_route_idx.size(); ++u) {
8437 const std::size_t idx = unique_route_idx[unique_route_idx.size() - 1 - u];
8438 qn::Layer<T>& L = ensemble[std::size_t(idxhash[idx])];
8439 bool updated =
false;
8440 for (
const RouteRow& r : route_map) {
8441 if (r.idx != idx)
continue;
8442 if (idxhash[r.tidx_caller] < 0)
continue;
8443 const std::size_t cl = std::size_t(idxhash[r.tidx_caller]);
8444 const qn::Layer<T>& CL = ensemble[cl];
8445 const LayerResult<T>& cr = results.back()[cl];
8448 const std::size_t cs = station_idx_of(CL, r.tidx_caller);
8450 for (std::size_t k = 0; k < CL.nclasses; ++k) Xtot += cr.TN(cs - 1, k);
8451 if (!(Xtot > Tzero()))
continue;
8452 T entry_tput = Tzero();
8453 for (
const auto& a : CL.attr_calls)
8455 entry_tput += cr.TN(station_idx_of(CL, lqn.parent[a[3]]) - 1, a[0] - 1);
8456 L.set_route(r.cfrom, r.cto, r.nodefrom, r.nodeto, T(entry_tput / Xtot));
8459 if (updated) L.refresh_chains();
8478 void require_transient_ready()
const {
8479 if (opt.layer_solver !=
"fluid")
8481 "SolverLN: the layered transient is the transient OF EACH LAYER, which only the "
8482 "fluid layer solver has; rebuild the ensemble with layer_solver 'fluid'");
8486 bool has_finite_horizon()
const {
8487 return std::isfinite(opt.timespan_end) && opt.timespan_end > 0.0;
8492 std::vector<std::vector<std::vector<double>>> Q, U, Tp, R;
8499 void run_layer_transient(std::size_t e,
const std::vector<FluidRateSched>& sched,
8500 LnTranLayer& block, TranTraj& traj) {
8501 qn::Layer<T>& L = ensemble[e];
8503 fluid::FluidOptions fo = opt.layer_fluid;
8504 fo.rate_sched = sched;
8510 if (e < layer_tran_init.size() && !layer_tran_init[e].empty())
8511 fo.init_sol = layer_tran_init[e];
8516 const double t_end =
8518 const std::vector<fluid::FluidTranPoint> pts =
8519 detail::ln_fluid_transient(L, fo, t_end, opt.tran_points, opt.tran_grid);
8520 const std::size_t M = L.nstations, K = L.nclasses,
P = pts.size();
8522 auto alloc = [&](std::vector<std::vector<std::vector<double>>>& A) {
8523 A.assign(M, std::vector<std::vector<double>>(K, std::vector<double>(P, 0.0)));
8532 for (std::size_t p = 0; p <
P; ++p) {
8533 block.t[p] = pts[p].t;
8534 for (std::size_t i = 0; i < M; ++i)
8535 for (std::size_t r = 0; r < K; ++r) {
8536 const double q = pts[p].QN(i, r), u = pts[p].UN(i, r), x = pts[p].TN(i, r);
8537 block.QN[i][r][p] = q;
8538 block.UN[i][r][p] = u;
8539 block.TN[i][r][p] = x;
8540 traj.Q[i][r][p] = q;
8541 traj.U[i][r][p] = u;
8542 traj.Tp[i][r][p] = x;
8554 LnTranSolution tran_avg_decoupled() {
8555 require_transient_ready();
8556 if (results.empty()) iterate();
8558 out.mode =
"decoupled";
8559 out.layers.resize(ensemble.size());
8560 for (std::size_t e = 0; e < ensemble.size(); ++e) {
8562 run_layer_transient(e, std::vector<FluidRateSched>(), out.layers[e], tj);
8584 LnTranSolution tran_avg_coupled() {
8585 require_transient_ready();
8590 if (!has_finite_horizon())
return tran_avg_decoupled();
8591 if (results.empty()) iterate();
8592 const std::size_t E = ensemble.size();
8595 out.mode =
"coupled";
8596 out.layers.resize(E);
8597 std::vector<TranTraj> traj(E), prev(E);
8598 for (std::size_t e = 0; e < E; ++e)
8599 run_layer_transient(e, std::vector<FluidRateSched>(), out.layers[e], traj[e]);
8600 const std::vector<double> tgrid = out.layers.empty() ? std::vector<double>() : out.
layers[0].t;
8602 for (
long iter = 1; iter <= opt.ln_transient_iter_max; ++iter) {
8604 const std::vector<std::vector<FluidRateSched>> sched =
8605 build_rate_sched(recompute_demand(traj, tgrid), tgrid);
8606 for (std::size_t e = 0; e < E; ++e)
8607 run_layer_transient(e, sched[e], out.layers[e], traj[e]);
8609 for (std::size_t e = 0; e < E; ++e)
8610 for (std::size_t i = 0; i < traj[e].Q.size(); ++i)
8611 for (std::size_t r = 0; r < traj[e].Q[i].size(); ++r)
8612 for (std::size_t p = 0; p < traj[e].Q[i][r].size(); ++p)
8613 gap = std::max(gap, std::fabs(traj[e].Q[i][r][p] - prev[e].Q[i][r][p]));
8614 out.iterations = iter;
8616 if (gap < opt.ln_transient_tol)
break;
8623 std::map<std::size_t, std::vector<double>> thinkt;
8624 std::map<std::size_t, std::vector<double>> callservt;
8635 TranDemand recompute_demand(
const std::vector<TranTraj>& traj,
8636 const std::vector<double>& tgrid)
const {
8638 const std::size_t ng = tgrid.size();
8640 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
8641 const std::size_t tidx = lqn.tshift + t;
8642 if (idxhash[tidx] < 0 || lqn.isref[tidx])
continue;
8643 const std::size_t e = std::size_t(idxhash[tidx]);
8644 const qn::Layer<T>& L = ensemble[e];
8645 const std::size_t s = station_idx_of(L, tidx) - 1;
8647 for (std::size_t c = 1; c <= NT(); ++c) nj = std::max(nj, njobs(tidx, c));
8649 const double userthink = dbl(ref_think_time(tidx));
8650 std::vector<double> tk(ng, 0.0);
8651 for (std::size_t p = 0; p < ng; ++p) {
8652 double U = 0.0, X = 0.0;
8653 for (std::size_t r = 0; r < L.nclasses; ++r) {
8654 U += traj[e].U[s][r][p];
8655 X += traj[e].Tp[s][r][p];
8658 double v = lqn.sched[tidx] == SchedStrategy::INF
8659 ? (nj - U) / Xs - userthink
8660 : nj * std::fabs(1.0 - U) / Xs - userthink;
8662 tk[p] = v + userthink;
8664 out.thinkt[tidx] = tk;
8667 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
8668 if (lqn.calltype[cidx] != CallType::SYNC)
continue;
8669 const std::size_t eidx = lqn.callpair_dst[cidx];
8670 const std::size_t tidx = lqn.parent[eidx];
8671 if (tidx > NT() || idxhash[tidx] < 0)
continue;
8672 const std::size_t e = std::size_t(idxhash[tidx]);
8673 const qn::Layer<T>& L = ensemble[e];
8674 const std::size_t s = station_idx_of(L, tidx) - 1;
8675 std::vector<double> Rc(ng, 0.0);
8677 for (std::size_t r = 0; r < L.nclasses; ++r) {
8678 if (L.classes[r].attr_kind !=
int(LqnElement::ENTRY))
continue;
8679 if (L.classes[r].attr_idx != eidx)
continue;
8681 for (std::size_t p = 0; p < ng; ++p) Rc[p] += traj[e].R[s][r][p];
8686 for (std::size_t r = 0; r < L.nclasses; ++r)
8687 for (std::size_t p = 0; p < ng; ++p) Rc[p] += traj[e].R[s][r][p];
8689 const double cm = dbl(lqn.callproc_mean[cidx]);
8690 for (std::size_t p = 0; p < ng; ++p) Rc[p] *= cm;
8691 out.callservt[cidx] = Rc;
8707 std::vector<std::vector<FluidRateSched>> build_rate_sched(
8708 const TranDemand& demand,
const std::vector<double>& tgrid)
const {
8709 std::vector<std::vector<FluidRateSched>> out(ensemble.size());
8710 const bool want_think =
8711 opt.ln_transient_channels ==
"both" || opt.ln_transient_channels ==
"thinkt";
8712 const bool want_call =
8713 opt.ln_transient_channels ==
"both" || opt.ln_transient_channels ==
"callservt";
8715 auto add = [&](std::size_t e, std::size_t station, std::size_t cls,
8716 const std::vector<double>& d) {
8717 if (d.empty())
return;
8718 const double dend = d.back();
8723 const double cap = 20.0;
8728 s.rates.resize(d.size());
8729 for (std::size_t p = 0; p < d.size(); ++p) {
8730 const double v = std::min(std::max(d[p], dend / cap), dend * cap);
8731 s.rates[p] = 1.0 / v;
8733 s.nominal = 1.0 / dend;
8734 out[e].push_back(s);
8738 for (
const UpdRow& row : thinkt_map) {
8739 if (idxhash[row.idx] < 0)
continue;
8740 const std::size_t e = std::size_t(idxhash[row.idx]);
8741 if (row.node != ensemble[e].clientIdx)
continue;
8742 if (lqn.type[row.aidx] != LqnElement::TASK)
continue;
8743 if (lqn.sched[row.aidx] == SchedStrategy::REF)
continue;
8744 const auto it = demand.thinkt.find(row.aidx);
8745 if (it == demand.thinkt.end())
continue;
8746 add(e, row.node, row.cls, it->second);
8749 for (
const UpdRow& row : call_map) {
8750 if (idxhash[row.idx] < 0)
continue;
8751 const std::size_t e = std::size_t(idxhash[row.idx]);
8752 if (row.node != ensemble[e].clientIdx)
continue;
8753 const auto it = demand.callservt.find(row.aidx);
8754 if (it == demand.callservt.end())
continue;
8755 add(e, row.node, row.cls, it->second);
8761 std::size_t bound_activity_of(std::size_t eidx)
const {
8762 for (std::size_t sx : lqn.graph.succ(eidx))
8763 if (sx > lqn.ashift)
return sx;
8770 LnSolution<T> aggregate() {
8771 const std::size_t N = lqn.nidx;
8773 auto mk = [&](std::vector<T>& v, std::vector<bool>& d) {
8774 v.assign(N + 1, Tzero());
8775 d.assign(N + 1,
false);
8777 std::vector<T>
QN,
UN, RN, TN, PN, SN, WN, AN;
8778 std::vector<bool> dQ, dU, dR, dT, dP, dS, dW, dA;
8779 mk(QN, dQ); mk(UN, dU); mk(RN, dR); mk(TN, dT);
8780 mk(PN, dP); mk(SN, dS); mk(WN, dW); mk(AN, dA);
8781 std::vector<bool> wn_done(N + 1,
false);
8783 for (std::size_t e = 0; e < ensemble.size(); ++e) {
8784 const qn::Layer<T>& L = ensemble[e];
8785 const LayerResult<T>& r = results.back()[e];
8786 const std::size_t clientIdx = L.clientIdx;
8791 const bool has_host_server = !L.host_stations.empty();
8792 for (std::size_t hs : L.host_stations) {
8793 const std::size_t hidx = L.stations[hs - 1].attr_idx;
8798 for (std::size_t k = 0; k < L.nclasses; ++k) {
8799 if (L.classes[k].completes) {
8800 T t = clientIdx > 0 ? r.TN(clientIdx - 1, k) : Tzero();
8801 const T ts = r.TN(hs - 1, k);
8802 TN[hidx] = T(TN[hidx] + (t > ts ? t : ts));
8804 if (L.classes[k].attr_kind ==
int(LqnElement::ACTIVITY)) {
8806 if (station_idx_of_class(L, k) != hs)
continue;
8807 const std::size_t aidx = L.classes[k].attr_idx;
8808 const std::size_t tidx = lqn.parent[aidx];
8811 PN[aidx] = T(PN[aidx] + r.UN(hs - 1, k));
8812 PN[tidx] = T(PN[tidx] + r.UN(hs - 1, k));
8813 PN[hidx] = T(PN[hidx] + r.UN(hs - 1, k));
8814 }
else if (L.classes[k].attr_kind ==
int(LqnElement::ENTRY)
8815 && L.classes[k].type == JobClassType::OPEN) {
8819 if (station_idx_of_class(L, k) != hs)
continue;
8820 const std::size_t eidx = L.classes[k].attr_idx;
8821 const std::size_t tidx = lqn.parent[eidx];
8822 const std::size_t aidx = bound_activity_of(eidx);
8823 const T uo = r.UN(hs - 1, k);
8826 PN[aidx] = T(PN[aidx] + uo);
8829 PN[tidx] = T(PN[tidx] + uo);
8830 PN[hidx] = T(PN[hidx] + uo);
8836 for (std::size_t k = 0; k < L.nclasses; ++k) {
8837 const int kind = L.classes[k].attr_kind;
8841 const std::size_t serverIdx = station_idx_of_class(L, k);
8842 if (kind ==
int(LqnElement::TASK)) {
8843 const std::size_t tidx = L.classes[k].attr_idx;
8844 if (has_host_server && !dT[tidx]) {
8846 TN[tidx] = r.TN(clientIdx - 1, k);
8848 }
else if (kind ==
int(LqnElement::ENTRY) && L.classes[k].type == JobClassType::OPEN) {
8852 const std::size_t eidx = L.classes[k].attr_idx;
8853 const T to = r.TN(L.classes[k].refstat - 1, k);
8854 for (std::size_t idx : {eidx, lqn.parent[eidx], bound_activity_of(eidx)}) {
8855 if (idx == 0)
continue;
8857 TN[idx] = T(TN[idx] + to);
8859 }
else if (kind ==
int(LqnElement::ENTRY)) {
8860 const std::size_t eidx = L.classes[k].attr_idx;
8867 if (has_host_server && !dT[eidx]) {
8869 TN[eidx] = r.TN(clientIdx - 1, k);
8871 }
else if (kind ==
int(LqnElement::CALL)) {
8872 const std::size_t cidx = L.classes[k].attr_idx;
8873 const std::size_t aidx = lqn.callpair_src[cidx];
8874 if (lqn.calltype[cidx] == CallType::SYNC) {
8876 SN[aidx] = T(SN[aidx] + r.RN(serverIdx - 1, k) * lqn.callproc_mean[cidx]);
8879 QN[aidx] = T(QN[aidx] + r.QN(serverIdx - 1, k));
8880 }
else if (kind ==
int(LqnElement::ACTIVITY)) {
8881 const std::size_t aidx = L.classes[k].attr_idx;
8882 const std::size_t tidx = lqn.parent[aidx];
8884 QN[tidx] = T(QN[tidx] + r.QN(serverIdx - 1, k));
8887 TN[aidx] = T(TN[aidx] + r.TN(serverIdx - 1, k));
8889 SN[aidx] = T(SN[aidx] + r.RN(serverIdx - 1, k));
8891 RN[aidx] = T(RN[aidx] + r.RN(serverIdx - 1, k));
8894 WN[aidx] = residt[aidx];
8895 if (!wn_done[aidx]) {
8896 WN[tidx] = T(WN[tidx] + residt[aidx]);
8897 wn_done[aidx] =
true;
8899 QN[aidx] = T(QN[aidx] + r.QN(serverIdx - 1, k));
8907 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
8908 const std::size_t tidx = lqn.tshift + t;
8909 const double nrep = lqn.repl[tidx];
8910 if (!(nrep > 1.0))
continue;
8911 std::vector<std::size_t> scaleIdx{tidx};
8912 scaleIdx.insert(scaleIdx.end(), lqn.entriesof[tidx].begin(), lqn.entriesof[tidx].end());
8913 scaleIdx.insert(scaleIdx.end(), lqn.actsof[tidx].begin(), lqn.actsof[tidx].end());
8916 const auto hr = host_all_replica_tasks.find(tidx);
8917 const bool hall = hr != host_all_replica_tasks.end();
8918 const T pscale = num_traits<T>::from_double(hall ?
double(hr->second) : nrep);
8919 for (std::size_t idx : scaleIdx) {
8920 const bool isact = std::find(lqn.actsof[tidx].begin(), lqn.actsof[tidx].end(), idx) !=
8921 lqn.actsof[tidx].end();
8922 const T tscale = !hall ? num_traits<T>::from_double(nrep)
8923 : isact ? pscale : Tone();
8924 if (dT[idx]) TN[idx] = T(tscale * TN[idx]);
8925 if (dP[idx]) PN[idx] = T(pscale * PN[idx]);
8928 for (std::size_t hidx = 1; hidx <= lqn.nhosts; ++hidx)
8929 if (lqn.repl[hidx] > 1.0 && dP[hidx]) PN[hidx] = T(num_traits<T>::from_double(lqn.repl[hidx]) * PN[hidx]);
8931 for (std::size_t e = 1; e <= lqn.nentries; ++e) {
8932 const std::size_t eidx = lqn.eshift + e;
8933 const std::size_t tidx = lqn.parent[eidx];
8940 ? T(TN[eidx] * (servt_ph1[eidx] + servt_ph2[eidx]))
8941 : T(TN[eidx] * SN[eidx]);
8944 for (std::size_t a : lqn.actsof[eidx])
8949 if (!lqn.actsof[eidx].empty()) {
8951 PN[eidx] = any ? ps : Tzero();
8953 for (std::size_t a : lqn.actsof[tidx]) {
8955 UN[a] = T(TN[a] * SN[a]);
8957 UN[tidx] = T(UN[tidx] + UN[eidx]);
8969 for (std::size_t i = 1; i <= N; ++i)
8974 const bool host = lqn.type[i] == LqnElement::HOST;
8975 const bool task = lqn.type[i] == LqnElement::TASK;
8976 const bool entry = lqn.type[i] == LqnElement::ENTRY;
8980 dS[i] = !host && !task;
8982 dW[i] = !host && !entry;
8988 s.QN =
UN; s.defined_Q = dU;
8989 s.UN = PN; s.defined_U = dP;
8990 s.RN = SN; s.defined_R = dS;
8991 s.TN = TN; s.defined_T = dT;
8992 s.AN = AN; s.defined_A = dA;
8993 s.WN = WN; s.defined_W = dW;
8994 s.iterations = iterations_done;
8995 s.converged = did_converge;