841 ldes_engine_reject(
sn, o);
843 const std::size_t M =
sn.nstations, K =
sn.nclasses;
844 if (M == 0 || K == 0)
throw InputError(
"SolverLDES (native engine): empty model");
846 const std::uint64_t max_events =
849 throw InputError(
"SolverLDES (native engine): the completion budget is zero");
852 const std::uint64_t base =
853 (o.
seed >= 0) ?
static_cast<std::uint64_t
>(o.
seed) : std::random_device{}();
863 const bool slotted = o.
slotted;
865 auto slot_snap = [&](
double v,
const char* what) ->
double {
866 if (!slotted || v == 0.0)
return v;
867 const double slots = v / slot_len;
868 const double rounded = std::floor(slots + 0.5);
869 if (rounded < 1.0 || std::fabs(slots - rounded) > 1e-9 * std::max(1.0, slots))
870 throw InputError(std::string(
"SolverLDES (native engine): slotted mode sampled a ") +
871 what +
" of " + std::to_string(v) +
872 ", which is not a positive multiple of the slot length " +
873 std::to_string(slot_len) +
874 "; a discrete-time model needs lattice-valued interarrival and "
875 "service times, e.g. Geometric or Det on an integral slot count");
876 return rounded * slot_len;
904 const long long seed_ll =
static_cast<long long>(base);
905 const long long Kll =
static_cast<long long>(K);
906 std::size_t num_sources = 0, num_service_nodes = 0;
907 std::vector<std::size_t> svc_index(M, 0);
908 for (std::size_t i = 0; i < M; ++i) {
909 if (
sn.stations[i].nodetype == NodeType::Source) {
912 svc_index[i] = num_service_nodes++;
915 const long long nsrc =
static_cast<long long>(num_sources);
916 const long long nsvc =
static_cast<long long>(num_service_nodes);
918 std::vector<Rng> g_arr;
920 for (std::size_t k = 0; k < K; ++k) {
921 const long long off = (
static_cast<long long>(k)) * 10;
922 g_arr.push_back(Rng(seed_ll, off, off + 2000));
924 std::vector<std::vector<Rng>> g_svc;
926 for (std::size_t i = 0; i < M; ++i) {
927 std::vector<Rng> row;
929 for (std::size_t k = 0; k < K; ++k) {
930 const long long off =
931 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
static_cast<long long>(k)) *
933 row.push_back(Rng(seed_ll, off));
935 g_svc.push_back(row);
940 std::vector<std::vector<std::vector<Rng>>> g_hsvc(M);
941 for (std::size_t i = 0; i < M; ++i) {
942 const std::size_t nT =
sn.stations[i].server_types.size();
943 g_hsvc[i].resize(nT);
944 for (std::size_t t = 0; t < nT; ++t) {
945 g_hsvc[i][t].reserve(K);
946 for (std::size_t k = 0; k < K; ++k) {
947 const long long off =
948 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
949 static_cast<long long>(k)) * 10 +
950 600000 +
static_cast<long long>(t) * 137;
951 g_hsvc[i][t].push_back(Rng(seed_ll, off));
955 std::vector<Rng> g_aux;
957 for (std::size_t i = 0; i < M; ++i) {
958 const long long off = ((nsrc + nsvc +
static_cast<long long>(svc_index[i])) * Kll) * 10;
959 g_aux.push_back(Rng(seed_ll, off));
961 Rng g_routing(seed_ll, 900000);
964 Rng g_spn(seed_ll, 5000);
968 Rng g_qplace(seed_ll, 5001);
973 Rng g_fork(seed_ll, 88888);
978 Rng g_siro(seed_ll, 99999);
986 auto fork_is_variable = [&](std::size_t node) ->
bool {
988 if (fp == 0)
return false;
989 const double tpl =
sn.nodes[node - 1].tasks_per_link;
990 for (std::size_t k = 0; k < fp->
fan_out_link.rows(); ++k)
991 for (std::size_t r = 0; r < fp->
fan_out_link.cols(); ++r) {
993 if (p == 0.0)
continue;
994 if (p != 1.0)
return true;
1004 for (std::size_t e = 0; e < d.params.size(); ++e) tot += d.params[e];
1005 const double u = g_fork.aux.next_double() * tot;
1007 for (std::size_t e = 0; e < d.params.size(); ++e) {
1008 accp += d.params[e];
1010 return static_cast<int>((d.trace.empty() ?
static_cast<double>(e + 1) : d.trace[e]) +
1013 return static_cast<int>(
1014 (d.trace.empty() ?
static_cast<double>(d.params.size()) : d.trace.back()) + 0.5);
1018 std::vector<StationState> S(M);
1019 std::size_t source_st = M;
1020 std::vector<int> classprio(K, 0);
1021 std::vector<double> classdeadline(K, std::numeric_limits<double>::infinity());
1022 for (std::size_t r = 0; r < K; ++r) {
1023 classprio[r] =
sn.classes[r].prio;
1024 classdeadline[r] =
sn.classes[r].deadline;
1027 for (std::size_t i = 0; i < M; ++i) {
1028 const auto& st =
sn.stations[i];
1029 StationState& s = S[i];
1031 s.off.assign(K,
true);
1032 s.class_mean.assign(K, 0.0);
1033 s.weight.assign(K, 1.0);
1034 for (std::size_t r = 0; r < K; ++r)
1037 if (st.nodetype == NodeType::Source) {
1038 s.role = Role::Source;
1040 }
else if (st.nodetype == NodeType::Fork || st.nodetype == NodeType::Join ||
1041 st.nodetype == NodeType::Place || st.nodetype == NodeType::Transition) {
1044 s.role = Role::Synchronization;
1050 if (st.nodetype == NodeType::Join) s.off.assign(K,
false);
1061 if (st.nodetype == NodeType::Place) {
1062 s.off.assign(K,
false);
1063 if (std::isfinite(st.nservers))
1064 s.nservers =
static_cast<std::size_t
>(st.nservers + 0.5);
1066 }
else if (st.nodetype == NodeType::Delay || st.sched == SchedStrategy::INF) {
1067 s.role = Role::Delay;
1069 s.role = Role::Queue;
1070 s.ps = is_ps_family(st.sched);
1071 s.preemptive = is_preemptive(st.sched);
1072 s.resume = is_preemptive_resume(st.sched);
1074 s.size_ordered = (st.sched == SchedStrategy::SJF ||
1075 st.sched == SchedStrategy::LJF ||
1076 st.sched == SchedStrategy::SRPT ||
1077 st.sched == SchedStrategy::SRPTPRIO ||
1078 st.sched == SchedStrategy::PSJF ||
1079 st.sched == SchedStrategy::LRPT ||
1080 st.sched == SchedStrategy::FSP);
1081 const double c = st.nservers;
1082 if (!(c >= 1.0) || !std::isfinite(c))
1083 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1084 "' has a server count that is neither finite nor at least one");
1085 s.nservers =
static_cast<std::size_t
>(c + 0.5);
1087 s.classcap.assign(K, std::numeric_limits<double>::infinity());
1088 s.blocked_at.assign(K, 0.0);
1090 for (std::size_t r = 0; r < K; ++r)
1091 if (i <
sn.droprule.size() && r <
sn.droprule[i].size())
1092 s.droprule[r] =
sn.droprule[i][r];
1093 for (std::size_t r = 0; r < K; ++r)
1094 if (i <
sn.classcap.size() && r <
sn.classcap[i].size())
1095 s.classcap[r] =
sn.classcap[i][r];
1096 if (st.sched == SchedStrategy::LPS) s.lps_limit = s.nservers;
1100 s.parallelism.assign(K, 1);
1101 for (std::size_t r = 0; r < K && r < st.server_parallelism.size(); ++r)
1102 if (st.server_parallelism[r] > 1) {
1103 s.parallelism[r] = st.server_parallelism[r];
1104 s.has_parallelism =
true;
1110 s.ps_cd.assign(K, 1.0);
1111 s.gd_peak.assign(K, 1.0);
1117 s.util_peak =
static_cast<double>(s.nservers);
1118 for (
double a : s.lld) s.util_peak = std::max(s.util_peak, a);
1126 if (st.cdscaling || st.jdscaling) {
1136 double dep_peak = 1.0;
1137 const std::vector<T>* pks[2] = {&st.cdscalingpeak, &st.jdscalingpeak};
1138 const bool on[2] = {
static_cast<bool>(st.cdscaling),
static_cast<bool>(st.jdscaling)};
1139 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
1140 for (std::size_t h = 0; h < 2; ++h) {
1141 if (!on[h])
continue;
1142 if (pks[h]->empty())
1144 std::string(
"SolverLDES: station '") + st.name +
1145 "' declares a dependent scaling with no declared peak rate. Utilization "
1146 "there is T*E[S]/peak, so pass the peak to " + names[h]);
1150 throw InputError(std::string(
"SolverLDES: station '") + st.name +
1151 "' declares a non-positive peak rate for " + names[h]);
1154 s.util_peak = dep_peak;
1155 const auto& beta = st.cdscaling;
1156 const auto& eta = st.jdscaling;
1157 s.cd = [beta, eta](
const std::vector<double>& n) {
1158 std::vector<T> nt(n.size());
1160 std::vector<double> bd, ed;
1162 const std::vector<T> b = beta(nt);
1163 bd.resize(b.size());
1167 const std::vector<T> e = eta(nt);
1168 ed.resize(e.size());
1171 if (bd.empty())
return ed;
1172 if (ed.empty())
return bd;
1175 const std::size_t n2 = std::max(bd.size(), ed.size());
1176 std::vector<double> out(n2, 1.0);
1177 for (std::size_t k = 0; k < n2; ++k)
1178 out[k] = bd[bd.size() > 1 ? k : 0] * ed[ed.size() > 1 ? k : 0];
1182 if (st.sched == SchedStrategy::PAS || st.sched == SchedStrategy::OI) {
1184 auto pp =
sn.pasparam.find(i + 1);
1185 if (pp !=
sn.pasparam.end()) {
1186 if (pp->second.svc_rate_fun) {
1187 const auto f = pp->second.svc_rate_fun;
1196 s.pas_rate = [f](
const std::vector<std::size_t>& seq) {
1197 std::vector<std::size_t> tags(seq.size());
1198 for (std::size_t k = 0; k < seq.size(); ++k) tags[k] = seq[k] + 1;
1202 s.pas_swap = pp->second.swap_graph;
1205 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1206 "' is a pass-and-swap station with no service rate function");
1208 if (st.sched == SchedStrategy::POLLING) {
1210 const auto pp =
sn.effective_polling(i + 1);
1211 s.poll_type = pp.ptype;
1212 s.poll_k = (pp.pk >= 1) ? pp.pk : 1;
1214 ? std::numeric_limits<std::size_t>::max()
1216 s.poll_parked =
true;
1217 s.switchover.resize(K);
1218 s.has_switchover.assign(K,
false);
1219 for (std::size_t r = 0; r < K && r < pp.switchover.size(); ++r)
1220 if (!pp.switchover[r].disabled &&
1222 s.switchover[r] = Sampler(pp.switchover[r],
1223 "the switchover of station '" + st.name +
1224 "', class '" +
sn.classes[r].name +
"'");
1225 s.has_switchover[r] =
true;
1228 s.retrial.resize(K);
1229 s.has_retrial.assign(K,
false);
1230 s.max_attempts.assign(K, 0);
1231 s.orbit_size.assign(K, 0.0);
1232 s.tot_orbit.assign(K, 0.0);
1233 s.retried.assign(K, 0.0);
1234 s.retrial_lost.assign(K, 0.0);
1236 auto rp =
sn.retrialparam.find(i + 1);
1237 if (rp !=
sn.retrialparam.end())
1238 for (std::size_t r = 0; r < K && r < rp->second.retrial_proc.size(); ++r)
1239 if (!rp->second.retrial_proc[r].disabled) {
1240 s.retrial[r] = Sampler(rp->second.retrial_proc[r],
1241 "the retrial process of station '" + st.name +
1242 "', class '" +
sn.classes[r].name +
"'");
1243 s.has_retrial[r] =
true;
1244 if (r < rp->second.max_attempts.size())
1245 s.max_attempts[r] = rp->second.max_attempts[r];
1248 s.patience.resize(K);
1249 s.has_patience.assign(K,
false);
1250 s.balk.assign(K, std::vector<StationState::BalkRule>());
1251 for (std::size_t r = 0; r < K; ++r) {
1253 r < st.patience.size() && !st.patience[r].disabled) {
1254 s.patience[r] = Sampler(st.patience[r],
"the patience of station '" + st.name +
1255 "', class '" +
sn.classes[r].name +
"'");
1256 s.has_patience[r] =
true;
1262 if (r < st.balking.size() &&
1264 for (
const auto& th : st.balking[r].thresholds) {
1265 StationState::BalkRule br;
1266 br.min_jobs = th.min_jobs;
1267 br.max_jobs = th.max_jobs;
1269 s.balk[r].push_back(br);
1275 auto sp =
sn.setupparam.find(i + 1);
1276 if (sp !=
sn.setupparam.end()) {
1278 if (sp->second.last(su, doff) && !su.
disabled) {
1280 s.setup_time = Sampler(su,
"the setup time of station '" + st.name +
"'");
1287 Sampler(doff,
"the delay-off time of station '" + st.name +
"'");
1289 s.delayoff_time = Sampler();
1294 typename std::map<std::size_t, qn::BreakdownParam<T> >::const_iterator bp =
1295 sn.breakdownparam.find(i + 1);
1296 if (bp !=
sn.breakdownparam.end()) {
1297 s.has_breakdown =
true;
1300 Sampler(bp->second.failure,
"the failure time of station '" + st.name +
"'");
1302 Sampler(bp->second.repair,
"the repair time of station '" + st.name +
"'");
1303 s.down_scale.assign(K, 0.0);
1304 s.down_rate_raw.assign(K, 0.0);
1305 for (std::size_t r = 0; r < K && r < bp->second.down_service_rates.size(); ++r)
1311 s.state_dependent = (!s.lld.empty() || s.has_cd || s.has_breakdown);
1314 if (s.role == Role::Synchronization)
continue;
1315 for (std::size_t r = 0; r < K; ++r) {
1316 s.off[r] =
sn.disabled[i][r];
1317 if (s.off[r])
continue;
1318 const std::string where =
"station '" + st.name +
"', class '" +
sn.classes[r].name +
"'";
1319 s.svc[r] = Sampler(
sn.service[i][r], where);
1320 s.class_mean[r] = s.svc[r].mean();
1321 if (!(s.class_mean[r] > 0.0))
1322 throw InputError(
"SolverLDES (native engine): " + where +
1323 " has a non-positive mean service time");
1335 if (s.role != Role::Source) {
1336 std::size_t nbatch = 0, enabled = 0, bcls = K;
1337 for (std::size_t r = 0; r < K; ++r) {
1338 if (s.off[r])
continue;
1347 const std::string at = fam +
" service (BMSP) at station '" + st.name +
"'";
1348 if (s.role == Role::Delay)
1350 " is not supported at an infinite-server station; "
1351 "batch service requires a single-server FCFS queue");
1352 if (s.nservers != 1)
1354 " requires a single server; multiserver bulk service "
1355 "is not supported");
1358 " requires FCFS scheduling");
1361 " requires exactly one job class; multiclass bulk "
1362 "service is not supported");
1363 if (s.pas || s.preemptive || s.polling)
1365 " is incompatible with PAS/preemptive/polling "
1369 " is incompatible with server setup/delayoff");
1370 if (s.state_dependent)
1372 " is not supported with load or class dependence or "
1373 "breakdowns: the rate rescaling cannot be applied to "
1374 "a firing clock armed at the instant the station "
1376 if (!st.server_types.empty())
1378 " is not supported with heterogeneous server types; a "
1379 "bulk server is a single server by construction");
1394 if (!st.server_types.empty()) {
1395 const std::size_t nT = st.server_types.size();
1397 s.hetero_policy = st.hetero_policy;
1398 s.type_count.assign(nT, 0);
1399 s.type_first.assign(nT, 0);
1400 s.type_compat.assign(nT, std::vector<bool>(K,
true));
1401 s.type_svc.assign(nT, std::vector<Sampler>(K));
1402 s.type_has_svc.assign(nT, std::vector<bool>(K,
false));
1403 s.type_rate.assign(nT, std::vector<double>(K, 0.0));
1404 std::size_t total = 0;
1405 for (std::size_t t = 0; t < nT; ++t) {
1407 const double c = pt.
count;
1409 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1410 "' declares server pool '" + pt.
name +
1411 "' with fewer than one server");
1412 s.type_first[t] = total;
1413 s.type_count[t] =
static_cast<std::size_t
>(c + 0.5);
1414 total += s.type_count[t];
1415 for (std::size_t r = 0; r < K; ++r) {
1421 if (s.off[r]) s.type_compat[t][r] =
false;
1423 const std::string where =
"station '" + st.name +
"', pool '" + pt.
name +
1424 "', class '" +
sn.classes[r].name +
"'";
1425 s.type_svc[t][r] = Sampler(pt.
service[r], where);
1426 s.type_has_svc[t][r] =
true;
1427 const double mu = s.type_svc[t][r].mean();
1429 throw InputError(
"SolverLDES (native engine): " + where +
1430 " has a non-positive mean service time");
1431 s.type_rate[t][r] = 1.0 / mu;
1432 }
else if (!s.off[r]) {
1434 (s.class_mean[r] > 0.0) ? 1.0 / s.class_mean[r] : 0.0;
1438 for (std::size_t r = 0; r < K; ++r) {
1439 if (s.off[r])
continue;
1440 bool served =
false;
1441 for (std::size_t t = 0; t < nT && !served; ++t) served = s.type_compat[t][r];
1443 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1444 "' declares no server pool compatible with class '" +
1445 sn.classes[r].name +
1446 "', so a job of that class would wait forever");
1452 s.util_peak =
static_cast<double>(total);
1454 s.server_type.assign(total, 0);
1455 for (std::size_t t = 0; t < nT; ++t)
1456 for (std::size_t j = 0; j < s.type_count[t]; ++j)
1457 s.server_type[s.type_first[t] + j] = t;
1458 s.type_order.resize(nT);
1459 for (std::size_t t = 0; t < nT; ++t) s.type_order[t] = t;
1462 s.alfs_order = s.type_order;
1463 std::stable_sort(s.alfs_order.begin(), s.alfs_order.end(),
1464 [&](std::size_t a, std::size_t b) {
1465 std::size_t ca = 0, cb = 0;
1466 for (std::size_t r = 0; r < K; ++r) {
1467 if (s.type_compat[a][r]) ++ca;
1468 if (s.type_compat[b][r]) ++cb;
1477 if (s.has_breakdown) {
1478 s.down_scale.assign(K, 0.0);
1479 for (std::size_t r = 0; r < K; ++r) {
1480 if (s.off[r] || !(s.class_mean[r] > 0.0))
continue;
1481 if (r < s.down_rate_raw.size() && s.down_rate_raw[r] > 0.0)
1482 s.down_scale[r] = s.down_rate_raw[r] * s.class_mean[r];
1486 s.cmp.sched = s.sched;
1487 s.cmp.class_mean = &s.class_mean;
1488 if (s.role == Role::Queue) {
1489 s.server.assign(s.nservers, Job());
1490 s.server_busy.assign(s.nservers,
false);
1491 s.server_start.assign(s.nservers, 0.0);
1492 s.server_tag.assign(s.nservers, 0);
1493 s.server_blocked.assign(s.nservers,
false);
1494 s.server_held.assign(s.nservers,
false);
1495 s.held_cls.assign(s.nservers, 0);
1496 s.blocked_job.assign(s.nservers, Job());
1497 s.blocked_dest.assign(s.nservers, 0);
1498 s.blocked_dest_cls.assign(s.nservers, 0);
1499 if (s.has_parallelism) s.held_extra.assign(s.nservers, std::vector<std::size_t>());
1515 for (std::size_t i = 0; i < M; ++i) {
1516 const auto& st =
sn.stations[i];
1517 std::size_t maxpar = 1;
1518 for (std::size_t r = 0; r < st.server_parallelism.size(); ++r)
1519 maxpar = std::max(maxpar, st.server_parallelism[r]);
1520 if (maxpar <= 1)
continue;
1521 StationState& s = S[i];
1522 const std::string where = std::string(
"SolverLDES (native engine): station '") +
1523 st.name +
"' declares server parallelism";
1524 if (s.role != Role::Queue)
1525 throw UnsupportedError(where +
" at an infinite server, which never makes a job "
1526 "wait for a slot; drop the declaration or give the "
1527 "station a finite server count");
1530 ", which shares its whole capacity rather than handing out "
1531 "slots, so a job cannot hold n of them; use a queueing "
1532 "discipline, or a class weight to slow the class down");
1533 if (s.polling || s.pas)
1535 ", whose controller picks the server itself and has no pool "
1536 "to seize n slots from");
1538 throw UnsupportedError(where +
" at a bulk (BMSP) server, which has only a "
1539 "station-level firing clock and no per-job server "
1542 throw UnsupportedError(where +
" together with heterogeneous server pools: a job is "
1543 "served at the law of the one pool it occupies, which "
1544 "n servers of mixed types cannot express");
1546 throw UnsupportedError(where +
" together with setup/delayoff: the slots of such a "
1547 "station power up one at a time, so 'n free at once' "
1548 "is not a state it passes through");
1549 if (maxpar > s.nservers)
1550 throw InputError(where +
" of " + std::to_string(maxpar) +
" above its " +
1551 std::to_string(s.nservers) +
" servers, so the job could never "
1568 const bool has_gd =
static_cast<bool>(sn.gdscaling);
1569 std::vector<std::vector<double> > gd_cache(M, std::vector<double>(K, 1.0));
1571 if (sn.gdscalingpeak.size() < M * K)
1573 "SolverLDES (native engine): a global dependence requires an explicit peak rate, "
1574 "because utilization under it is reported as T*E[S]/peak; pass one to "
1575 "setGlobalDependence");
1576 for (std::size_t i = 0; i < M; ++i) {
1577 StationState& s = S[i];
1578 const std::string& nm = sn.stations[i].name;
1581 "SolverLDES (native engine): station '" + nm +
1582 "' is a bulk (BMSP) server under a global dependence, which holds only a "
1583 "station-level firing clock and no per-job residual work to rescale");
1586 "SolverLDES (native engine): station '" + nm +
1587 "' is a pass-and-swap station under a global dependence, whose single "
1588 "aggregate clock is armed from its own list and carries no residual work "
1590 for (std::size_t r = 0; r < K; ++r) {
1591 const double pk = num_traits<T>::to_double(sn.gdscalingpeak[i * K + r]);
1592 if (!(pk > 0.0) || !std::isfinite(pk))
1593 throw InputError(
"SolverLDES (native engine): the global dependence declares "
1594 "a peak rate that is not finite and positive at station '" +
1602 if (s.role == Role::Queue) s.state_dependent =
true;
1612 const std::size_t nnodes = sn.nof_nodes();
1613 std::vector<std::size_t> node_to_station(nnodes + 1, M);
1614 for (std::size_t i = 0; i < M; ++i) node_to_station[sn.station_to_node[i]] = i;
1627 std::vector<std::vector<std::vector<RouteDest>>> nroute(
1628 nnodes + 1, std::vector<std::vector<RouteDest>>(K));
1637 std::vector<std::vector<lang::RoutingStrategy>> node_routing(
1639 std::vector<std::vector<std::size_t>> rr_counter(nnodes + 1, std::vector<std::size_t>(K, 0));
1640 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1641 for (std::size_t r = 0; r < K; ++r)
1642 if (sn.nodes[inode - 1].routing.size() > r)
1643 node_routing[inode][r] = sn.nodes[inode - 1].routing[r];
1644 for (std::size_t inode = 1; inode <= nnodes; ++inode) {
1651 if (sn.nodes[inode - 1].nodetype == NodeType::Sink)
continue;
1655 if (sn.nodes[inode - 1].nodetype == NodeType::Place ||
1656 sn.nodes[inode - 1].nodetype == NodeType::Transition)
1658 for (std::size_t r = 0; r < K; ++r) {
1661 for (std::size_t j = 1; j <= nnodes; ++j) {
1664 d.sink = (sn.nodes[j - 1].nodetype == NodeType::Sink);
1665 d.station = node_to_station[j];
1666 for (std::size_t s2 = 0; s2 < K; ++s2) {
1667 const double p = num_traits<T>::to_double(sn.route_eff(r + 1, s2 + 1, inode, j));
1668 if (!(p > 0.0))
continue;
1670 d.cls.push_back(std::make_pair(s2, p));
1672 if (d.cls.empty())
continue;
1679 if (!d.sink && d.station >= M && sn.nodes[j - 1].nodetype != NodeType::Fork &&
1680 sn.nodes[j - 1].nodetype != NodeType::Join &&
1681 sn.nodes[j - 1].nodetype != NodeType::Cache &&
1682 sn.nodes[j - 1].nodetype != NodeType::ClassSwitch &&
1683 sn.nodes[j - 1].nodetype != NodeType::Router &&
1684 sn.nodes[j - 1].nodetype != NodeType::Logger &&
1685 sn.nodes[j - 1].nodetype != NodeType::Place &&
1686 sn.nodes[j - 1].nodetype != NodeType::Transition)
1688 "SolverLDES (native engine): the routing crosses node '" +
1689 sn.nodes[j - 1].name +
1690 "', which the refresh did not fold into the station-to-station "
1692 if (d.station < M && S[d.station].role == Role::Source)
1693 throw InputError(
"SolverLDES (native engine): the routing sends a "
1694 "job back into the Source");
1695 nroute[inode][r].push_back(d);
1705 std::vector<int> join_of_fork(nnodes + 1, -1);
1706 std::vector<bool> is_fork(nnodes + 1,
false), is_join(nnodes + 1,
false);
1707 for (
const auto& fjp : sn.fj) {
1708 if (fjp.first > nnodes || fjp.second > nnodes)
continue;
1709 is_fork[fjp.first] =
true;
1710 is_join[fjp.second] =
true;
1711 join_of_fork[fjp.first] =
static_cast<int>(fjp.second);
1715 std::vector<bool> spawn_joins_at(K,
false);
1716 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1717 for (std::size_t r = 0; r < K; ++r)
1718 for (
const RouteDest& d : nroute[inode][r])
1719 if (d.node >= 1 && d.node <= nnodes &&
1720 sn.nodes[d.node - 1].nodetype == NodeType::Join)
1721 for (std::size_t c = 0; c < d.cls.size(); ++c)
1722 spawn_joins_at[d.cls[c].first] =
true;
1723 for (std::size_t nd = 1; nd <= nnodes; ++nd) {
1724 if (sn.nodes[nd - 1].nodetype == NodeType::Fork && !is_fork[nd])
1725 throw InputError(
"SolverLDES (native engine): Fork node '" + sn.nodes[nd - 1].name +
1726 "' has no matching Join");
1727 if (sn.nodes[nd - 1].nodetype == NodeType::Join && !is_join[nd])
1728 throw InputError(
"SolverLDES (native engine): Join node '" + sn.nodes[nd - 1].name +
1729 "' closes no Fork");
1741 std::size_t cls = 0;
1753 std::vector<std::pair<std::size_t, double>> siblings;
1755 std::map<std::uint64_t, ForkSync> fork_sync;
1756 std::uint64_t next_parent = 0;
1764 std::map<std::size_t, CacheState> caches;
1765 for (
const auto& np : sn.nodeparam) {
1766 const std::size_t nd = np.first;
1767 if (nd == 0 || nd > nnodes)
continue;
1768 if (sn.nodes[nd - 1].nodetype != NodeType::Cache)
continue;
1769 const auto& cp = np.second;
1772 cs.nitems = cp.nitems;
1773 cs.policy = cp.replacestrat;
1774 for (
int c : cp.itemcap)
1775 if (c > 0) cs.capacity.push_back(
static_cast<std::size_t
>(c));
1776 if (cs.capacity.empty()) cs.capacity.push_back(1);
1777 cs.lists.assign(cs.capacity.size(), std::list<std::size_t>());
1778 for (
int v : cp.itemsize) cs.item_size.push_back(v);
1779 for (
int v : cp.costcap) cs.cost_cap.push_back(v);
1780 cs.hits.assign(K, 0.0);
1781 cs.misses.assign(K, 0.0);
1782 cs.hit_class.assign(K, -1);
1783 cs.miss_class.assign(K, -1);
1784 for (std::size_t r = 0; r < K && r < cp.hitclass.size(); ++r)
1785 if (cp.hitclass[r] > 0) cs.hit_class[r] =
static_cast<int>(cp.hitclass[r] - 1);
1786 for (std::size_t r = 0; r < K && r < cp.missclass.size(); ++r)
1787 if (cp.missclass[r] > 0) cs.miss_class[r] =
static_cast<int>(cp.missclass[r] - 1);
1790 cs.popularity.assign(K, std::vector<double>());
1791 for (std::size_t r = 0; r < K && r < cp.pread.size(); ++r) {
1793 for (
const T& v : cp.pread[r]) {
1794 acc += num_traits<T>::to_double(v);
1795 cs.popularity[r].push_back(acc);
1797 if (!cs.popularity[r].empty()) cs.popularity[r].back() = 1.0;
1801 if (cp.retrieval_capacity > 0 && !cp.retrieval_classes.empty()) {
1802 cs.has_retrieval =
true;
1803 cs.retrieval_class = cp.retrieval_classes;
1804 cs.retrieval_class_to_item.assign(K, -1);
1805 for (std::size_t it = 0; it < cs.retrieval_class.size(); ++it)
1806 for (std::size_t r = 0; r < cs.retrieval_class[it].size(); ++r) {
1807 const std::size_t rc = cs.retrieval_class[it][r];
1808 if (rc > 0 && rc <= K) cs.retrieval_class_to_item[rc - 1] =
static_cast<int>(it);
1810 cs.in_flight.assign(cs.nitems, 0);
1811 cs.fetch_start.assign(cs.nitems, 0.0);
1812 cs.held.assign(cs.nitems, std::vector<CacheState::HeldRequest>());
1813 cs.delayed.assign(K, 0.0);
1823 std::vector<std::size_t> place_of(nnodes + 1, M);
1824 std::vector<std::size_t> place_nodes;
1825 for (std::size_t nd = 1; nd <= nnodes; ++nd)
1826 if (sn.nodes[nd - 1].nodetype == NodeType::Place) {
1827 place_of[nd] = place_nodes.size();
1828 place_nodes.push_back(nd);
1830 std::vector<std::size_t> place_station(place_nodes.size(), M);
1831 for (std::size_t p = 0; p < place_nodes.size(); ++p)
1832 place_station[p] = node_to_station[place_nodes[p]];
1845 std::vector<std::vector<double>> marking(place_nodes.size(), std::vector<double>(K, 0.0));
1846 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1847 auto im = sn.initmarking.find(place_nodes[p]);
1848 if (im != sn.initmarking.end())
1849 for (std::size_t r = 0; r < K && r < im->second.size(); ++r)
1850 marking[p][r] = num_traits<T>::to_double(im->second[r]);
1851 for (std::size_t r = 0; r < K; ++r) {
1853 const double n = sn.classes[r].population;
1854 if (!std::isfinite(n) || !(n > 0.0))
continue;
1855 if (sn.classes[r].refstat != place_station[p] + 1)
continue;
1856 marking[p][r] = std::max(marking[p][r], n);
1878 std::vector<std::vector<double>> avail(place_nodes.size(), std::vector<double>(K, 0.0));
1879 std::vector<bool> qplace(place_nodes.size(),
false);
1880 std::vector<std::size_t> place_servers(place_nodes.size(), 1);
1881 std::vector<std::deque<std::size_t>> place_wait(place_nodes.size());
1882 std::vector<std::size_t> place_busy(place_nodes.size(), 0);
1883 std::vector<std::vector<double>> place_insvc(place_nodes.size(), std::vector<double>(K, 0.0));
1884 std::vector<std::vector<Sampler>> place_svc(place_nodes.size(), std::vector<Sampler>(K));
1896 std::vector<std::vector<Rng>> g_place(place_nodes.size());
1897 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1898 const std::size_t ist = place_station[p];
1899 g_place[p].reserve(K);
1900 for (std::size_t k = 0; k < K; ++k) {
1901 const long long off =
1902 ((nsrc + nsvc +
static_cast<long long>(p)) * Kll +
static_cast<long long>(k)) *
1904 g_place[p].push_back(Rng(seed_ll, off));
1906 if (ist >= M)
continue;
1907 qplace[p] = sn.is_queueing_place(ist);
1909 for (std::size_t r = 0; r < K; ++r) avail[p][r] = marking[p][r];
1912 const auto& pst = sn.stations[ist];
1918 if (pst.sched != SchedStrategy::FCFS && pst.sched != SchedStrategy::LCFS &&
1919 pst.sched != SchedStrategy::SIRO && pst.sched != SchedStrategy::INF)
1921 "SolverLDES (native engine): queueing place '" + pst.name +
1923 ", which the embedded-queue algorithm does not support "
1924 "(supported: FCFS, LCFS, SIRO, INF)");
1925 place_servers[p] = (pst.sched == SchedStrategy::INF || !std::isfinite(pst.nservers))
1926 ? std::numeric_limits<std::size_t>::max()
1927 :
static_cast<std::size_t
>(pst.nservers + 0.5);
1928 for (std::size_t r = 0; r < K; ++r) {
1929 if (sn.service[ist][r].disabled)
continue;
1931 Sampler(sn.service[ist][r],
1932 "queueing place '" + pst.name +
"', class '" + sn.classes[r].name +
"'");
1938 for (std::size_t k = 0; k < K; ++k)
1939 for (
double t = 0.0; t + 0.5 < marking[p][k]; t += 1.0) place_wait[p].push_back(k);
1942 std::vector<SpnTransition> transitions;
1943 for (
const auto& tp : sn.transparam) {
1944 const std::size_t nd = tp.first;
1945 if (nd == 0 || nd > nnodes)
continue;
1948 tr.places = place_nodes;
1949 for (std::size_t m = 0; m < tp.second.nmodes; ++m) {
1953 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1954 const std::size_t pn = place_nodes[p];
1955 for (std::size_t r = 0; r < K; ++r) {
1956 mode.enabling.push_back(
1957 (m < tp.second.enabling.size() && pn - 1 < tp.second.enabling[m].rows() &&
1958 r < tp.second.enabling[m].cols())
1959 ? num_traits<T>::to_double(tp.second.enabling[m](pn - 1, r))
1961 mode.inhibiting.push_back(
1962 (m < tp.second.inhibiting.size() &&
1963 pn - 1 < tp.second.inhibiting[m].rows() &&
1964 r < tp.second.inhibiting[m].cols())
1965 ? num_traits<T>::to_double(tp.second.inhibiting[m](pn - 1, r))
1966 : std::numeric_limits<double>::infinity());
1967 mode.firing.push_back(
1968 (m < tp.second.firing.size() && pn - 1 < tp.second.firing[m].rows() &&
1969 r < tp.second.firing[m].cols())
1970 ? num_traits<T>::to_double(tp.second.firing[m](pn - 1, r))
1974 if (m < tp.second.nmodeservers.size()) mode.servers = tp.second.nmodeservers[m];
1975 if (m < tp.second.firingprio.size()) mode.priority = tp.second.firingprio[m];
1976 if (m < tp.second.fireweight.size())
1977 mode.weight = num_traits<T>::to_double(tp.second.fireweight[m]);
1978 mode.immediate = (m < tp.second.timing.size() &&
1980 if (m < tp.second.firingproc.size() && !tp.second.firingproc[m].disabled) {
1981 const double mean = num_traits<T>::to_double(tp.second.firingproc[m].mean);
1982 if (mean > 0.0) mode.rate = 1.0 / mean;
1984 if (m < tp.second.firingdep.size() && tp.second.firingdep[m]) {
1985 const auto f = tp.second.firingdep[m];
1986 const std::vector<std::size_t> pl = place_nodes;
1987 const std::size_t nn = nnodes, KK = K;
1993 mode.dep = [f, pl, nn, KK](
const std::vector<double>& tok) {
1994 std::vector<T> mk(nn, num_traits<T>::from_int(0));
1995 for (std::size_t p = 0; p < pl.size(); ++p) {
1997 for (std::size_t r = 0; r < KK; ++r)
1998 if (p * KK + r < tok.size()) s += tok[p * KK + r];
1999 if (pl[p] >= 1 && pl[p] - 1 < nn)
2000 mk[pl[p] - 1] = num_traits<T>::from_double(s);
2002 return num_traits<T>::to_double(f(mk));
2005 tr.modes.push_back(mode);
2007 tr.fired.assign(tr.modes.size(), 0.0);
2008 transitions.push_back(tr);
2013 for (std::size_t i = 0; i < M; ++i) acc.util_peak[i] = S[i].util_peak;
2018 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
2019 if (place_station[p] >= M)
continue;
2020 for (std::size_t r = 0; r < K; ++r) acc.qlen[place_station[p]][r] = marking[p][r];
2025 std::vector<double> sys_resp_sum(K, 0.0), sys_resp_cnt(K, 0.0), sys_completed(K, 0.0);
2028 std::vector<std::vector<double>> dropped(M, std::vector<double>(K, 0.0));
2029 std::vector<std::vector<double>> balked(M, std::vector<double>(K, 0.0));
2030 std::vector<std::vector<double>> reneged(M, std::vector<double>(K, 0.0));
2031 std::vector<std::vector<double>> blocked_count(M, std::vector<double>(K, 0.0));
2040 const bool want_respt = o.export_respt || o.export_trajectory;
2041 std::vector<std::vector<std::vector<double>>> resp_samples(
2042 want_respt ? M : 0, std::vector<std::vector<double>>(K));
2043 std::uint64_t job_id = 0;
2056 std::vector<bool> is_removal_signal(K,
false);
2057 bool has_removal_signal =
false;
2058 for (std::size_t r = 0; r < K; ++r) {
2059 if (r >= sn.issignal.size() || !sn.issignal[r])
continue;
2063 is_removal_signal[r] =
true;
2064 has_removal_signal =
true;
2083 const bool track_delay_jobs = has_removal_signal || has_gd;
2084 std::vector<std::vector<Job>> delay_live(track_delay_jobs ? M : 0);
2095 std::vector<std::size_t> sync_reply(K, K);
2096 std::vector<bool> is_reply_signal(K,
false);
2097 bool has_sync_call =
false;
2098 for (std::size_t r = 0; r < K; ++r) {
2099 if (r < sn.syncreply.size() && sn.syncreply[r] >= 1 && sn.syncreply[r] <= K) {
2100 sync_reply[r] = sn.syncreply[r] - 1;
2101 has_sync_call =
true;
2103 if (r < sn.issignal.size() && sn.issignal[r] && r < sn.signaltype.size() &&
2105 is_reply_signal[r] =
true;
2108 struct PendingCall {
2109 std::size_t station = 0;
2110 std::size_t slot = 0;
2111 std::size_t cls = 0;
2114 std::map<std::uint64_t, PendingCall> pending_reply;
2115 std::uint64_t next_call = 0;
2118 bool has_immfeed =
false;
2119 for (std::size_t i = 0; i < M && i < sn.immfeed.size(); ++i)
2120 for (std::size_t r = 0; r < sn.immfeed[i].size(); ++r)
2121 if (sn.immfeed[i][r]) has_immfeed =
true;
2131 std::vector<std::size_t> spawn_of(K, K);
2132 bool has_spawn =
false;
2133 for (std::size_t r = 0; r < K; ++r)
2134 if (sn.classes[r].spawn >= 1 && sn.classes[r].spawn <= K) {
2135 spawn_of[r] = sn.classes[r].spawn - 1;
2140 std::priority_queue<Event, std::vector<Event>, EventLater> evq;
2141 std::uint64_t seq = 0, ps_tag = 0;
2143 auto push = [&](Event e) {
2161 std::function<void(std::size_t)> place_try_start = [&](std::size_t p) {
2162 const std::size_t ist = place_station[p];
2163 if (ist >= M)
return;
2164 while (place_busy[p] < place_servers[p] && !place_wait[p].empty()) {
2166 if (S[ist].sched == SchedStrategy::LCFS) {
2167 r = place_wait[p].back();
2168 place_wait[p].pop_back();
2169 }
else if (S[ist].sched == SchedStrategy::SIRO) {
2170 const std::size_t at = std::min(
2171 place_wait[p].size() - 1,
2172 static_cast<std::size_t
>(uniform01(g_qplace) *
2173 static_cast<double>(place_wait[p].size())));
2174 r = place_wait[p][at];
2175 place_wait[p].erase(place_wait[p].begin() +
static_cast<std::ptrdiff_t
>(at));
2177 r = place_wait[p].front();
2178 place_wait[p].pop_front();
2185 if (place_svc[p][r].disabled())
2186 throw InputError(
"SolverLDES (native engine): a token of class '" +
2187 sn.classes[r].name +
"' entered queueing place '" +
2188 sn.stations[ist].name +
2189 "', which has no service process for that class; call "
2190 "setService for every token colour of a queueing place");
2191 acc.update_busy(ist, r, now);
2193 place_insvc[p][r] += 1.0;
2194 acc.busy[ist][r] = place_insvc[p][r];
2196 e.t = now + place_svc[p][r].next(g_place[p][r]);
2205 auto place_deposit = [&](std::size_t p, std::size_t r,
double n) {
2206 const std::size_t ist = place_station[p];
2208 acc.update_qlen(ist, r, now);
2210 acc.qlen[ist][r] = marking[p][r];
2211 acc.arrived[ist][r] += n;
2219 for (
double t = 0.0; t + 0.5 < n; t += 1.0) place_wait[p].push_back(r);
2231 auto place_take = [&](std::size_t p, std::size_t r,
double n) {
2232 const std::size_t ist = place_station[p];
2236 acc.update_qlen(ist, r, now);
2237 acc.qlen[ist][r] = marking[p][r];
2238 acc.completed[ist][r] += n;
2251 auto buffer_push = [&](std::size_t i,
const Job& job) {
2252 StationState& s = S[i];
2253 s.buffer.push_back(job);
2254 if (s.sched != SchedStrategy::FSP)
2255 std::push_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
2257 auto buffer_pop = [&](std::size_t i) -> Job {
2258 StationState& s = S[i];
2259 if (s.sched != SchedStrategy::FSP) {
2260 std::pop_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
2261 Job job = s.buffer.back();
2262 s.buffer.pop_back();
2265 std::vector<double> works;
2266 for (
const Job& j : s.buffer) works.push_back(j.remaining);
2267 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2268 if (s.server_busy[sl])
2269 works.push_back(std::max(0.0, s.server[sl].remaining -
2270 (now - s.server_start[sl])));
2271 std::size_t best = 0;
2272 double best_vft = std::numeric_limits<double>::infinity();
2273 for (std::size_t j = 0; j < s.buffer.size(); ++j) {
2275 static_cast<double>(s.nservers), now);
2281 Job job = s.buffer[best];
2282 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(best));
2297 auto slot_ok_for = [&](
const StationState& s, std::size_t sl, std::size_t cls) ->
bool {
2298 if (s.server_busy[sl] || s.server_blocked[sl] || s.server_held[sl])
return false;
2299 if (!s.has_pools)
return true;
2300 return s.type_compat[s.server_type[sl]][cls];
2304 auto free_slot_count = [&](std::size_t i) -> std::size_t {
2305 const StationState& s = S[i];
2307 for (std::size_t sl = 0; sl < s.server_busy.size(); ++sl)
2308 if (!s.server_busy[sl] && !s.server_blocked[sl] && !s.server_held[sl]) ++n;
2320 auto free_slot_for = [&](std::size_t i, std::size_t cls) -> std::size_t {
2321 StationState& s = S[i];
2325 if (s.has_parallelism && free_slot_count(i) < s.parallelism[cls])
return s.nservers;
2327 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2328 if (slot_ok_for(s, sl, cls))
return sl;
2332 std::vector<std::size_t> cand;
2333 for (std::size_t t = 0; t < s.type_count.size(); ++t) {
2334 if (!s.type_compat[t][cls])
continue;
2335 for (std::size_t j = 0; j < s.type_count[t]; ++j)
2336 if (slot_ok_for(s, s.type_first[t] + j, cls)) {
2341 if (cand.empty())
return s.nservers;
2342 std::size_t chosen = cand[0];
2343 if (cand.size() > 1) {
2344 switch (s.hetero_policy) {
2350 for (std::size_t k = 0; k < s.type_order.size(); ++k) {
2351 const std::size_t t = s.type_order[k];
2352 if (std::find(cand.begin(), cand.end(), t) == cand.end())
continue;
2354 s.type_order.erase(s.type_order.begin() +
2355 static_cast<std::ptrdiff_t
>(k));
2356 s.type_order.push_back(t);
2364 for (std::size_t k = 0; k < s.alfs_order.size(); ++k)
2365 if (std::find(cand.begin(), cand.end(), s.alfs_order[k]) != cand.end()) {
2366 chosen = s.alfs_order[k];
2374 for (std::size_t k = 0; k < cand.size(); ++k) {
2375 const double rt = s.type_rate[cand[k]][cls];
2386 std::size_t at =
static_cast<std::size_t
>(
2387 uniform01(g_routing) *
static_cast<double>(cand.size()));
2388 if (at >= cand.size()) at = cand.size() - 1;
2400 for (std::size_t j = 0; j < s.type_count[chosen]; ++j) {
2401 const std::size_t sl = s.type_first[chosen] + j;
2402 if (slot_ok_for(s, sl, cls))
return sl;
2408 auto buffer_has_for_slot = [&](std::size_t i, std::size_t sl) ->
bool {
2409 StationState& s = S[i];
2410 if (s.buffer.empty())
return false;
2411 if (!s.has_pools)
return true;
2412 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
2413 for (std::size_t j = 0; j < s.buffer.size(); ++j)
2414 if (ok[s.buffer[j].cls])
return true;
2429 auto buffer_pop_for_slot = [&](std::size_t i, std::size_t sl) -> Job {
2430 StationState& s = S[i];
2431 if (!s.has_pools)
return buffer_pop(i);
2432 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
2433 std::vector<Job> skipped;
2436 while (!s.buffer.empty()) {
2437 Job cand = buffer_pop(i);
2443 skipped.push_back(cand);
2445 for (std::size_t j = 0; j < skipped.size(); ++j) buffer_push(i, skipped[j]);
2447 throw InputError(
"SolverLDES (native engine): buffer_pop_for_slot was asked for a "
2448 "job no pool of this slot can serve; guard with buffer_has_for_slot");
2471 auto lld_factor = [&](std::size_t i) ->
double {
2472 StationState& s = S[i];
2473 if (s.lld.empty() || s.ps)
return 1.0;
2475 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
2476 if (!(total > 0.0))
return 1.0;
2477 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
2478 return s.lld[idx] > 0.0 ? s.lld[idx] : 1.0;
2488 auto cd_factor = [&](std::size_t i, std::size_t r) ->
double {
2489 StationState& s = S[i];
2490 if (!s.has_cd)
return 1.0;
2491 std::vector<double> nvec(K, 0.0);
2492 for (std::size_t k = 0; k < K; ++k) nvec[k] = acc.qlen[i][k];
2493 const std::vector<double> beta = s.cd(nvec);
2494 const double b = (beta.size() > 1) ? beta[r] : (beta.empty() ? 1.0 : beta[0]);
2495 return (b > 1e-10) ? b : 1.0;
2506 auto gd_refresh = [&]() {
2507 std::vector<T> nvec(M * K);
2508 for (std::size_t i = 0; i < M; ++i)
2509 for (std::size_t r = 0; r < K; ++r)
2510 nvec[i * K + r] = num_traits<T>::from_double(acc.qlen[i][r]);
2511 const std::vector<T> v = sn.gdscaling(nvec);
2513 throw InputError(
"SolverLDES (native engine): the global dependence handle returned "
2515 for (std::size_t i = 0; i < M; ++i)
2516 for (std::size_t r = 0; r < K; ++r) {
2519 const double f = num_traits<T>::to_double(
2520 (v.size() == 1) ? v[0] : ((v.size() == M) ? v[i] : v[i * K + r]));
2521 if (!std::isfinite(f) || f < 0.0)
2522 throw InputError(
"SolverLDES (native engine): the global dependence handle "
2523 "returned a scaling that is not finite and nonnegative at "
2524 "station '" + sn.stations[i].name +
"'");
2525 if (!(f > 0.0) && acc.qlen[i][r] > 0.0)
2527 "SolverLDES (native engine): the global dependence handle returned a "
2528 "zero scaling at station '" + sn.stations[i].name +
2529 "', which holds jobs of class '" + sn.classes[r].name +
2530 "'. A station that stops while it holds work has no completion left to "
2531 "schedule; use Queue.setBreakdown for a server that genuinely stops");
2536 auto rate_scaling = [&](std::size_t i, std::size_t r) ->
double {
2537 StationState& s = S[i];
2541 if (has_gd) factor *= gd_cache[i][r];
2546 if (s.has_breakdown && !s.up)
2547 factor *= (r < s.down_scale.size() ? s.down_scale[r] : 0.0);
2557 auto ps_servers = [&](std::size_t i) ->
double {
2558 StationState& s = S[i];
2559 if (s.lld.empty())
return static_cast<double>(s.nservers);
2561 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
2562 if (!(total > 0.0))
return 1.0;
2563 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
2573 auto ps_advance_one = [&](std::size_t i) {
2574 StationState& s = S[i];
2575 const double dt = now - s.ps_last_update;
2576 s.ps_last_update = now;
2584 for (std::size_t r = 0; r < K; ++r) acc.last_busy[i][r] = now;
2585 if (!(dt > 0.0) || s.ps_jobs.empty())
return;
2586 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
2588 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2589 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
2591 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2592 rates[j] *= gd_cache[i][s.ps_jobs[j].cls];
2593 if (s.has_breakdown && !s.up)
2594 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2595 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
2597 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
2598 if (rates[j] > 0.0) {
2599 acc.tot_busy[i][s.ps_jobs[j].cls] += rates[j] * dt;
2600 s.ps_jobs[j].remaining = std::max(0.0, s.ps_jobs[j].remaining - rates[j] * dt);
2603 auto ps_reschedule_one = [&](std::size_t i) {
2604 StationState& s = S[i];
2608 for (std::size_t r = 0; r < K; ++r) s.ps_cd[r] =
cd_factor(i, r);
2609 if (s.ps_jobs.empty())
return;
2610 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
2612 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2613 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
2615 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2616 rates[j] *= gd_cache[i][s.ps_jobs[j].cls];
2617 if (s.has_breakdown && !s.up)
2618 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2619 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
2621 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j) {
2622 PsJob& pj = s.ps_jobs[j];
2625 const double rate = rates[j];
2626 if (!(rate > 0.0))
continue;
2628 e.t = now + ((pj.remaining <= 1e-12) ? 1e-12 : pj.remaining / rate);
2647 auto sd_advance_one = [&](std::size_t i) {
2648 StationState& s = S[i];
2649 if (!s.state_dependent || s.ps)
return;
2650 const double dt = now - s.sd_last_update;
2651 s.sd_last_update = now;
2652 if (!(dt > 0.0))
return;
2653 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2654 if (s.server_busy[sl]) {
2655 const double scale = rate_scaling(i, s.server[sl].cls);
2656 s.server[sl].remaining = std::max(0.0, s.server[sl].remaining - dt * scale);
2657 s.server[sl].elapsed += dt * scale;
2658 s.server_start[sl] = now;
2661 auto sd_reschedule_one = [&](std::size_t i) {
2662 StationState& s = S[i];
2663 if (!s.state_dependent || s.ps)
return;
2669 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2670 if (s.server_busy[sl]) {
2671 const double scale = rate_scaling(i, s.server[sl].cls);
2681 s.server_tag[sl] = ++ps_tag;
2682 if (!(scale > 0.0))
continue;
2684 e.t = now + s.server[sl].remaining / scale;
2687 e.cls = s.server[sl].cls;
2689 e.tag = s.server_tag[sl];
2704 auto gd_delay_advance = [&](std::size_t i) {
2705 StationState& s = S[i];
2706 const double dt = now - s.sd_last_update;
2707 s.sd_last_update = now;
2708 if (!(dt > 0.0))
return;
2709 for (std::size_t j = 0; j < delay_live[i].size(); ++j) {
2710 Job& d = delay_live[i][j];
2711 const double scale = gd_cache[i][d.cls];
2712 d.remaining = std::max(0.0, d.remaining - dt * scale);
2713 d.elapsed += dt * scale;
2716 auto gd_delay_reschedule = [&](std::size_t i) {
2717 for (std::size_t j = 0; j < delay_live[i].size(); ++j) {
2718 Job& d = delay_live[i][j];
2722 const double scale = gd_cache[i][d.cls];
2723 if (!(scale > 0.0))
continue;
2725 e.t = now + ((d.remaining <= 1e-12) ? 1e-12 : d.remaining / scale);
2729 e.slot = std::numeric_limits<std::size_t>::max();
2749 auto gd_advance_all = [&]() {
2750 for (std::size_t a = 0; a < M; ++a) {
2751 if (S[a].role == Role::Delay)
2752 gd_delay_advance(a);
2753 else if (S[a].role != Role::Queue)
2761 auto gd_reschedule_all = [&]() {
2763 for (std::size_t a = 0; a < M; ++a) {
2764 if (S[a].role == Role::Delay)
2765 gd_delay_reschedule(a);
2766 else if (S[a].role != Role::Queue)
2769 ps_reschedule_one(a);
2771 sd_reschedule_one(a);
2774 auto ps_advance = [&](std::size_t i) {
2775 if (has_gd) gd_advance_all();
else ps_advance_one(i);
2777 auto ps_reschedule = [&](std::size_t i) {
2778 if (has_gd) gd_reschedule_all();
else ps_reschedule_one(i);
2780 auto sd_advance = [&](std::size_t i) {
2781 if (has_gd) gd_advance_all();
else sd_advance_one(i);
2783 auto sd_reschedule = [&](std::size_t i) {
2784 if (has_gd) gd_reschedule_all();
else sd_reschedule_one(i);
2800 auto dest_has_room = [&](std::size_t j, std::size_t r) ->
bool {
2801 StationState& d = S[j];
2802 if (d.role != Role::Queue)
return true;
2804 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[j][k] - d.blocked_at[k];
2805 return (total + 1.0 <= d.cap) &&
2806 (acc.qlen[j][r] - d.blocked_at[r] + 1.0 <= d.classcap[r]);
2811 return (S[j].role == Role::Queue && r < S[j].droprule.size()) ? S[j].droprule[r]
2819 std::vector<Region> regions;
2820 std::vector<int> region_of(M, -1);
2821 for (
const auto& rg : sn.regions) {
2824 R.members.assign(M,
false);
2825 R.class_cap.assign(K, -1.0);
2826 R.class_size.assign(K, 1.0);
2827 R.class_weight.assign(K, 1.0);
2829 R.jobs.assign(K, 0.0);
2830 R.blocked.assign(K, 0.0);
2831 R.dropped.assign(K, 0.0);
2832 R.tot_jobs.assign(K, 0.0);
2833 R.tot_weight.assign(K, 0.0);
2834 R.tot_mem.assign(K, 0.0);
2835 R.completed.assign(K, 0.0);
2836 R.resp_sum.assign(K, 0.0);
2837 R.resp_cnt.assign(K, 0.0);
2838 for (std::size_t i = 0; i < M && i < rg.members.size(); ++i)
2839 if (rg.members[i]) {
2840 R.members[i] =
true;
2841 if (region_of[i] >= 0)
2843 "SolverLDES (native engine): station '" + sn.stations[i].name +
2844 "' belongs to more than one finite capacity region");
2845 region_of[i] =
static_cast<int>(regions.size());
2850 for (std::size_t i = 0; i < rg.cap.size(); ++i) {
2851 if (i >= M || !R.members[i])
continue;
2852 for (std::size_t r = 0; r < K && r < rg.cap[i].size(); ++r)
2853 if (rg.cap[i][r] >= 0.0)
2854 R.class_cap[r] = (R.class_cap[r] < 0.0) ? rg.cap[i][r]
2855 : std::min(R.class_cap[r], rg.cap[i][r]);
2856 if (rg.cap[i].size() > K && rg.cap[i][K] >= 0.0)
2857 R.global_cap = (R.global_cap < 0.0) ? rg.cap[i][K]
2858 : std::min(R.global_cap, rg.cap[i][K]);
2860 for (
double mm : rg.maxmem)
2861 if (mm >= 0.0) R.max_mem = (R.max_mem < 0.0) ? mm : std::min(R.max_mem, mm);
2862 for (std::size_t r = 0; r < K && r < rg.rule.size(); ++r) R.rule[r] = rg.rule[r];
2863 for (std::size_t r = 0; r < K && r < rg.size.size(); ++r)
2864 R.class_size[r] = num_traits<T>::to_double(rg.size[r]);
2865 for (std::size_t r = 0; r < K && r < rg.weight.size(); ++r)
2866 R.class_weight[r] = num_traits<T>::to_double(rg.weight[r]);
2867 for (std::size_t c = 0; c < rg.lincon_A.rows(); ++c) {
2868 std::vector<double> row;
2869 for (std::size_t r = 0; r < rg.lincon_A.cols(); ++r)
2870 row.push_back(num_traits<T>::to_double(rg.lincon_A(c, r)));
2871 R.lincon_A.push_back(row);
2872 R.lincon_b.push_back(c < rg.lincon_b.size() ? num_traits<T>::to_double(rg.lincon_b[c])
2875 regions.push_back(R);
2883 struct RegionWaiter {
2884 std::size_t station = 0;
2887 std::vector<std::vector<RegionWaiter>> region_wait(regions.size());
2903 auto draw_at_arrival = [&](std::size_t i, std::size_t r) ->
bool {
2904 const StationState& s = S[i];
2909 if (s.bmsp)
return false;
2910 if (s.role != Role::Queue || s.ps || s.size_ordered)
return true;
2911 return !s.svc[r].time_varying();
2929 auto bmsp_arm = [&](std::size_t i) {
2930 StationState& s = S[i];
2931 const std::size_t r = s.bmsp_cls;
2932 const double gap = slot_snap(s.svc[r].next_at(g_svc[i][r], now),
"firing interval");
2933 const int b = s.svc[r].last_batch();
2934 s.bmsp_pending = (b > 0) ?
static_cast<std::size_t
>(b) : 1;
2935 if (!(gap > 0.0) && s.svc[r].time_varying())
return;
2947 auto start_service = [&](std::size_t i, std::size_t slot,
const Job& job_in) {
2948 StationState& s = S[i];
2958 if (!draw_at_arrival(i, job.cls)) {
2959 job.service = slot_snap(s.svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2960 job.remaining = job.service;
2966 if (!drew && s.preemptive && !s.resume && job.elapsed > 0.0) {
2967 job.service = slot_snap(s.svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2968 job.remaining = job.service;
2978 if (s.has_pools && !(s.preemptive && s.resume && job.elapsed > 0.0)) {
2979 const std::size_t ty = s.server_type[slot];
2980 if (s.type_has_svc[ty][job.cls]) {
2981 job.service = slot_snap(
2982 s.type_svc[ty][job.cls].next_at(g_hsvc[i][ty][job.cls], now),
2984 job.remaining = job.service;
2988 s.server[slot] = job;
2989 s.server_busy[slot] =
true;
2990 s.server_start[slot] = now;
2991 s.server_tag[slot] = ++ps_tag;
2997 double occupied = 1.0;
2998 if (s.has_parallelism && s.parallelism[job.cls] > 1) {
2999 const std::size_t need = s.parallelism[job.cls];
3000 std::vector<std::size_t> extras;
3001 for (std::size_t sl = 0; sl < s.nservers && extras.size() + 1 < need; ++sl) {
3002 if (sl == slot || s.server_busy[sl] || s.server_blocked[sl] || s.server_held[sl])
3004 s.server_busy[sl] =
true;
3005 extras.push_back(sl);
3007 if (extras.size() + 1 < need)
3008 throw InputError(
"SolverLDES (native engine): station '" + sn.stations[i].name +
3009 "' started a job holding " + std::to_string(need) +
3010 " servers with only " + std::to_string(extras.size() + 1) +
3012 s.held_extra[slot] = extras;
3013 occupied =
static_cast<double>(need);
3015 acc.update_busy(i, job.cls, now);
3018 acc.busy[i][job.cls] += occupied;
3025 const double scale0 = rate_scaling(i, job.cls);
3026 if (!(scale0 > 0.0))
return;
3028 e.t = now + job.remaining / scale0;
3033 e.tag = s.server_tag[slot];
3046 auto release_slots = [&](std::size_t i, std::size_t slot) ->
double {
3047 StationState& s = S[i];
3048 if (!s.has_parallelism || slot >= s.held_extra.size())
return 1.0;
3049 std::vector<std::size_t>& extras = s.held_extra[slot];
3050 if (extras.empty())
return 1.0;
3051 for (std::size_t j = 0; j < extras.size(); ++j) s.server_busy[extras[j]] =
false;
3052 const double n =
static_cast<double>(extras.size() + 1);
3065 auto serve_next_on_slot = [&](std::size_t i, std::size_t sl) {
3066 if (!buffer_has_for_slot(i, sl))
return;
3067 Job nextjob = buffer_pop_for_slot(i, sl);
3068 if (S[i].has_parallelism && free_slot_count(i) < S[i].parallelism[nextjob.cls]) {
3069 buffer_push(i, nextjob);
3072 start_service(i, sl, nextjob);
3082 auto preempt = [&](std::size_t i, std::size_t slot) {
3083 StationState& s = S[i];
3084 Job job = s.server[slot];
3085 const double served = now - s.server_start[slot];
3086 job.remaining = std::max(0.0, job.remaining - served);
3087 job.elapsed += served;
3088 s.server_busy[slot] =
false;
3089 s.server_tag[slot] = 0;
3090 const double freed = release_slots(i, slot);
3091 acc.update_busy(i, job.cls, now);
3092 acc.busy[i][job.cls] -= freed;
3095 buffer_push(i, job);
3106 std::function<void(std::size_t)> poll_serve;
3107 std::function<void(std::size_t)> poll_advance;
3119 auto poll_open_visit = [&](StationState& s) {
3120 std::size_t found = 0;
3121 for (
const Job& j : s.buffer)
3122 if (j.cls == s.poll_at) ++found;
3123 switch (s.poll_type) {
3127 s.poll_budget = std::numeric_limits<std::size_t>::max();
3128 s.poll_gate = found;
3131 default: s.poll_budget = std::numeric_limits<std::size_t>::max();
break;
3135 poll_serve = [&](std::size_t i) {
3136 StationState& s = S[i];
3137 if (s.server_busy[0] || s.server_blocked[0] || s.server_held[0])
return;
3139 std::size_t at = s.buffer.size();
3140 std::size_t waiting = 0;
3141 for (std::size_t j = 0; j < s.buffer.size(); ++j)
3142 if (s.buffer[j].cls == s.poll_at) {
3144 if (at == s.buffer.size() || s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
3148 const bool decremented =
3150 if (at == s.buffer.size() || s.poll_budget == 0 || decremented) {
3154 Job job = s.buffer[at];
3155 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
3156 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
3159 start_service(i, 0, job);
3162 poll_advance = [&](std::size_t i) {
3163 StationState& s = S[i];
3164 for (std::size_t step = 0; step < K; ++step) {
3165 const std::size_t from = s.poll_at;
3166 const std::size_t next = (from + 1) % K;
3169 if (s.has_switchover[from]) {
3170 s.poll_switching =
true;
3172 e.t = now + s.switchover[from].next(g_aux[i]);
3181 bool has_work =
false;
3182 for (
const Job& j : s.buffer)
3183 if (j.cls == next) {
3195 s.poll_parked =
true;
3210 std::function<void(std::size_t)> pas_reschedule = [&](std::size_t i) {
3211 StationState& s = S[i];
3212 if (s.pas_list.empty())
return;
3213 std::vector<std::size_t> seq;
3214 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
3215 const double total = s.pas_rate(seq);
3216 if (!(total > 0.0))
return;
3217 s.pas_tag = ++ps_tag;
3219 e.t = now + (-std::log(uniform01(g_aux[i])) / total);
3224 e.slot = std::numeric_limits<std::size_t>::max();
3232 auto pas_complete = [&](std::size_t i) -> Job {
3233 StationState& s = S[i];
3234 std::vector<std::size_t> seq;
3235 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
3236 std::vector<double> inc(seq.size(), 0.0);
3237 double prev = 0.0, total = 0.0;
3238 for (std::size_t p = 0; p < seq.size(); ++p) {
3239 std::vector<std::size_t> pre(seq.begin(),
3240 seq.begin() +
static_cast<std::ptrdiff_t
>(p + 1));
3241 const double cur = s.pas_rate(pre);
3242 inc[p] = std::max(0.0, cur - prev);
3246 std::size_t pos = 0;
3248 const double u = uniform01(g_routing) * total;
3250 for (std::size_t p = 0; p < inc.size(); ++p) {
3262 std::vector<std::size_t> chain(1, pos);
3263 std::size_t moving = seq[pos], cur = pos;
3265 std::size_t nxt = seq.size();
3266 for (std::size_t j = cur + 1; j < seq.size(); ++j) {
3267 const bool swappable =
3268 s.pas_swap.empty() ||
3269 (moving < s.pas_swap.size() && seq[j] < s.pas_swap[moving].size() &&
3270 s.pas_swap[moving][seq[j]]);
3276 if (nxt >= seq.size())
break;
3277 chain.push_back(nxt);
3281 Job departing = s.pas_list[chain.back()];
3282 std::vector<Job> arr = s.pas_list;
3283 for (std::size_t k = 0; k + 1 < chain.size(); ++k)
3284 arr[chain[k + 1]] = s.pas_list[chain[k]];
3285 std::vector<Job> survivors;
3286 for (std::size_t k = 0; k < arr.size(); ++k)
3287 if (k != chain.front()) survivors.push_back(arr[k]);
3288 s.pas_list = survivors;
3307 std::size_t hop_old_cls = K;
3308 std::function<void(std::size_t)> release_region;
3310 double pending_region_wait = 0.0;
3311 bool hop_internal =
false;
3314 bool pending_retry =
false;
3315 std::function<bool(std::size_t, Job, std::size_t)> admit =
3316 [&](std::size_t i, Job job, std::size_t from) ->
bool {
3317 StationState& s = S[i];
3318 const double rwait = pending_region_wait;
3319 pending_region_wait = 0.0;
3320 const bool is_retry = pending_retry;
3321 pending_retry =
false;
3323 throw InputError(
"SolverLDES (native engine): a job of class '" +
3324 sn.classes[job.cls].name +
"' reached station '" +
3325 sn.stations[i].name +
"', which does not serve it");
3333 int rgi = region_of[i];
3334 if (rgi >= 0 && from < M && region_of[from] == rgi) {
3335 const std::size_t oc = hop_old_cls;
3340 hop_internal =
true;
3341 if (oc != job.cls) {
3342 const std::size_t rg =
static_cast<std::size_t
>(rgi);
3343 regions[rg].completed[oc] += 1.0;
3351 Region& R = regions[
static_cast<std::size_t
>(rgi)];
3352 if (R.would_exceed(job.cls)) {
3354 if (R.drops(job.cls)) {
3355 R.dropped[job.cls] += 1.0;
3356 dropped[i][job.cls] += 1.0;
3364 w.job.id = ++job_id;
3365 region_wait[
static_cast<std::size_t
>(rgi)].push_back(w);
3366 R.blocked[job.cls] += 1.0;
3373 if (s.role == Role::Queue) {
3375 for (std::size_t r = 0; r < K; ++r) total += acc.qlen[i][r] - s.blocked_at[r];
3376 if (total + 1.0 > s.cap ||
3377 acc.qlen[i][job.cls] - s.blocked_at[job.cls] + 1.0 > s.classcap[job.cls]) {
3382 if (s.has_retrial[job.cls] &&
3383 (s.max_attempts[job.cls] <= 0 ||
3384 job.attempts < s.max_attempts[job.cls])) {
3385 const double dt = now - s.orbit_last;
3387 for (std::size_t r = 0; r < K; ++r) s.tot_orbit[r] += s.orbit_size[r] * dt;
3389 s.orbit_size[job.cls] += 1.0;
3391 e.t = now + s.retrial[job.cls].next(g_svc[i][job.cls]);
3396 e.job.attempts = job.attempts + 1;
3400 if (s.has_retrial[job.cls]) s.retrial_lost[job.cls] += 1.0;
3401 dropped[i][job.cls] += 1.0;
3413 if (!is_retry && !s.balk[job.cls].empty()) {
3415 for (std::size_t r = 0; r < K; ++r)
3416 total += acc.qlen[i][r] - (s.blocked_at.empty() ? 0.0 : s.blocked_at[r]);
3417 for (
const StationState::BalkRule& br : s.balk[job.cls])
3418 if (total >= br.min_jobs && (br.max_jobs < 0.0 || total <= br.max_jobs)) {
3419 if (uniform01(g_routing) < br.probability) {
3420 balked[i][job.cls] += 1.0;
3427 job.region_wait = rwait;
3428 job.priority = classprio[job.cls];
3434 if (draw_at_arrival(i, job.cls)) {
3435 job.service = slot_snap(S[i].svc[job.cls].next_at(g_svc[i][job.cls], now),
3437 job.remaining = job.service;
3440 job.rank = g_siro.aux.next_double();
3441 job.deadline = now + classdeadline[job.cls];
3444 Region& R = regions[
static_cast<std::size_t
>(rgi)];
3449 acc.update_qlen(i, job.cls, now);
3450 acc.qlen[i][job.cls] += 1.0;
3453 acc.arrived[i][job.cls] += 1.0;
3454 bp.track(i, job.cls, +1, now);
3456 if (s.role == Role::Delay) {
3457 if (track_delay_jobs) {
3459 delay_live[i].push_back(job);
3462 e.t = now + job.service;
3466 e.slot = std::numeric_limits<std::size_t>::max();
3474 if (has_gd) sd_reschedule(i);
3481 if (s.lps_limit > 0 && s.ps_jobs.size() >= s.lps_limit) {
3482 buffer_push(i, job);
3487 pj.priority = job.priority;
3488 pj.t_arr = job.t_arr;
3489 pj.region_wait = job.region_wait;
3490 pj.t_sys = job.t_sys;
3491 pj.total = job.service;
3492 pj.remaining = job.service;
3493 pj.parent = job.parent;
3494 s.ps_jobs.push_back(pj);
3501 if (s.has_setup && !s.setup_on) {
3502 buffer_push(i, job);
3503 if (!s.setup_running) {
3504 s.setup_running =
true;
3506 e.t = now + s.setup_time.next(g_aux[i]);
3515 s.pas_list.push_back(job);
3516 acc.update_busy(i, job.cls, now);
3517 acc.busy[i][job.cls] += 1.0;
3522 buffer_push(i, job);
3523 if (s.poll_parked && !s.poll_switching) {
3524 s.poll_parked =
false;
3527 if (job.cls == s.poll_at) {
3533 }
else if (!s.server_busy[0] && !s.poll_switching) {
3542 buffer_push(i, job);
3543 if (!s.server_busy[0]) {
3544 s.server_busy[0] =
true;
3545 s.server_start[0] = now;
3546 acc.update_busy(i, job.cls, now);
3547 acc.busy[i][job.cls] += 1.0;
3553 const std::size_t sl = free_slot_for(i, job.cls);
3554 if (sl < s.nservers) {
3555 start_service(i, sl, job);
3570 (!s.has_parallelism || free_slot_count(i) + 1 >= s.parallelism[job.cls])) {
3571 std::vector<Job> held;
3572 std::vector<std::size_t> slot_of;
3573 for (std::size_t sl = 0; sl < s.nservers; ++sl)
3574 if (s.server_busy[sl] && !s.server_blocked[sl]) {
3579 if (s.has_pools && !s.type_compat[s.server_type[sl]][job.cls])
continue;
3580 Job h = s.server[sl];
3584 h.remaining = std::max(0.0, h.remaining - (now - s.server_start[sl]));
3585 h.elapsed += now - s.server_start[sl];
3587 slot_of.push_back(sl);
3590 static_cast<double>(s.nservers), now);
3591 if (v < held.size()) {
3592 const std::size_t sl = slot_of[v];
3594 start_service(i, sl, job);
3599 buffer_push(i, job);
3604 if (s.has_patience[job.cls]) {
3606 e.t = now + s.patience[job.cls].next(g_svc[i][job.cls]);
3617 const std::map<std::size_t, double> kNoWeights;
3619 std::vector<std::size_t> tab_all;
3629 auto draw_prob = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
3631 for (std::size_t k = 0; k < tab.size(); ++k) total += tab[k].mass;
3632 const double u = uniform01(g_routing) * total;
3634 std::size_t pick = 0;
3635 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3636 cum += tab[pick].mass;
3637 if (u <= cum)
break;
3653 const bool has_sdr = !sn.sdr.empty() && !sn.sdr_nodes.empty();
3654 pfqn::SdrCoeff sdr_coeff;
3656 std::vector<double> sdr_pop, sdr_mass;
3668 auto draw_sdr = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
3669 sdr_pop.assign(M, 0.0);
3670 for (std::size_t i = 0; i < M; ++i) {
3671 if (S[i].role == Role::Source)
continue;
3673 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[i][q];
3679 sdr_mass.assign(tab.size(), 0.0);
3681 for (std::size_t i = 0; i < tab.size(); ++i) {
3682 const std::size_t dnode = tab[i].node - 1;
3684 for (std::size_t b = 1; b < sn.sdr_nodes.branch.size(); ++b)
3685 if (sn.sdr_nodes.entryOf[b] == dnode) p += Pb[b];
3686 if (sn.sdr_nodes.departure == dnode) p += Ped;
3689 if (p < 0.0) p = 0.0;
3698 if (!(total > 0.0))
return draw_prob(tab);
3699 const double u = uniform01(g_routing) * total;
3701 std::size_t pick = 0;
3702 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3703 cum += sdr_mass[pick];
3704 if (u <= cum)
break;
3717 auto shortest_queue = [&](
const std::vector<RouteDest>& tab,
3718 const std::vector<std::size_t>& cand) -> std::size_t {
3719 double best = std::numeric_limits<double>::infinity();
3720 std::vector<std::size_t> tied;
3721 for (std::size_t c = 0; c < cand.size(); ++c) {
3722 const std::size_t k = cand[c];
3723 if (tab[k].station >= M || S[tab[k].station].role == Role::Source)
continue;
3725 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[tab[k].station][q];
3730 }
else if (held == best) {
3734 if (tied.empty())
return cand.empty() ? 0 : cand[0];
3735 if (tied.size() == 1)
return tied[0];
3736 std::size_t at =
static_cast<std::size_t
>(uniform01(g_routing) *
3737 static_cast<double>(tied.size()));
3738 if (at >= tied.size()) at = tied.size() - 1;
3752 auto draw_node_route = [&](std::size_t inode, std::size_t r) -> RouteEntry {
3753 const std::vector<RouteDest>& tab = nroute[inode][r];
3755 throw InputError(
"SolverLDES (native engine): node '" + sn.nodes[inode - 1].name +
3756 "' has no routing for class '" + sn.classes[r].name +
"'");
3757 std::size_t pick = 0;
3758 if (tab.size() > 1) {
3759 if (tab_all.size() != tab.size()) {
3760 tab_all.resize(tab.size());
3761 for (std::size_t k = 0; k < tab.size(); ++k) tab_all[k] = k;
3763 switch (node_routing[inode][r]) {
3765 pick =
static_cast<std::size_t
>(uniform01(g_routing) *
3766 static_cast<double>(tab.size()));
3767 if (pick >= tab.size()) pick = tab.size() - 1;
3771 pick = rr_counter[inode][r] % tab.size();
3772 ++rr_counter[inode][r];
3781 const std::map<std::size_t, double>& w =
3782 (sn.nodes[inode - 1].routing_weights.size() > r)
3783 ? sn.nodes[inode - 1].routing_weights[r]
3785 std::vector<double> raw(tab.size(), 0.0);
3786 bool integral =
true;
3787 for (std::size_t k = 0; k < tab.size(); ++k) {
3788 const std::map<std::size_t, double>::const_iterator it = w.find(tab[k].node);
3789 raw[k] = (it == w.end()) ? 0.0 : it->second;
3790 if (raw[k] < 0.0 || raw[k] != std::floor(raw[k])) integral =
false;
3792 std::vector<std::size_t> quota(tab.size(), 0);
3793 std::size_t total = 0;
3794 for (std::size_t k = 0; k < tab.size(); ++k) {
3795 double v = integral ? raw[k] : raw[k] * 1000.0;
3796 std::size_t q = (v > 0.0) ?
static_cast<std::size_t
>(v) : 0;
3797 if (raw[k] > 0.0 && q < 1) q = 1;
3804 pick = rr_counter[inode][r] % tab.size();
3805 ++rr_counter[inode][r];
3808 std::size_t counter = rr_counter[inode][r] % total;
3809 ++rr_counter[inode][r];
3810 std::size_t cum = 0;
3811 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3813 if (counter < cum)
break;
3825 pick = has_sdr ? draw_sdr(tab) : draw_prob(tab);
3829 pick = shortest_queue(tab, tab_all);
3836 std::size_t d = (sn.nodes[inode - 1].routing_param.size() > r &&
3837 sn.nodes[inode - 1].routing_param[r] > 0)
3838 ?
static_cast<std::size_t
>(
3839 sn.nodes[inode - 1].routing_param[r])
3841 if (tab.size() <= d) {
3842 pick = shortest_queue(tab, tab_all);
3845 std::vector<std::size_t> avail(tab.size());
3846 for (std::size_t k = 0; k < tab.size(); ++k) avail[k] = k;
3847 std::vector<std::size_t> cand;
3848 for (std::size_t k = 0; k < d && !avail.empty(); ++k) {
3849 std::size_t at =
static_cast<std::size_t
>(
3850 uniform01(g_routing) *
static_cast<double>(avail.size()));
3851 if (at >= avail.size()) at = avail.size() - 1;
3852 cand.push_back(avail[at]);
3853 avail.erase(avail.begin() +
static_cast<std::ptrdiff_t
>(at));
3855 pick = shortest_queue(tab, cand);
3859 pick = draw_prob(tab);
3864 const RouteDest& d = tab[pick];
3867 e.station = d.station;
3869 e.cls = d.cls[0].first;
3870 if (d.cls.size() > 1) {
3872 for (std::size_t k = 0; k < d.cls.size(); ++k) total += d.cls[k].second;
3873 const double u = uniform01(g_routing) * total;
3875 for (std::size_t k = 0; k < d.cls.size(); ++k) {
3876 cum += d.cls[k].second;
3877 if (u <= cum || k + 1 == d.cls.size()) {
3878 e.cls = d.cls[k].first;
3907 auto resolve_final_cls = [&](std::size_t node, std::size_t cls) -> std::size_t {
3908 std::size_t at_node = node, at_cls = cls;
3909 for (std::size_t hop = 0; hop <= nnodes && at_node != 0; ++hop) {
3910 const NodeType dt = sn.nodes[at_node - 1].nodetype;
3911 if (dt != NodeType::ClassSwitch && dt != NodeType::Router && dt != NodeType::Logger)
3913 const RouteEntry e = draw_node_route(at_node, at_cls);
3946 auto resolve_forced_dest = [&](
const RouteEntry& re) -> RouteEntry {
3948 for (std::size_t hop = 0; hop <= nnodes; ++hop) {
3949 if (at.node == 0 || at.sink)
break;
3950 const NodeType dt = sn.nodes[at.node - 1].nodetype;
3951 if (dt != NodeType::ClassSwitch && dt != NodeType::Router && dt != NodeType::Logger)
3953 const std::vector<RouteDest>& tab = nroute[at.node][at.cls];
3954 if (tab.size() != 1 || tab[0].cls.size() != 1)
break;
3956 nxt.node = tab[0].node;
3957 nxt.station = tab[0].station;
3958 nxt.sink = tab[0].sink;
3959 nxt.cls = tab[0].cls[0].first;
3970 if (o.busy_period_orders > 0) {
3971 std::vector<std::vector<std::size_t>> sets;
3972 std::vector<std::string> set_names;
3973 for (std::size_t i = 0; i < M; ++i) {
3974 if (S[i].role == Role::Source || S[i].role == Role::Synchronization)
continue;
3975 sets.push_back(std::vector<std::size_t>(1, i));
3976 set_names.push_back(sn.stations[i].name);
3978 for (
const std::vector<std::size_t>& sub : o.busy_period_subnets) {
3980 for (std::size_t s2 : sub) {
3981 if (s2 >= M || S[s2].role == Role::Source)
3982 throw InputError(
"SolverLDES (native engine): station " +
3983 std::to_string(s2) +
3984 " of a busy period subnetwork is not a service station");
3985 nm += (nm.empty() ?
"" :
"+") + sn.stations[s2].name;
3987 sets.push_back(sub);
3988 set_names.push_back(nm);
3990 std::vector<BpTarget> targets;
3991 for (std::size_t si = 0; si < sets.size(); ++si) {
3993 t.stations = sets[si];
3995 t.name = set_names[si];
3996 targets.push_back(t);
3997 for (std::size_t r = 0; r < K; ++r) {
3999 tc.stations = sets[si];
4000 tc.job_class =
static_cast<int>(r);
4001 tc.name = set_names[si] +
":" + sn.classes[r].name;
4002 targets.push_back(tc);
4005 bp.init(targets, o.busy_period_orders, M);
4018 std::function<void(std::size_t)> release_blocked = [&](std::size_t j) {
4019 bool progress =
true;
4022 std::size_t best_i = M, best_sl = 0;
4023 double oldest = std::numeric_limits<double>::infinity();
4024 for (std::size_t a = 0; a < M; ++a) {
4028 if (S[a].role != Role::Queue || S[a].ps || S[a].server_blocked.empty())
continue;
4029 for (std::size_t sl = 0; sl < S[a].nservers; ++sl)
4030 if (S[a].server_blocked[sl] && S[a].blocked_dest[sl] == j &&
4031 S[a].blocked_job[sl].t_arr < oldest) {
4032 oldest = S[a].blocked_job[sl].t_arr;
4037 if (best_i >= M)
break;
4038 const std::size_t dcls = S[best_i].blocked_dest_cls[best_sl];
4039 if (!dest_has_room(j, dcls))
break;
4041 StationState& src = S[best_i];
4042 Job moved = src.blocked_job[best_sl];
4043 src.server_blocked[best_sl] =
false;
4044 src.server_busy[best_sl] =
false;
4045 src.server_tag[best_sl] = 0;
4046 acc.update_qlen(j, dcls, now);
4047 acc.qlen[j][dcls] -= 1.0;
4048 S[j].blocked_at[dcls] -= 1.0;
4051 acc.update_busy(best_i, moved.cls, now);
4052 if (acc.busy[best_i][moved.cls] > 0.0) acc.busy[best_i][moved.cls] -= 1.0;
4056 fresh.t_sys = moved.t_sys;
4057 fresh.parent = moved.parent;
4058 admit(j, fresh, best_i);
4062 serve_next_on_slot(best_i, best_sl);
4081 release_region = [&](std::size_t rg) {
4082 std::vector<RegionWaiter>& q = region_wait[rg];
4083 while (!q.empty()) {
4085 const std::size_t w = 0;
4087 if (R.would_exceed(q[w].job.cls))
break;
4088 RegionWaiter taken = q[w];
4090 R.blocked[taken.job.cls] -= 1.0;
4091 q.erase(q.begin() +
static_cast<std::ptrdiff_t
>(w));
4093 fresh.cls = taken.job.cls;
4094 fresh.t_sys = taken.job.t_sys;
4097 fresh.parent = taken.job.parent;
4098 fresh.call = taken.job.call;
4102 pending_region_wait = now - taken.job.t_arr;
4103 admit(taken.station, fresh, M);
4111 auto signal_held = [&](std::size_t i) -> std::size_t {
4112 const StationState& s = S[i];
4113 if (s.role == Role::Delay)
return delay_live[i].size();
4114 if (s.pas)
return s.pas_list.size();
4115 std::size_t n = s.buffer.size();
4116 if (s.ps)
return n + s.ps_jobs.size();
4117 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4118 if (s.server_busy[sl]) ++n;
4139 StationState& s = S[i];
4142 if (s.role == Role::Delay) {
4143 std::vector<Job>& live = delay_live[i];
4144 if (live.empty())
return K;
4147 for (std::size_t j = 1; j < live.size(); ++j)
4148 if (live[j].t_arr < live[at].t_arr) at = j;
4150 for (std::size_t j = 1; j < live.size(); ++j)
4151 if (live[j].t_arr > live[at].t_arr) at = j;
4153 at =
static_cast<std::size_t
>(uniform01(g_routing) *
4154 static_cast<double>(live.size()));
4155 if (at >= live.size()) at = live.size() - 1;
4157 const std::size_t rc = live[at].cls;
4158 live.erase(live.begin() +
static_cast<std::ptrdiff_t
>(at));
4166 if (s.pas_list.empty())
return K;
4169 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
4170 if (s.pas_list[j].t_arr < s.pas_list[at].t_arr) at = j;
4172 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
4173 if (s.pas_list[j].t_arr > s.pas_list[at].t_arr) at = j;
4175 at =
static_cast<std::size_t
>(uniform01(g_routing) *
4176 static_cast<double>(s.pas_list.size()));
4177 if (at >= s.pas_list.size()) at = s.pas_list.size() - 1;
4179 const std::size_t rc = s.pas_list[at].cls;
4180 s.pas_list.erase(s.pas_list.begin() +
static_cast<std::ptrdiff_t
>(at));
4181 acc.update_busy(i, rc, now);
4182 if (acc.busy[i][rc] > 0.0) acc.busy[i][rc] -= 1.0;
4187 const std::size_t waiting = s.buffer.size();
4192 const std::size_t inservice = s.ps_jobs.size();
4193 if (waiting + inservice == 0)
return K;
4196 const std::size_t pick =
static_cast<std::size_t
>(
4197 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
4198 from_wait = pick < waiting;
4200 from_wait = waiting > 0;
4205 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4206 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
4208 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4209 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
4211 at = std::min(waiting - 1,
4212 static_cast<std::size_t
>(uniform01(g_routing) *
4213 static_cast<double>(waiting)));
4215 const std::size_t rc = s.buffer[at].cls;
4216 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
4217 if (s.sched != SchedStrategy::FSP)
4218 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
4224 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
4225 if (s.ps_jobs[j].t_arr < s.ps_jobs[at].t_arr) at = j;
4227 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
4228 if (s.ps_jobs[j].t_arr > s.ps_jobs[at].t_arr) at = j;
4230 at = std::min(inservice - 1,
4231 static_cast<std::size_t
>(uniform01(g_routing) *
4232 static_cast<double>(inservice)));
4234 const std::size_t rc = s.ps_jobs[at].cls;
4235 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(at));
4237 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
4238 Job nextjob = buffer_pop(i);
4240 pj.cls = nextjob.cls;
4241 pj.priority = nextjob.priority;
4242 pj.t_arr = nextjob.t_arr;
4243 pj.region_wait = nextjob.region_wait;
4244 pj.t_sys = nextjob.t_sys;
4245 pj.total = nextjob.service;
4246 pj.remaining = nextjob.service;
4247 pj.parent = nextjob.parent;
4248 s.ps_jobs.push_back(pj);
4257 std::size_t inservice = 0;
4258 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4259 if (s.server_busy[sl]) ++inservice;
4260 if (waiting + inservice == 0)
return K;
4263 const std::size_t pick =
static_cast<std::size_t
>(
4264 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
4265 from_wait = pick < waiting;
4267 from_wait = waiting > 0;
4273 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4274 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
4276 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4277 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
4279 at = std::min(waiting - 1,
4280 static_cast<std::size_t
>(uniform01(g_routing) *
4281 static_cast<double>(waiting)));
4283 const std::size_t rc = s.buffer[at].cls;
4284 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
4285 if (s.sched != SchedStrategy::FSP)
4286 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
4290 std::size_t slot = s.nservers;
4291 for (std::size_t sl = 0; sl < s.nservers; ++sl) {
4292 if (!s.server_busy[sl])
continue;
4293 if (slot == s.nservers) {
4298 if (s.server[sl].t_arr < s.server[slot].t_arr) slot = sl;
4300 if (s.server[sl].t_arr > s.server[slot].t_arr) slot = sl;
4304 std::size_t pick = std::min(inservice - 1,
4305 static_cast<std::size_t
>(uniform01(g_routing) *
4306 static_cast<double>(inservice)));
4307 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4308 if (s.server_busy[sl]) {
4316 if (slot == s.nservers)
return K;
4318 const std::size_t rc = s.server[slot].cls;
4321 s.server_busy[slot] =
false;
4322 s.server_tag[slot] = 0;
4323 const double freed = release_slots(i, slot);
4324 acc.update_busy(i, rc, now);
4325 acc.busy[i][rc] -= freed;
4330 }
else if (!s.server_blocked[slot] && !s.server_held[slot]) {
4331 serve_next_on_slot(i, slot);
4353 auto apply_signal = [&](std::size_t i,
const Job& sig) {
4354 StationState& s = S[i];
4355 const std::size_t held = signal_held(i);
4357 std::size_t to_remove = 1;
4358 if (sig.cls < sn.signaltype.size() &&
4361 }
else if (sig.cls < sn.signalremdist.size() && !sn.signalremdist[sig.cls].empty()) {
4362 const std::vector<T>& pmf = sn.signalremdist[sig.cls];
4363 const double u = uniform01(g_routing);
4366 for (; b + 1 < pmf.size(); ++b) {
4367 cum +=
static_cast<double>(pmf[b]);
4370 to_remove = std::min(b, held);
4372 to_remove = std::min<std::size_t>(1, held);
4375 if (sig.cls < sn.signalrempolicy.size()) policy = sn.signalrempolicy[sig.cls];
4376 for (std::size_t rep = 0; rep < to_remove; ++rep) {
4377 if (signal_held(i) == 0)
break;
4378 const std::size_t rc = signal_remove_one(i, policy);
4381 acc.update_qlen(i, rc, now);
4382 acc.qlen[i][rc] -= 1.0;
4383 bp.track(i, rc, -1, now);
4387 if (region_of[i] >= 0) {
4388 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
4389 regions[rg].update(now);
4390 regions[rg].leave(rc);
4399 sys_resp_sum[sig.cls] += now - sig.t_sys;
4400 sys_resp_cnt[sig.cls] += 1.0;
4401 sys_completed[sig.cls] += 1.0;
4418 std::uint64_t total_completions = 0;
4430 std::function<void(CacheState&, std::size_t, std::size_t)> release_delayed_hits;
4446 std::function<void(std::size_t, Job, std::size_t)> deliver =
4447 [&](std::size_t node, Job job, std::size_t from) {
4448 const NodeType nt = sn.nodes[node - 1].nodetype;
4450 if (nt == NodeType::Sink) {
4451 sys_resp_sum[job.cls] += now - job.t_sys;
4452 sys_resp_cnt[job.cls] += 1.0;
4453 sys_completed[job.cls] += 1.0;
4457 if (nt == NodeType::Cache) {
4458 auto ci = caches.find(node);
4459 if (ci == caches.end())
4460 throw InputError(
"SolverLDES (native engine): Cache node '" +
4461 sn.nodes[node - 1].name +
"' carries no parameters");
4462 CacheState& cs = ci->second;
4470 if (cs.has_retrieval) {
4471 const int fetched = cs.fetches_item(job.cls);
4473 const std::size_t fi =
static_cast<std::size_t
>(fetched);
4474 cache_miss(cs, fi, uniform01(g_routing), uniform01(g_routing));
4475 cs.in_flight[fi] = 0;
4476 cs.total_fetch_time += now - cs.fetch_start[fi];
4477 cs.completed_fetches += 1.0;
4478 release_delayed_hits(cs, fi, node);
4479 if (cs.miss_class[job.cls] >= 0)
4480 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
4481 const RouteEntry re = draw_node_route(node, out.cls);
4483 if (re.node != 0 && sn.nodes[re.node - 1].nodetype == NodeType::Sink)
4484 ++total_completions;
4485 deliver(re.node, out, M);
4490 const std::vector<double>& pop = cs.popularity[job.cls];
4492 throw InputError(
"SolverLDES (native engine): class '" +
4493 sn.classes[job.cls].name +
"' reads cache '" +
4494 sn.nodes[node - 1].name +
"' with no item popularity");
4495 const double u = uniform01(g_routing);
4496 std::size_t item = 0;
4497 while (item + 1 < pop.size() && u >= pop[item]) ++item;
4499 const int at = cs.find(item);
4501 cs.hits[job.cls] += 1.0;
4502 cache_hit(cs, item,
static_cast<std::size_t
>(at), uniform01(g_routing));
4503 if (cs.hit_class[job.cls] >= 0)
4504 out.cls =
static_cast<std::size_t
>(cs.hit_class[job.cls]);
4505 }
else if (cs.has_retrieval && cs.retrieval_of(item, job.cls) > 0) {
4506 if (cs.in_flight[item]) {
4510 CacheState::HeldRequest hr;
4513 hr.t_sys = job.t_sys;
4514 cs.held[item].push_back(hr);
4520 cs.misses[job.cls] += 1.0;
4521 cs.in_flight[item] = 1;
4522 cs.fetch_start[item] = now;
4523 out.cls = cs.retrieval_of(item, job.cls) - 1;
4525 cs.misses[job.cls] += 1.0;
4526 cache_miss(cs, item, uniform01(g_routing), uniform01(g_routing));
4527 if (cs.miss_class[job.cls] >= 0)
4528 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
4532 const RouteEntry e = draw_node_route(node, out.cls);
4536 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
4537 ++total_completions;
4538 deliver(e.node, out, M);
4542 if (nt == NodeType::ClassSwitch || nt == NodeType::Router || nt == NodeType::Logger) {
4558 std::size_t at = node;
4559 for (std::size_t hop = 0; hop <= nnodes; ++hop) {
4560 const RouteEntry e = draw_node_route(at, out.cls);
4562 if (e.node == 0)
return;
4563 const NodeType dt = sn.nodes[e.node - 1].nodetype;
4564 if (dt == NodeType::ClassSwitch || dt == NodeType::Router ||
4565 dt == NodeType::Logger) {
4569 deliver(e.node, out, from);
4572 throw InputError(
"SolverLDES (native engine): the routing out of node '" +
4573 sn.nodes[node - 1].name +
4574 "' cycles through pass-through nodes without reaching a station");
4577 if (nt == NodeType::Fork) {
4581 const std::vector<RouteDest>& tab = nroute[node][job.cls];
4582 if (tab.empty())
return;
4583 std::vector<RouteEntry> links;
4584 for (std::size_t di = 0; di < tab.size(); ++di)
4585 for (std::size_t ci = 0; ci < tab[di].cls.size(); ++ci) {
4587 e.node = tab[di].node;
4588 e.station = tab[di].station;
4589 e.sink = tab[di].sink;
4590 e.cls = tab[di].cls[ci].first;
4593 const qn::NodeDef& fk = sn.nodes[node - 1];
4594 const qn::ForkParam<double>* fp = sn.fork_param_of(node);
4595 const int per_link = std::max(1,
static_cast<int>(fk.tasks_per_link + 0.5));
4603 std::vector<int> per_branch(links.size(), per_link);
4605 const bool variable = fork_is_variable(node);
4606 for (std::size_t li = 0; li < links.size(); ++li) {
4608 const std::size_t k0 = links[li].node - 1, r0 = job.cls;
4609 const double p = fp->fan_out_prob(k0, r0);
4610 if (p < 1.0 && g_fork.aux.next_double() >= p) {
4612 }
else if (!fp->fan_out_dist[k0][r0].disabled) {
4613 per_branch[li] = sample_fork_degree(fp->fan_out_dist[k0][r0]);
4615 per_branch[li] =
static_cast<int>(fp->fan_out_link(k0, r0) + 0.5);
4618 total += per_branch[li];
4621 throw InputError(
"SolverLDES (native engine): Fork '" + fk.name +
4622 "' emitted no sibling; at least one branch must be certain "
4623 "to emit at least one task");
4627 fs.t_sys = job.t_sys;
4629 fs.required = fs.total;
4630 const int jn = join_of_fork[node];
4632 auto jd = sn.joindecl.find(
static_cast<std::size_t
>(jn));
4633 if (jd != sn.joindecl.end() &&
4639 fs.required = std::min(fs.total,
4640 std::max(1,
static_cast<int>(jd->second.quorum + 0.5)));
4643 const std::uint64_t pid = ++next_parent;
4644 fork_sync[pid] = fs;
4645 for (std::size_t li = 0; li < links.size(); ++li)
4646 for (
int t = 0; t < per_branch[li]; ++t) {
4648 sib.cls = links[li].cls;
4649 sib.t_sys = job.t_sys;
4651 deliver(links[li].node, sib, M);
4656 if (nt == NodeType::Join) {
4657 auto it = fork_sync.find(job.parent);
4658 if (it == fork_sync.end()) {
4670 const std::size_t jd = node_to_station[node];
4672 acc.arrived[jd][job.cls] += 1.0;
4673 acc.join_dropped[jd][job.cls] += 1.0;
4677 ForkSync& fs = it->second;
4686 const std::size_t js = node_to_station[node];
4688 acc.update_qlen(js, job.cls, now);
4689 acc.qlen[js][job.cls] += 1.0;
4690 acc.arrived[js][job.cls] += 1.0;
4692 fs.siblings.push_back(std::make_pair(job.cls, now));
4693 if (
static_cast<int>(fs.siblings.size()) < fs.required)
return;
4697 for (std::size_t si = 0; si < fs.siblings.size(); ++si) {
4698 const std::size_t sc = fs.siblings[si].first;
4699 acc.update_qlen(js, sc, now);
4700 acc.qlen[js][sc] -= 1.0;
4703 acc.resp_sum[js][sc] += now - fs.siblings[si].second;
4704 acc.resp_cnt[js][sc] += 1.0;
4706 acc.completed[js][fs.cls] += 1.0;
4709 merged.cls = fs.cls;
4710 merged.t_sys = fs.t_sys;
4711 fork_sync.erase(it);
4712 const RouteEntry e = draw_node_route(node, merged.cls);
4714 deliver(e.node, merged, M);
4719 const std::size_t j = node_to_station[node];
4723 if (has_removal_signal && j < M && is_removal_signal[job.cls]) {
4724 apply_signal(j, job);
4737 if (has_sync_call && j < M && is_reply_signal[job.cls]) {
4738 acc.completed[j][job.cls] += 1.0;
4739 typename std::map<std::uint64_t, PendingCall>::iterator pc =
4740 pending_reply.find(job.call);
4741 if (pc != pending_reply.end()) {
4742 const std::size_t bi = pc->second.station, bs = pc->second.slot;
4743 const std::size_t bc = pc->second.cls;
4744 pending_reply.erase(pc);
4745 StationState& bst = S[bi];
4746 bst.server_held[bs] =
false;
4747 acc.update_busy(bi, bc, now);
4748 if (acc.busy[bi][bc] > 0.0) acc.busy[bi][bc] -= 1.0;
4749 acc.update_qlen(bi, bc, now);
4750 acc.held[bi][bc] -= 1.0;
4751 if (!bst.server_blocked[bs]) {
4752 serve_next_on_slot(bi, bs);
4755 release_blocked(bi);
4757 const RouteEntry re2 = draw_node_route(node, job.cls);
4759 onward.cls = re2.cls;
4760 onward.t_sys = job.t_sys;
4761 onward.parent = job.parent;
4762 deliver(re2.node, onward, M);
4770 j + 1 == sn.classes[job.cls].refstat) {
4771 sys_resp_sum[job.cls] += now - job.t_sys;
4772 sys_resp_cnt[job.cls] += 1.0;
4773 sys_completed[job.cls] += 1.0;
4777 throw InputError(
"SolverLDES (native engine): a closed job was dropped at "
4778 "station '" + sn.stations[j].name +
4779 "'; a closed class cannot lose population");
4782 release_delayed_hits = [&](CacheState& cs, std::size_t item, std::size_t node) {
4783 if (item >= cs.held.size() || cs.held[item].empty())
return;
4784 std::vector<CacheState::HeldRequest> freed;
4785 freed.swap(cs.held[item]);
4786 for (std::size_t i = 0; i < freed.size(); ++i) {
4787 const CacheState::HeldRequest& hr = freed[i];
4788 cs.delayed[hr.cls] += 1.0;
4789 cs.delayed_wait += now - hr.hold_time;
4791 out.cls = (cs.hit_class[hr.cls] >= 0) ?
static_cast<std::size_t
>(cs.hit_class[hr.cls])
4793 out.t_sys = hr.t_sys;
4794 const RouteEntry e = draw_node_route(node, out.cls);
4796 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
4797 ++total_completions;
4798 deliver(e.node, out, M);
4810 auto flat_marking = [&]() {
4811 std::vector<double> tok(place_nodes.size() * K, 0.0);
4812 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4813 for (std::size_t r = 0; r < K; ++r) tok[p * K + r] = marking[p][r];
4827 auto flat_avail = [&]() {
4828 std::vector<double> tok(place_nodes.size() * K, 0.0);
4829 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4830 for (std::size_t r = 0; r < K; ++r) tok[p * K + r] = avail[p][r];
4843 auto spn_apply = [&](
const SpnMode& m) {
4844 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4845 for (std::size_t r = 0; r < K; ++r) {
4846 const double e = m.enabling[p * K + r];
4847 if (e > 0.0) place_take(p, r, e);
4849 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4850 for (std::size_t r = 0; r < K; ++r) {
4851 const double f = m.firing[p * K + r];
4852 if (f > 0.0) place_deposit(p, r, f);
4865 std::uint64_t fire_tag = 0;
4866 std::vector<std::uint64_t> live_fire(transitions.size(), 0);
4867 std::function<void()> spn_settle = [&]() {
4868 if (transitions.empty())
return;
4869 for (
int guard = 0; guard < 1000000; ++guard) {
4870 bool fired_any =
false;
4871 std::vector<double> tok = flat_avail();
4872 for (std::size_t t = 0; t < transitions.size(); ++t) {
4874 if (k < 0)
continue;
4875 const SpnMode& m = transitions[t].modes[
static_cast<std::size_t
>(k)];
4879 transitions[t].fired[
static_cast<std::size_t
>(k)] += 1.0;
4880 ++total_completions;
4884 if (!fired_any)
break;
4889 const std::vector<double> tok = flat_avail();
4890 const std::vector<double> mk = flat_marking();
4891 for (std::size_t t = 0; t < transitions.size(); ++t) {
4892 double best = std::numeric_limits<double>::infinity();
4894 for (std::size_t k = 0; k < transitions[t].modes.size(); ++k) {
4895 const SpnMode& m = transitions[t].modes[k];
4896 if (m.immediate || !
spn_enabled(m, tok))
continue;
4897 const double rate =
spn_rate(m, mk);
4898 if (!(rate > 0.0))
continue;
4899 const double d = -std::log(uniform01(g_spn)) / rate;
4902 best_mode =
static_cast<int>(k);
4905 live_fire[t] = ++fire_tag;
4906 if (best_mode < 0)
continue;
4911 e.cls =
static_cast<std::size_t
>(best_mode);
4912 e.tag = live_fire[t];
4935 const bool warm_start = !o.init_sol.empty() && place_nodes.empty();
4937 for (std::size_t i = 0; i < M; ++i) {
4938 if (S[i].role != Role::Queue && S[i].role != Role::Delay)
continue;
4939 for (std::size_t k = K; k-- > 0;) {
4940 const std::size_t idx = i * K + k;
4941 if (idx >= o.init_sol.size())
continue;
4942 const double v = o.init_sol[idx];
4943 if (!(v > 0.0))
continue;
4944 const std::size_t count =
static_cast<std::size_t
>(v);
4945 for (std::size_t j = 0; j < count; ++j) {
4949 if (!admit(i, job, M))
4950 throw InputError(
"SolverLDES (native engine): the warm-start placement "
4951 "of class '" + sn.classes[k].name +
"' does not fit "
4952 "station '" + sn.stations[i].name +
"'");
4958 for (std::size_t r = 0; r < K; ++r) {
4961 for (std::size_t i = 0; i < M; ++i) held += acc.qlen[i][r];
4962 const double want = sn.classes[r].population;
4963 if (std::fabs(held - want) > 0.5)
4964 throw InputError(
"SolverLDES (native engine): the warm-start placement holds " +
4965 std::to_string(
static_cast<long>(held + 0.5)) +
" jobs of class '" +
4966 sn.classes[r].name +
"' against a population of " +
4967 std::to_string(
static_cast<long>(want + 0.5)));
4973 for (std::size_t i = 0; i < M; ++i) {
4974 if (!S[i].has_breakdown)
continue;
4976 e.t = S[i].failure_time.next_at(g_aux[i], 0.0);
4982 std::vector<Sampler> arrival(K);
4983 std::vector<double> lambda(K, 0.0);
4992 std::vector<Sampler> arr_batch(K);
4993 std::vector<bool> has_arr_batch(K,
false);
4994 std::vector<Rng> g_arr_batch;
4995 g_arr_batch.reserve(K);
4996 for (std::size_t k = 0; k < K; ++k)
4997 g_arr_batch.push_back(Rng(seed_ll, 991LL + 97LL * (
static_cast<long long>(k) + 1LL)));
4998 if (source_st < M) {
4999 const auto& sb = sn.stations[source_st].arrival_batch;
5000 for (std::size_t r = 0; r < K && r < sb.size(); ++r) {
5001 if (sb[r].disabled)
continue;
5002 arr_batch[r] = Sampler(sb[r],
"the arrival batch of class '" + sn.classes[r].name +
5003 "' at station '" + sn.stations[source_st].name +
"'");
5004 has_arr_batch[r] =
true;
5021 std::vector<std::size_t> mark_class;
5022 std::vector<double> mark_rate;
5023 std::size_t mark_carrier = K;
5024 if (source_st < M) {
5025 for (std::size_t c : sn.stations[source_st].marked_classes)
5026 if (c >= 1 && c <= K) mark_class.push_back(c - 1);
5035 auto nominal_of = [](
const std::vector<T>& bp,
const std::vector<Matrix<T>>& segs) {
5036 const T zero = num_traits<T>::from_int(0);
5037 Matrix<T> acc(segs[0].rows(), segs[0].cols(), zero);
5039 for (std::size_t k = 0; k + 1 < bp.size(); ++k) total += T(bp[k + 1] - bp[k]);
5040 for (std::size_t k = 0; k < segs.size(); ++k) {
5041 const T w = T(T(bp[k + 1] - bp[k]) / total);
5042 for (std::size_t a = 0; a < acc.rows(); ++a)
5043 for (std::size_t b = 0; b < acc.cols(); ++b) acc(a, b) += w * segs[k](a, b);
5058 auto mean_batch_of = [&](
const lang::Distrib<T>& cd) ->
double {
5059 std::vector<Matrix<T>> blocks;
5060 std::vector<std::size_t> sizes;
5062 for (std::size_t b = 0; b < cd.Dmark.size(); ++b) {
5063 blocks.push_back(cd.Dmark[b]);
5064 sizes.push_back(b + 1);
5066 }
else if (cd.has_batch_schedule()) {
5067 for (std::size_t c = 0; c < cd.sched_Dbatch.size(); ++c)
5068 for (std::size_t b = 0; b < cd.sched_Dbatch[c].size(); ++b) {
5069 blocks.push_back(nominal_of(cd.sched_bp, cd.sched_Dbatch[c][b]));
5070 sizes.push_back(b + 1);
5073 if (blocks.empty())
return 1.0;
5079 double epochs = 0.0, jobs = 0.0;
5080 for (std::size_t b = 0; b < blocks.size(); ++b) {
5081 const double v = num_traits<T>::to_double(lam[b]);
5083 jobs +=
static_cast<double>(sizes[b]) * v;
5085 return (epochs > 0.0) ? (jobs / epochs) : 1.0;
5087 if (!mark_class.empty()) {
5088 mark_carrier = mark_class[0];
5089 const lang::Distrib<T>& cd = sn.service[source_st][mark_carrier];
5090 if (cd.Dmark.size() < mark_class.size())
5091 throw InputError(
"SolverLDES (native engine): the Source binds " +
5092 std::to_string(mark_class.size()) +
5093 " marks but its arrival process carries only " +
5094 std::to_string(cd.Dmark.size()) +
" marked blocks");
5095 if (cd.has_batch_schedule()) {
5100 const std::size_t B = cd.sched_Dbatch[0].size();
5101 std::vector<Matrix<T>> blocks;
5102 for (std::size_t c = 0; c < cd.sched_Dbatch.size(); ++c)
5103 for (std::size_t b = 0; b < B; ++b)
5104 blocks.push_back(nominal_of(cd.sched_bp, cd.sched_Dbatch[c][b]));
5110 for (std::size_t k = 0; k < mark_class.size(); ++k) {
5112 for (std::size_t b = 0; b < B; ++b)
5113 jobs +=
static_cast<double>(b + 1) *
5114 num_traits<T>::to_double(lam[k * B + b]);
5115 mark_rate.push_back(jobs);
5123 for (std::size_t k = 0; k < mark_class.size(); ++k)
5124 mark_rate.push_back(num_traits<T>::to_double(lam[k]));
5128 const std::vector<std::size_t>& mkc = mark_class;
5129 auto marked_non_carrier = [&mkc, &mark_carrier](std::size_t r) {
5130 if (mkc.empty() || r == mark_carrier)
return false;
5131 for (std::size_t c : mkc)
5132 if (c == r)
return true;
5136 for (std::size_t r = 0; r < K; ++r) {
5139 throw InputError(
"SolverLDES (native engine): an open class with no Source");
5140 if (S[source_st].off[r])
continue;
5141 arrival[r] = S[source_st].svc[r];
5145 lambda[r] = mean_batch_of(sn.service[source_st][r]) / arrival[r].mean();
5146 if (has_arr_batch[r]) lambda[r] = arr_batch[r].mean() / arrival[r].mean();
5147 for (std::size_t k = 0; k < mark_class.size(); ++k)
5148 if (mark_class[k] == r) lambda[r] = mark_rate[k];
5150 if (marked_non_carrier(r))
continue;
5152 e.t = slot_snap(arrival[r].next_at(g_arr[r], 0.0),
"interarrival time");
5154 e.station = source_st;
5158 if (warm_start)
continue;
5159 const double n = sn.classes[r].population;
5160 if (!(n >= 0.0) || !std::isfinite(n))
5161 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
5162 "' has a population that is not a finite count");
5163 const std::size_t refst = sn.classes[r].refstat;
5164 if (refst == 0 || refst > M)
5165 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
5166 "' has no reference station");
5176 if (sn.stations[refst - 1].nodetype == NodeType::Place)
continue;
5177 const std::size_t count =
static_cast<std::size_t
>(n + 0.5);
5178 for (std::size_t j = 0; j < count; ++j) {
5182 if (!admit(refst - 1, job, M))
5183 throw InputError(
"SolverLDES (native engine): the initial population of "
5184 "class '" + sn.classes[r].name +
5185 "' does not fit its reference station's capacity");
5192 cnvg.init(M, K, o, max_events);
5193 std::vector<std::size_t> servers_of(M, 1);
5194 std::vector<std::vector<bool>> off_of(M, std::vector<bool>(K,
true));
5195 for (std::size_t i = 0; i < M; ++i) {
5196 servers_of[i] = S[i].nservers;
5197 for (std::size_t r = 0; r < K; ++r)
5198 off_of[i][r] = S[i].off[r] || S[i].role == Role::Source ||
5199 S[i].role == Role::Synchronization;
5201 std::uint64_t last_cnvg_events = 0;
5202 bool converged =
false;
5207 for (std::size_t p = 0; p < place_nodes.size(); ++p)
5208 if (qplace[p]) place_try_start(p);
5213 Observations obs(M, K, o.tranfilter ==
"mser5", o.mserbatch > 0 ? o.mserbatch : 5);
5214 for (std::size_t i = 0; i < M; ++i)
5215 if (S[i].role == Role::Source || S[i].role == Role::Synchronization) obs.in_mser[i] = 0;
5216 std::uint64_t mser_interval = max_events / 1000ULL;
5217 if (mser_interval < 1) mser_interval = 1;
5218 std::uint64_t last_mser_events = 0;
5231 const bool transient_run = o.has_timespan && std::isfinite(o.t1) && o.t1 > o.t0;
5232 const double horizon = transient_run ? o.t1 : std::numeric_limits<double>::infinity();
5235 const double tran_points = (o.tranobs > 0) ?
static_cast<double>(o.tranobs) : 1000.0;
5236 const double tran_interval = transient_run ? (o.t1 - o.t0) / tran_points : 0.0;
5237 double next_tran_sample = transient_run ? o.t0 + tran_interval : 0.0;
5238 double last_tran_time = transient_run ? o.t0 : 0.0;
5239 std::vector<double> tran_times;
5240 std::vector<std::vector<std::vector<double>>> tran_q(M, std::vector<std::vector<double>>(K));
5241 std::vector<std::vector<std::vector<double>>> tran_u(M, std::vector<std::vector<double>>(K));
5242 std::vector<std::vector<std::vector<double>>> tran_t(M, std::vector<std::vector<double>>(K));
5243 std::vector<std::vector<double>> last_tran_q(M, std::vector<double>(K, 0.0));
5244 std::vector<std::vector<double>> last_tran_b(M, std::vector<double>(K, 0.0));
5245 std::vector<std::vector<double>> last_tran_c(M, std::vector<double>(K, 0.0));
5256 std::map<std::vector<int>,
double> histogram;
5257 std::vector<std::pair<double, std::vector<int>>> trajectory;
5258 double hist_last = 0.0;
5261 const bool want_traj = transient_run || o.export_trajectory;
5262 const bool want_hist = transient_run || o.export_histogram || want_traj;
5264 auto joint_state = [&]() {
5265 std::vector<int> row(M * K, 0);
5266 for (std::size_t i = 0; i < M; ++i)
5267 for (std::size_t r = 0; r < K; ++r)
5268 row[i * K + r] =
static_cast<int>(acc.qlen[i][r] + acc.held[i][r] + 0.5);
5271 auto hist_accumulate = [&]() {
5272 if (!want_hist)
return;
5273 const double dt = now - hist_last;
5275 const std::vector<int> row = joint_state();
5276 histogram[row] += dt;
5277 if (want_traj) trajectory.push_back(std::make_pair(hist_last, row));
5282 auto tran_sample = [&]() {
5283 const double dt = now - last_tran_time;
5284 tran_times.push_back(now);
5285 for (std::size_t i = 0; i < M; ++i)
5286 for (std::size_t r = 0; r < K; ++r) {
5288 tran_q[i][r].push_back((acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt);
5289 const double c = S[i].util_peak;
5290 tran_u[i][r].push_back(
5291 (S[i].role == Role::Delay)
5292 ? (acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt
5293 : (acc.tot_busy[i][r] - last_tran_b[i][r]) / (dt * c));
5294 tran_t[i][r].push_back((acc.completed[i][r] - last_tran_c[i][r]) / dt);
5296 tran_q[i][r].push_back(acc.qlen[i][r] + acc.held[i][r]);
5297 tran_u[i][r].push_back(0.0);
5298 tran_t[i][r].push_back(0.0);
5300 last_tran_q[i][r] = acc.tot_qlen[i][r];
5301 last_tran_b[i][r] = acc.tot_busy[i][r];
5302 last_tran_c[i][r] = acc.completed[i][r];
5304 last_tran_time = now;
5308 while (!evq.empty() && (transient_run || total_completions < max_events)) {
5309 const Event ev = evq.top();
5310 if (transient_run && ev.t > horizon)
break;
5317 if (want_hist) hist_accumulate();
5318 if (transient_run) {
5319 while (now >= next_tran_sample && next_tran_sample <= horizon) {
5320 const double save = now;
5321 now = next_tran_sample;
5322 for (std::size_t a = 0; a < M; ++a) {
5323 if (S[a].ps) ps_advance(a);
5324 for (std::size_t r = 0; r < K; ++r) {
5325 acc.update_qlen(a, r, now);
5326 acc.update_busy(a, r, now);
5330 next_tran_sample += tran_interval;
5335 if (ev.kind == EV_BREAKDOWN) {
5340 StationState& s = S[ev.station];
5342 ps_advance(ev.station);
5344 sd_advance(ev.station);
5347 ps_reschedule(ev.station);
5349 sd_reschedule(ev.station);
5359 nxt.t = now + (s.up ? s.failure_time.next_at(g_aux[ev.station], now)
5360 : s.repair_time.next_at(g_aux[ev.station], now));
5362 nxt.station = ev.station;
5367 if (ev.kind == EV_FIRING) {
5370 if (ev.station >= transitions.size() || live_fire[ev.station] != ev.tag)
continue;
5371 SpnTransition& tr = transitions[ev.station];
5372 const std::vector<double> tok = flat_avail();
5373 const SpnMode& m = tr.modes[ev.cls];
5376 tr.fired[ev.cls] += 1.0;
5377 ++total_completions;
5391 if (ev.kind == EV_PLACE_SVC) {
5392 const std::size_t p = ev.station;
5393 const std::size_t r = ev.cls;
5394 const std::size_t ist = place_station[p];
5396 acc.update_busy(ist, r, now);
5397 place_insvc[p][r] -= 1.0;
5398 acc.busy[ist][r] = place_insvc[p][r];
5407 if (ev.kind == EV_SWITCHOVER) {
5408 StationState& sp = S[ev.station];
5409 sp.poll_switching =
false;
5410 sp.poll_at = ev.cls;
5411 poll_open_visit(sp);
5413 for (
const Job& j : sp.buffer)
5414 if (j.cls == sp.poll_at) {
5419 poll_serve(ev.station);
5421 poll_advance(ev.station);
5425 if (ev.kind == EV_RETRIAL) {
5426 StationState& sr = S[ev.station];
5427 const double dt = now - sr.orbit_last;
5429 for (std::size_t r = 0; r < K; ++r) sr.tot_orbit[r] += sr.orbit_size[r] * dt;
5430 sr.orbit_last = now;
5431 sr.orbit_size[ev.cls] -= 1.0;
5432 sr.retried[ev.cls] += 1.0;
5435 pending_retry =
true;
5436 admit(ev.station, ev.job, M);
5440 if (ev.kind == EV_SETUP) {
5441 StationState& ss = S[ev.station];
5446 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[ev.station][r];
5447 if (held > 0.0 || now + 1e-12 < ss.delayoff_at)
continue;
5448 ss.setup_on =
false;
5449 ss.delayoff_at = std::numeric_limits<double>::infinity();
5452 if (ss.setup_on)
continue;
5453 ss.setup_running =
false;
5455 ss.delayoff_at = std::numeric_limits<double>::infinity();
5456 for (std::size_t sl = 0; sl < ss.nservers && !ss.buffer.empty(); ++sl)
5457 if (!ss.server_busy[sl] && !ss.server_blocked[sl] && !ss.server_held[sl] &&
5458 buffer_has_for_slot(ev.station, sl)) {
5459 Job nextjob = buffer_pop_for_slot(ev.station, sl);
5460 start_service(ev.station, sl, nextjob);
5462 sd_reschedule(ev.station);
5466 if (ev.kind == EV_RENEGE) {
5470 StationState& sr = S[ev.station];
5471 std::size_t at = sr.buffer.size();
5472 for (std::size_t j = 0; j < sr.buffer.size(); ++j)
5473 if (sr.buffer[j].id == ev.tag) {
5477 if (at == sr.buffer.size())
continue;
5478 const std::size_t rc = sr.buffer[at].cls;
5479 sd_advance(ev.station);
5480 acc.update_qlen(ev.station, rc, now);
5481 acc.qlen[ev.station][rc] -= 1.0;
5482 bp.track(ev.station, rc, -1, now);
5483 sr.buffer.erase(sr.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
5484 if (sr.sched != SchedStrategy::FSP)
5485 std::make_heap(sr.buffer.begin(), sr.buffer.end(), sr.cmp);
5486 reneged[ev.station][rc] += 1.0;
5487 sd_reschedule(ev.station);
5491 if (ev.kind == EV_ARRIVAL) {
5496 const int mark = arrival[ev.cls].last_mark();
5501 std::size_t batch = 1;
5502 if (arrival[ev.cls].batched()) {
5503 const int b = arrival[ev.cls].last_batch();
5504 if (b > 0) batch =
static_cast<std::size_t
>(b);
5505 }
else if (has_arr_batch[ev.cls]) {
5506 const double sampled = arr_batch[ev.cls].next(g_arr_batch[ev.cls]);
5507 const double rounded = std::floor(sampled + 0.5);
5508 if (!(rounded >= 1.0) || std::fabs(sampled - rounded) > 1e-9)
5510 "SolverLDES (native engine): the arrival batch of class '" +
5511 sn.classes[ev.cls].name +
"' sampled " + std::to_string(sampled) +
5512 ", which is not a positive integer; a batch-size law must be "
5513 "supported on {1,2,...}");
5514 batch =
static_cast<std::size_t
>(rounded);
5517 const double gap = slot_snap(arrival[ev.cls].next_at(g_arr[ev.cls], now),
5518 "interarrival time");
5521 if (!(gap > 0.0) && arrival[ev.cls].time_varying())
continue;
5524 nxt.station = ev.station;
5530 std::size_t arv_cls = ev.cls;
5531 if (mark > 0 &&
static_cast<std::size_t
>(mark) <= mark_class.size())
5532 arv_cls = mark_class[
static_cast<std::size_t
>(mark) - 1];
5539 for (std::size_t b = 0; b < batch; ++b) {
5540 const RouteEntry e = draw_node_route(sn.station_to_node[ev.station], arv_cls);
5544 deliver(e.node, job, M);
5550 const std::size_t i = ev.station;
5551 StationState& s = S[i];
5565 if (s.bmsp_tag != ev.tag)
continue;
5566 const std::size_t r = s.bmsp_cls;
5567 const std::size_t want = s.bmsp_pending;
5568 std::vector<Job> served;
5569 for (std::size_t d = 0; d < want && !s.buffer.empty(); ++d)
5570 served.push_back(buffer_pop(i));
5571 for (std::size_t d = 0; d < served.size(); ++d) {
5572 const Job& sj = served[d];
5573 acc.update_qlen(i, sj.cls, now);
5574 acc.qlen[i][sj.cls] -= 1.0;
5575 bp.track(i, sj.cls, -1, now);
5576 acc.completed[i][sj.cls] += 1.0;
5577 acc.resp_sum[i][sj.cls] += now - sj.t_arr + sj.region_wait;
5578 if (want_respt) resp_samples[i][sj.cls].push_back(now - sj.t_arr + sj.region_wait);
5579 acc.resp_cnt[i][sj.cls] += 1.0;
5580 ++total_completions;
5585 if (!s.buffer.empty()) {
5588 acc.update_busy(i, r, now);
5589 if (acc.busy[i][r] > 0.0) acc.busy[i][r] -= 1.0;
5590 s.server_busy[0] =
false;
5595 for (std::size_t d = 0; d < served.size(); ++d) {
5596 const Job& sj = served[d];
5597 const RouteEntry re2 = draw_node_route(sn.station_to_node[i], sj.cls);
5598 if (region_of[i] >= 0) {
5599 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
5600 const bool inside = !re2.sink && re2.station < M &&
5601 region_of[re2.station] == region_of[i];
5603 regions[rg].update(now);
5604 regions[rg].leave(sj.cls);
5605 regions[rg].completed[sj.cls] += 1.0;
5610 moved.cls = re2.cls;
5611 moved.t_sys = sj.t_sys;
5612 moved.parent = sj.parent;
5613 moved.call = sj.call;
5614 deliver(re2.node, moved, i);
5616 if (total_completions >= max_events)
break;
5620 if (s.role == Role::Delay) {
5622 if (track_delay_jobs) {
5628 std::size_t at = delay_live[i].size();
5629 for (std::size_t j = 0; j < delay_live[i].size(); ++j)
5630 if (delay_live[i][j].
id == job.id) {
5634 if (at == delay_live[i].size())
continue;
5635 if (delay_live[i][at].tag != ev.tag)
continue;
5636 delay_live[i].erase(delay_live[i].begin() +
static_cast<std::ptrdiff_t
>(at));
5640 std::size_t idx = s.ps_jobs.size();
5641 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
5642 if (s.ps_jobs[j].tag == ev.tag) {
5649 if (idx == s.ps_jobs.size())
continue;
5650 const PsJob& pj = s.ps_jobs[idx];
5652 job.t_arr = pj.t_arr;
5653 job.region_wait = pj.region_wait;
5654 job.t_sys = pj.t_sys;
5655 job.parent = pj.parent;
5656 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(idx));
5660 if (s.pas_tag != ev.tag || s.pas_list.empty())
continue;
5661 job = pas_complete(i);
5662 acc.update_busy(i, job.cls, now);
5663 if (acc.busy[i][job.cls] > 0.0) acc.busy[i][job.cls] -= 1.0;
5668 if (!s.server_busy[ev.slot] || s.server_tag[ev.slot] != ev.tag)
continue;
5670 job = s.server[ev.slot];
5671 s.server_busy[ev.slot] =
false;
5672 s.server_tag[ev.slot] = 0;
5673 const double freed = release_slots(i, ev.slot);
5674 acc.update_busy(i, job.cls, now);
5675 acc.busy[i][job.cls] -= freed;
5679 acc.update_qlen(i, job.cls, now);
5680 acc.qlen[i][job.cls] -= 1.0;
5681 bp.track(i, job.cls, -1, now);
5682 acc.completed[i][job.cls] += 1.0;
5683 acc.resp_sum[i][job.cls] += now - job.t_arr + job.region_wait;
5684 if (want_respt) resp_samples[i][job.cls].push_back(now - job.t_arr + job.region_wait);
5685 acc.resp_cnt[i][job.cls] += 1.0;
5686 ++total_completions;
5689 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
5690 Job nextjob = buffer_pop(i);
5692 pj.cls = nextjob.cls;
5693 pj.priority = nextjob.priority;
5694 pj.t_arr = nextjob.t_arr;
5695 pj.region_wait = nextjob.region_wait;
5696 pj.t_sys = nextjob.t_sys;
5697 pj.total = nextjob.service;
5698 pj.remaining = nextjob.service;
5699 pj.parent = nextjob.parent;
5700 s.ps_jobs.push_back(pj);
5705 const RouteEntry re = draw_node_route(sn.station_to_node[i], job.cls);
5706 const bool to_sink = re.sink;
5707 const std::size_t dst = re.station, dcls = re.cls;
5721 const RouteEntry fb = has_immfeed ? resolve_forced_dest(re) : re;
5722 const std::size_t fcls = fb.cls;
5723 if (has_immfeed && !fb.sink && fb.station == i && s.role == Role::Queue && !s.ps &&
5724 !s.pas && ev.slot < s.nservers && fcls < sn.immfeed[i].size() && sn.immfeed[i][fcls]) {
5727 fed.t_sys = job.t_sys;
5728 fed.parent = job.parent;
5730 fed.priority = classprio[fcls];
5731 if (draw_at_arrival(i, fcls)) {
5732 fed.service = slot_snap(s.svc[fcls].next_at(g_svc[i][fcls], now),
"service time");
5733 fed.remaining = fed.service;
5736 fed.rank = g_siro.aux.next_double();
5737 fed.deadline = now + classdeadline[fcls];
5740 acc.update_qlen(i, fcls, now);
5741 acc.qlen[i][fcls] += 1.0;
5747 acc.arrived[i][fcls] += 1.0;
5748 bp.track(i, fcls, +1, now);
5749 start_service(i, ev.slot, fed);
5751 if (total_completions >= max_events)
break;
5770 if (has_sync_call && sync_reply[job.cls] < K &&
5771 !is_reply_signal[resolve_final_cls(re.node, dcls)] &&
5772 s.role == Role::Queue && !s.ps && !s.pas && ev.slot < s.nservers) {
5773 const std::uint64_t call = ++next_call;
5779 pending_reply[call] = pc;
5780 s.server_held[ev.slot] =
true;
5781 s.held_cls[ev.slot] = job.cls;
5782 acc.update_busy(i, job.cls, now);
5783 acc.busy[i][job.cls] += 1.0;
5786 acc.update_qlen(i, job.cls, now);
5787 acc.held[i][job.cls] += 1.0;
5790 moved.t_sys = job.t_sys;
5791 moved.parent = job.parent;
5793 deliver(re.node, moved, i);
5794 if (total_completions >= max_events)
break;
5803 if (!to_sink && dst < M && s.role == Role::Queue && !s.ps && ev.slot < s.nservers &&
5804 !dest_has_room(dst, dcls)) {
5809 s.server_blocked[ev.slot] =
true;
5810 s.blocked_job[ev.slot] = held;
5811 s.blocked_dest[ev.slot] = dst;
5812 s.blocked_dest_cls[ev.slot] = dcls;
5819 acc.update_qlen(dst, dcls, now);
5820 acc.qlen[dst][dcls] += 1.0;
5821 S[dst].blocked_at[dcls] += 1.0;
5827 acc.update_busy(i, job.cls, now);
5828 acc.busy[i][job.cls] += 1.0;
5829 blocked_count[i][job.cls] += 1.0;
5841 }
else if (s.role == Role::Queue && !s.ps && !s.pas && !s.server_blocked.empty() &&
5842 ev.slot < s.nservers && !s.server_blocked[ev.slot] &&
5843 !s.server_held[ev.slot]) {
5844 serve_next_on_slot(i, ev.slot);
5851 std::uint64_t spawn_parent = 0;
5852 if (has_spawn && spawn_of[job.cls] < K && spawn_joins_at[spawn_of[job.cls]]) {
5853 spawn_parent = job.parent;
5860 const int dep_rg = region_of[i];
5862 regions[
static_cast<std::size_t
>(dep_rg)].update(now);
5863 regions[
static_cast<std::size_t
>(dep_rg)].leave(job.cls);
5869 if (has_spawn && spawn_of[job.cls] < K) {
5870 const std::size_t scls = spawn_of[job.cls];
5873 spawned.t_sys = now;
5878 spawned.parent = spawn_parent;
5879 admit(i, spawned, M);
5884 moved.t_sys = job.t_sys;
5885 moved.parent = job.parent;
5889 moved.call = job.call;
5890 hop_old_cls = job.cls;
5891 hop_internal =
false;
5892 deliver(re.node, moved, i);
5898 const bool stays_inside = hop_internal;
5899 hop_internal =
false;
5900 if (!stays_inside) {
5901 regions[
static_cast<std::size_t
>(dep_rg)].completed[job.cls] += 1.0;
5902 release_region(
static_cast<std::size_t
>(dep_rg));
5907 if (s.has_setup && s.setup_on) {
5909 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[i][r];
5910 if (!(held > 0.0)) {
5912 s.delayoff_time.disabled() ? 0.0 : s.delayoff_time.next(g_aux[i]);
5913 s.delayoff_at = now + idle;
5915 e.t = s.delayoff_at;
5925 if (total_completions >= max_events)
break;
5926 if (cnvg.enabled() && (total_completions - last_cnvg_events) >= cnvg.interval()) {
5927 for (std::size_t a = 0; a < M; ++a) {
5928 if (S[a].ps) ps_advance(a);
5929 for (std::size_t r = 0; r < K; ++r) {
5930 acc.update_qlen(a, r, now);
5931 acc.update_busy(a, r, now);
5934 cnvg.finalize_batch(acc, servers_of, now);
5935 last_cnvg_events = total_completions;
5936 if (cnvg.converged(off_of)) {
5941 if ((total_completions - last_mser_events) >= mser_interval) {
5942 for (std::size_t a = 0; a < M; ++a) {
5943 if (S[a].ps) ps_advance(a);
5944 for (std::size_t r = 0; r < K; ++r) {
5945 acc.update_qlen(a, r, now);
5946 acc.update_busy(a, r, now);
5949 obs.collect(acc, now);
5950 last_mser_events = total_completions;
5955 for (std::size_t i = 0; i < M; ++i) {
5956 if (S[i].ps) ps_advance(i);
5957 for (std::size_t r = 0; r < K; ++r) {
5958 acc.update_qlen(i, r, now);
5959 acc.update_busy(i, r, now);
5964 const Truncation tr = obs.truncate();
5965 const double sim_time = now - tr.warmup_end;
5966 const double elapsed = tr.applied ? (now - obs.time[tr.index]) : sim_time;
5972 res.nchains = sn.nchains;
5973 for (std::size_t i = 0; i < M; ++i) res.station_names.push_back(sn.stations[i].name);
5974 for (std::size_t r = 0; r < K; ++r) res.class_names.push_back(sn.classes[r].name);
5975 res.QN = Matrix<double>(M, K, 0.0);
5976 res.UN = Matrix<double>(M, K, 0.0);
5977 res.RN = Matrix<double>(M, K, 0.0);
5978 res.TN = Matrix<double>(M, K, 0.0);
5979 res.CN = Matrix<double>(1, K, 0.0);
5980 res.XN = Matrix<double>(1, K, 0.0);
5981 res.AN = Matrix<double>(M, K, 0.0);
5982 res.WN = Matrix<double>(M, K, 0.0);
5984 for (std::size_t i = 0; i < M; ++i) {
5985 for (std::size_t r = 0; r < K; ++r) {
5986 if (S[i].role == Role::Source) {
5987 res.TN(i, r) = lambda[r];
5990 if (S[i].off[r])
continue;
5991 if (elapsed > 0.0) {
5992 const double q0 = tr.applied ? obs.qt[i][r][tr.index] : 0.0;
5993 const double b0 = tr.applied ? obs.bt[i][r][tr.index] : 0.0;
5994 const double c0 = tr.applied ? obs.cmp[i][r][tr.index] : 0.0;
5995 res.QN(i, r) = (acc.tot_qlen[i][r] - q0) / elapsed;
5996 res.TN(i, r) = (acc.completed[i][r] - c0) / elapsed;
5997 if (S[i].role == Role::Delay) {
6006 res.UN(i, r) = res.TN(i, r) * S[i].class_mean[r] / S[i].gd_peak[r];
6007 }
else if (S[i].has_cd || has_gd) {
6027 (S[i].has_cd || !S[i].lld.empty()) ? S[i].util_peak : 1.0;
6028 const double peak = base * S[i].gd_peak[r];
6030 (peak > 0.0) ? res.TN(i, r) * S[i].class_mean[r] / peak : 0.0;
6032 res.UN(i, r) = (acc.tot_busy[i][r] - b0) / (elapsed * S[i].util_peak);
6035 if (acc.resp_cnt[i][r] > 0.0) {
6036 res.RN(i, r) = acc.resp_sum[i][r] / acc.resp_cnt[i][r];
6037 }
else if (sn.stations[i].nodetype == NodeType::Place && res.TN(i, r) > 0.0) {
6045 res.RN(i, r) = res.QN(i, r) / res.TN(i, r);
6052 if (elapsed > 0.0) res.AN(i, r) = acc.arrived[i][r] / elapsed;
6053 res.WN(i, r) = res.RN(i, r);
6061 if (!sn.fj.empty()) {
6062 res.DropRateJoin = Matrix<double>(M, K, 0.0);
6063 for (std::size_t i = 0; i < M; ++i)
6064 for (std::size_t r = 0; r < K; ++r) {
6065 const double d0 = tr.applied ? obs.drp[i][r][tr.index] : 0.0;
6067 res.DropRateJoin(i, r) = (acc.join_dropped[i][r] - d0) / elapsed;
6070 for (std::size_t r = 0; r < K; ++r) {
6071 if (sim_time > 0.0) res.XN(0, r) = sys_completed[r] / sim_time;
6072 if (sys_resp_cnt[r] > 0.0) res.CN(0, r) = sys_resp_sum[r] / sys_resp_cnt[r];
6082 const std::size_t rs = sn.classes[r].refstat;
6083 if (res.XN(0, r) == 0.0 && rs != 0 && rs <= M &&
6084 sn.stations[rs - 1].nodetype == NodeType::Place)
6085 res.XN(0, r) = res.TN(rs - 1, r);
6089 for (std::size_t ti = 0; ti < bp.targets().size(); ++ti) {
6091 out.
name = bp.targets()[ti].name;
6092 out.stations = bp.targets()[ti].stations;
6093 out.job_class = bp.targets()[ti].job_class;
6094 for (std::size_t oi = 0; oi < static_cast<std::size_t>(bp.orders()); ++oi) {
6095 out.mean.push_back(bp.mean(ti, oi));
6096 out.count.push_back(bp.count(ti, oi));
6098 res.busy_periods.push_back(out);
6102 if (transient_run) {
6103 now = std::min(now, horizon);
6106 res.QNt.assign(M, std::vector<Matrix<double>>(K));
6107 res.UNt.assign(M, std::vector<Matrix<double>>(K));
6108 res.TNt.assign(M, std::vector<Matrix<double>>(K));
6109 for (std::size_t i = 0; i < M; ++i)
6110 for (std::size_t r = 0; r < K; ++r) {
6111 const std::size_t n = tran_times.size();
6114 Matrix<double> q(n, 2, 0.0), u(n, 2, 0.0), t(n, 2, 0.0);
6115 for (std::size_t k = 0; k < n; ++k) {
6116 q(k, 0) = tran_q[i][r][k];
6117 q(k, 1) = tran_times[k];
6118 u(k, 0) = tran_u[i][r][k];
6119 u(k, 1) = tran_times[k];
6120 t(k, 0) = tran_t[i][r][k];
6121 t(k, 1) = tran_times[k];
6127 res.stopping_reason =
"max_time";
6131 res.respTimeSamples.assign(M, std::vector<std::vector<double>>(K));
6132 for (std::size_t i = 0; i < M; ++i)
6133 for (std::size_t r = 0; r < K; ++r) res.respTimeSamples[i][r] = resp_samples[i][r];
6141 if (want_hist && !transient_run) hist_accumulate();
6142 if (!histogram.empty()) {
6143 res.histogram_space = Matrix<double>(histogram.size(), M * K, 0.0);
6144 res.histogram_time = Matrix<double>(histogram.size(), 1, 0.0);
6145 std::size_t row = 0;
6146 for (
const auto& kv : histogram) {
6147 for (std::size_t c = 0; c < kv.first.size(); ++c)
6148 res.histogram_space(row, c) = kv.first[c];
6149 res.histogram_time(row, 0) = kv.second;
6153 if (!trajectory.empty()) {
6154 res.traj_space = Matrix<double>(trajectory.size(), M * K, 0.0);
6155 res.traj_time = Matrix<double>(trajectory.size(), 1, 0.0);
6156 for (std::size_t k = 0; k < trajectory.size(); ++k) {
6157 for (std::size_t c = 0; c < trajectory[k].second.size(); ++c)
6158 res.traj_space(k, c) = trajectory[k].second[c];
6159 res.traj_time(k, 0) = trajectory[k].first;
6164 for (
const auto& kv : caches) {
6165 const CacheState& cs = kv.second;
6167 cm.
hit = Matrix<double>(1, K, 0.0);
6168 cm.miss = Matrix<double>(1, K, 0.0);
6173 if (cs.has_retrieval) cm.delayed = Matrix<double>(1, K, 0.0);
6174 for (std::size_t r = 0; r < K; ++r) {
6175 const double dl = cs.has_retrieval ? cs.delayed[r] : 0.0;
6176 const double tot = cs.hits[r] + cs.misses[r] + dl;
6178 cm.hit(0, r) = cs.hits[r] / tot;
6179 cm.miss(0, r) = cs.misses[r] / tot;
6180 if (cs.has_retrieval) cm.delayed(0, r) = dl / tot;
6199 const double released = std::accumulate(cs.delayed.begin(), cs.delayed.end(), 0.0);
6200 if (cs.has_retrieval && cs.completed_fetches + released > 0.0) {
6201 cm.latency = Matrix<double>(1, K, 0.0);
6202 const double mean_wait = (cs.total_fetch_time + cs.delayed_wait)
6203 / (cs.completed_fetches + released);
6204 for (std::size_t r = 0; r < K; ++r)
6205 cm.latency(0, r) = (cs.hits[r] + cs.misses[r] + cs.delayed[r] > 0.0)
6207 : std::numeric_limits<double>::quiet_NaN();
6209 res.cache_metrics[sn.nodes[cs.node - 1].name] = cm;
6213 if (!regions.empty()) {
6214 const std::size_t NR = regions.size();
6216 res.QNfcr = Matrix<double>(NR, K, 0.0);
6217 res.TNfcr = Matrix<double>(NR, K, 0.0);
6218 res.WeightNfcr = Matrix<double>(NR, K, 0.0);
6219 res.MemOccNfcr = Matrix<double>(NR, K, 0.0);
6220 res.DropRateNfcr = Matrix<double>(NR, K, 0.0);
6221 for (std::size_t g = 0; g < NR; ++g) {
6222 regions[g].update(now);
6223 for (std::size_t r = 0; r < K; ++r) {
6224 if (sim_time > 0.0) {
6228 res.QNfcr(g, r) = regions[g].tot_jobs[r] / sim_time;
6229 res.WeightNfcr(g, r) = regions[g].tot_weight[r] / sim_time;
6230 res.MemOccNfcr(g, r) = regions[g].tot_mem[r] / sim_time;
6231 res.TNfcr(g, r) = regions[g].completed[r] / sim_time;
6232 res.DropRateNfcr(g, r) = regions[g].dropped[r] / sim_time;
6243 res.total_simulated_events =
static_cast<long long>(total_completions);
6248 double any_balk = 0.0, any_renege = 0.0;
6249 for (std::size_t i = 0; i < M; ++i)
6250 for (std::size_t r = 0; r < K; ++r) {
6251 any_balk += balked[i][r];
6252 any_renege += reneged[i][r];
6254 double any_orbit = 0.0;
6255 for (std::size_t i = 0; i < M; ++i)
6256 for (std::size_t r = 0; r < K; ++r) any_orbit += S[i].retried[r];
6257 if (any_orbit > 0.0) {
6258 res.retriedCustomers = Matrix<double>(M, K, 0.0);
6259 res.retrialDropped = Matrix<double>(M, K, 0.0);
6260 res.avgOrbitSize = Matrix<double>(M, K, 0.0);
6261 for (std::size_t i = 0; i < M; ++i) {
6262 const double dt = now - S[i].orbit_last;
6263 for (std::size_t r = 0; r < K; ++r) {
6264 double tot = S[i].tot_orbit[r];
6265 if (dt > 0.0) tot += S[i].orbit_size[r] * dt;
6266 res.retriedCustomers(i, r) = S[i].retried[r];
6267 res.retrialDropped(i, r) = S[i].retrial_lost[r];
6268 if (sim_time > 0.0) res.avgOrbitSize(i, r) = tot / sim_time;
6273 if (any_balk > 0.0 || any_renege > 0.0) {
6274 res.balkedCustomers = Matrix<double>(M, K, 0.0);
6275 res.renegedCustomers = Matrix<double>(M, K, 0.0);
6276 res.renegingRate = Matrix<double>(M, K, 0.0);
6277 for (std::size_t i = 0; i < M; ++i)
6278 for (std::size_t r = 0; r < K; ++r) {
6279 res.balkedCustomers(i, r) = balked[i][r];
6280 res.renegedCustomers(i, r) = reneged[i][r];
6281 if (sim_time > 0.0) res.renegingRate(i, r) = reneged[i][r] / sim_time;
6285 res.converged = converged;
6286 res.convergence_batches = cnvg.batches();
6287 if (!transient_run) res.stopping_reason = converged ?
"convergence" :
"max_events";
6288 res.engine =
"native";
6289 res.method = o.method;