839 ldes_engine_reject(
sn, o);
841 const std::size_t M =
sn.nstations, K =
sn.nclasses;
842 if (M == 0 || K == 0)
throw InputError(
"SolverLDES (native engine): empty model");
844 const std::uint64_t max_events =
847 throw InputError(
"SolverLDES (native engine): the completion budget is zero");
850 const std::uint64_t base =
851 (o.
seed >= 0) ?
static_cast<std::uint64_t
>(o.
seed) : std::random_device{}();
861 const bool slotted = o.
slotted;
863 auto slot_snap = [&](
double v,
const char* what) ->
double {
864 if (!slotted || v == 0.0)
return v;
865 const double slots = v / slot_len;
866 const double rounded = std::floor(slots + 0.5);
867 if (rounded < 1.0 || std::fabs(slots - rounded) > 1e-9 * std::max(1.0, slots))
868 throw InputError(std::string(
"SolverLDES (native engine): slotted mode sampled a ") +
869 what +
" of " + std::to_string(v) +
870 ", which is not a positive multiple of the slot length " +
871 std::to_string(slot_len) +
872 "; a discrete-time model needs lattice-valued interarrival and "
873 "service times, e.g. Geometric or Det on an integral slot count");
874 return rounded * slot_len;
901 const long long seed_ll =
static_cast<long long>(base);
902 const long long Kll =
static_cast<long long>(K);
903 std::size_t num_sources = 0, num_service_nodes = 0;
904 std::vector<std::size_t> svc_index(M, 0);
905 for (std::size_t i = 0; i < M; ++i) {
906 if (
sn.stations[i].nodetype == NodeType::Source) {
909 svc_index[i] = num_service_nodes++;
912 const long long nsrc =
static_cast<long long>(num_sources);
913 const long long nsvc =
static_cast<long long>(num_service_nodes);
915 std::vector<Rng> g_arr;
917 for (std::size_t k = 0; k < K; ++k) {
918 const long long off = (
static_cast<long long>(k)) * 10;
919 g_arr.push_back(Rng(seed_ll, off, off + 2000));
921 std::vector<std::vector<Rng>> g_svc;
923 for (std::size_t i = 0; i < M; ++i) {
924 std::vector<Rng> row;
926 for (std::size_t k = 0; k < K; ++k) {
927 const long long off =
928 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
static_cast<long long>(k)) *
930 row.push_back(Rng(seed_ll, off));
932 g_svc.push_back(row);
937 std::vector<std::vector<std::vector<Rng>>> g_hsvc(M);
938 for (std::size_t i = 0; i < M; ++i) {
939 const std::size_t nT =
sn.stations[i].server_types.size();
940 g_hsvc[i].resize(nT);
941 for (std::size_t t = 0; t < nT; ++t) {
942 g_hsvc[i][t].reserve(K);
943 for (std::size_t k = 0; k < K; ++k) {
944 const long long off =
945 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
946 static_cast<long long>(k)) * 10 +
947 600000 +
static_cast<long long>(t) * 137;
948 g_hsvc[i][t].push_back(Rng(seed_ll, off));
952 std::vector<Rng> g_aux;
954 for (std::size_t i = 0; i < M; ++i) {
955 const long long off = ((nsrc + nsvc +
static_cast<long long>(svc_index[i])) * Kll) * 10;
956 g_aux.push_back(Rng(seed_ll, off));
958 Rng g_routing(seed_ll, 900000);
961 Rng g_spn(seed_ll, 5000);
965 Rng g_qplace(seed_ll, 5001);
970 Rng g_fork(seed_ll, 88888);
978 auto fork_is_variable = [&](std::size_t node) ->
bool {
980 if (fp == 0)
return false;
981 const double tpl =
sn.nodes[node - 1].tasks_per_link;
982 for (std::size_t k = 0; k < fp->
fan_out_link.rows(); ++k)
983 for (std::size_t r = 0; r < fp->
fan_out_link.cols(); ++r) {
985 if (p == 0.0)
continue;
986 if (p != 1.0)
return true;
996 for (std::size_t e = 0; e < d.params.size(); ++e) tot += d.params[e];
997 const double u = g_fork.aux.next_double() * tot;
999 for (std::size_t e = 0; e < d.params.size(); ++e) {
1000 accp += d.params[e];
1002 return static_cast<int>((d.trace.empty() ?
static_cast<double>(e + 1) : d.trace[e]) +
1005 return static_cast<int>(
1006 (d.trace.empty() ?
static_cast<double>(d.params.size()) : d.trace.back()) + 0.5);
1010 std::vector<StationState> S(M);
1011 std::size_t source_st = M;
1012 std::vector<int> classprio(K, 0);
1013 std::vector<double> classdeadline(K, std::numeric_limits<double>::infinity());
1014 for (std::size_t r = 0; r < K; ++r) {
1015 classprio[r] =
sn.classes[r].prio;
1016 classdeadline[r] =
sn.classes[r].deadline;
1019 for (std::size_t i = 0; i < M; ++i) {
1020 const auto& st =
sn.stations[i];
1021 StationState& s = S[i];
1023 s.off.assign(K,
true);
1024 s.class_mean.assign(K, 0.0);
1025 s.weight.assign(K, 1.0);
1026 for (std::size_t r = 0; r < K; ++r)
1029 if (st.nodetype == NodeType::Source) {
1030 s.role = Role::Source;
1032 }
else if (st.nodetype == NodeType::Fork || st.nodetype == NodeType::Join ||
1033 st.nodetype == NodeType::Place || st.nodetype == NodeType::Transition) {
1036 s.role = Role::Synchronization;
1042 if (st.nodetype == NodeType::Join) s.off.assign(K,
false);
1053 if (st.nodetype == NodeType::Place) {
1054 s.off.assign(K,
false);
1055 if (std::isfinite(st.nservers))
1056 s.nservers =
static_cast<std::size_t
>(st.nservers + 0.5);
1058 }
else if (st.nodetype == NodeType::Delay || st.sched == SchedStrategy::INF) {
1059 s.role = Role::Delay;
1061 s.role = Role::Queue;
1062 s.ps = is_ps_family(st.sched);
1063 s.preemptive = is_preemptive(st.sched);
1064 s.resume = is_preemptive_resume(st.sched);
1066 s.size_ordered = (st.sched == SchedStrategy::SJF ||
1067 st.sched == SchedStrategy::LJF ||
1068 st.sched == SchedStrategy::SRPT ||
1069 st.sched == SchedStrategy::SRPTPRIO ||
1070 st.sched == SchedStrategy::PSJF ||
1071 st.sched == SchedStrategy::LRPT ||
1072 st.sched == SchedStrategy::FSP);
1073 const double c = st.nservers;
1074 if (!(c >= 1.0) || !std::isfinite(c))
1075 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1076 "' has a server count that is neither finite nor at least one");
1077 s.nservers =
static_cast<std::size_t
>(c + 0.5);
1079 s.classcap.assign(K, std::numeric_limits<double>::infinity());
1080 s.blocked_at.assign(K, 0.0);
1082 for (std::size_t r = 0; r < K; ++r)
1083 if (i <
sn.droprule.size() && r <
sn.droprule[i].size())
1084 s.droprule[r] =
sn.droprule[i][r];
1085 for (std::size_t r = 0; r < K; ++r)
1086 if (i <
sn.classcap.size() && r <
sn.classcap[i].size())
1087 s.classcap[r] =
sn.classcap[i][r];
1088 if (st.sched == SchedStrategy::LPS) s.lps_limit = s.nservers;
1092 s.parallelism.assign(K, 1);
1093 for (std::size_t r = 0; r < K && r < st.server_parallelism.size(); ++r)
1094 if (st.server_parallelism[r] > 1) {
1095 s.parallelism[r] = st.server_parallelism[r];
1096 s.has_parallelism =
true;
1102 s.ps_cd.assign(K, 1.0);
1103 s.gd_peak.assign(K, 1.0);
1109 s.util_peak =
static_cast<double>(s.nservers);
1110 for (
double a : s.lld) s.util_peak = std::max(s.util_peak, a);
1118 if (st.cdscaling || st.jdscaling) {
1128 double dep_peak = 1.0;
1129 const std::vector<T>* pks[2] = {&st.cdscalingpeak, &st.jdscalingpeak};
1130 const bool on[2] = {
static_cast<bool>(st.cdscaling),
static_cast<bool>(st.jdscaling)};
1131 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
1132 for (std::size_t h = 0; h < 2; ++h) {
1133 if (!on[h])
continue;
1134 if (pks[h]->empty())
1136 std::string(
"SolverLDES: station '") + st.name +
1137 "' declares a dependent scaling with no declared peak rate. Utilization "
1138 "there is T*E[S]/peak, so pass the peak to " + names[h]);
1142 throw InputError(std::string(
"SolverLDES: station '") + st.name +
1143 "' declares a non-positive peak rate for " + names[h]);
1146 s.util_peak = dep_peak;
1147 const auto& beta = st.cdscaling;
1148 const auto& eta = st.jdscaling;
1149 s.cd = [beta, eta](
const std::vector<double>& n) {
1150 std::vector<T> nt(n.size());
1152 std::vector<double> bd, ed;
1154 const std::vector<T> b = beta(nt);
1155 bd.resize(b.size());
1159 const std::vector<T> e = eta(nt);
1160 ed.resize(e.size());
1163 if (bd.empty())
return ed;
1164 if (ed.empty())
return bd;
1167 const std::size_t n2 = std::max(bd.size(), ed.size());
1168 std::vector<double> out(n2, 1.0);
1169 for (std::size_t k = 0; k < n2; ++k)
1170 out[k] = bd[bd.size() > 1 ? k : 0] * ed[ed.size() > 1 ? k : 0];
1174 if (st.sched == SchedStrategy::PAS || st.sched == SchedStrategy::OI) {
1176 auto pp =
sn.pasparam.find(i + 1);
1177 if (pp !=
sn.pasparam.end()) {
1178 if (pp->second.svc_rate_fun) {
1179 const auto f = pp->second.svc_rate_fun;
1188 s.pas_rate = [f](
const std::vector<std::size_t>& seq) {
1189 std::vector<std::size_t> tags(seq.size());
1190 for (std::size_t k = 0; k < seq.size(); ++k) tags[k] = seq[k] + 1;
1194 s.pas_swap = pp->second.swap_graph;
1197 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1198 "' is a pass-and-swap station with no service rate function");
1200 if (st.sched == SchedStrategy::POLLING) {
1202 const auto pp =
sn.effective_polling(i + 1);
1203 s.poll_type = pp.ptype;
1204 s.poll_k = (pp.pk >= 1) ? pp.pk : 1;
1206 ? std::numeric_limits<std::size_t>::max()
1208 s.poll_parked =
true;
1209 s.switchover.resize(K);
1210 s.has_switchover.assign(K,
false);
1211 for (std::size_t r = 0; r < K && r < pp.switchover.size(); ++r)
1212 if (!pp.switchover[r].disabled &&
1214 s.switchover[r] = Sampler(pp.switchover[r],
1215 "the switchover of station '" + st.name +
1216 "', class '" +
sn.classes[r].name +
"'");
1217 s.has_switchover[r] =
true;
1220 s.retrial.resize(K);
1221 s.has_retrial.assign(K,
false);
1222 s.max_attempts.assign(K, 0);
1223 s.orbit_size.assign(K, 0.0);
1224 s.tot_orbit.assign(K, 0.0);
1225 s.retried.assign(K, 0.0);
1226 s.retrial_lost.assign(K, 0.0);
1228 auto rp =
sn.retrialparam.find(i + 1);
1229 if (rp !=
sn.retrialparam.end())
1230 for (std::size_t r = 0; r < K && r < rp->second.retrial_proc.size(); ++r)
1231 if (!rp->second.retrial_proc[r].disabled) {
1232 s.retrial[r] = Sampler(rp->second.retrial_proc[r],
1233 "the retrial process of station '" + st.name +
1234 "', class '" +
sn.classes[r].name +
"'");
1235 s.has_retrial[r] =
true;
1236 if (r < rp->second.max_attempts.size())
1237 s.max_attempts[r] = rp->second.max_attempts[r];
1240 s.patience.resize(K);
1241 s.has_patience.assign(K,
false);
1242 s.balk.assign(K, std::vector<StationState::BalkRule>());
1243 for (std::size_t r = 0; r < K; ++r) {
1245 r < st.patience.size() && !st.patience[r].disabled) {
1246 s.patience[r] = Sampler(st.patience[r],
"the patience of station '" + st.name +
1247 "', class '" +
sn.classes[r].name +
"'");
1248 s.has_patience[r] =
true;
1250 if (r < st.balking.size() &&
1254 "SolverLDES (native engine): station '" + st.name +
1255 "' declares a balking rule that is not QUEUE_LENGTH; the "
1256 "expected-wait rules are not ported yet");
1257 for (
const auto& th : st.balking[r].thresholds) {
1258 StationState::BalkRule br;
1259 br.min_jobs = th.min_jobs;
1260 br.max_jobs = th.max_jobs;
1262 s.balk[r].push_back(br);
1268 auto sp =
sn.setupparam.find(i + 1);
1269 if (sp !=
sn.setupparam.end()) {
1271 if (sp->second.last(su, doff) && !su.
disabled) {
1273 s.setup_time = Sampler(su,
"the setup time of station '" + st.name +
"'");
1280 Sampler(doff,
"the delay-off time of station '" + st.name +
"'");
1282 s.delayoff_time = Sampler();
1287 typename std::map<std::size_t, qn::BreakdownParam<T> >::const_iterator bp =
1288 sn.breakdownparam.find(i + 1);
1289 if (bp !=
sn.breakdownparam.end()) {
1290 s.has_breakdown =
true;
1293 Sampler(bp->second.failure,
"the failure time of station '" + st.name +
"'");
1295 Sampler(bp->second.repair,
"the repair time of station '" + st.name +
"'");
1296 s.down_scale.assign(K, 0.0);
1297 s.down_rate_raw.assign(K, 0.0);
1298 for (std::size_t r = 0; r < K && r < bp->second.down_service_rates.size(); ++r)
1304 s.state_dependent = (!s.lld.empty() || s.has_cd || s.has_breakdown);
1307 if (s.role == Role::Synchronization)
continue;
1308 for (std::size_t r = 0; r < K; ++r) {
1309 s.off[r] =
sn.disabled[i][r];
1310 if (s.off[r])
continue;
1311 const std::string where =
"station '" + st.name +
"', class '" +
sn.classes[r].name +
"'";
1312 s.svc[r] = Sampler(
sn.service[i][r], where);
1313 s.class_mean[r] = s.svc[r].mean();
1314 if (!(s.class_mean[r] > 0.0))
1315 throw InputError(
"SolverLDES (native engine): " + where +
1316 " has a non-positive mean service time");
1328 if (s.role != Role::Source) {
1329 std::size_t nbatch = 0, enabled = 0, bcls = K;
1330 for (std::size_t r = 0; r < K; ++r) {
1331 if (s.off[r])
continue;
1340 const std::string at = fam +
" service (BMSP) at station '" + st.name +
"'";
1341 if (s.role == Role::Delay)
1343 " is not supported at an infinite-server station; "
1344 "batch service requires a single-server FCFS queue");
1345 if (s.nservers != 1)
1347 " requires a single server; multiserver bulk service "
1348 "is not supported");
1351 " requires FCFS scheduling");
1354 " requires exactly one job class; multiclass bulk "
1355 "service is not supported");
1356 if (s.pas || s.preemptive || s.polling)
1358 " is incompatible with PAS/preemptive/polling "
1362 " is incompatible with server setup/delayoff");
1363 if (s.state_dependent)
1365 " is not supported with load or class dependence or "
1366 "breakdowns: the rate rescaling cannot be applied to "
1367 "a firing clock armed at the instant the station "
1369 if (!st.server_types.empty())
1371 " is not supported with heterogeneous server types; a "
1372 "bulk server is a single server by construction");
1387 if (!st.server_types.empty()) {
1388 const std::size_t nT = st.server_types.size();
1390 s.hetero_policy = st.hetero_policy;
1391 s.type_count.assign(nT, 0);
1392 s.type_first.assign(nT, 0);
1393 s.type_compat.assign(nT, std::vector<bool>(K,
true));
1394 s.type_svc.assign(nT, std::vector<Sampler>(K));
1395 s.type_has_svc.assign(nT, std::vector<bool>(K,
false));
1396 s.type_rate.assign(nT, std::vector<double>(K, 0.0));
1397 std::size_t total = 0;
1398 for (std::size_t t = 0; t < nT; ++t) {
1400 const double c = pt.
count;
1402 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1403 "' declares server pool '" + pt.
name +
1404 "' with fewer than one server");
1405 s.type_first[t] = total;
1406 s.type_count[t] =
static_cast<std::size_t
>(c + 0.5);
1407 total += s.type_count[t];
1408 for (std::size_t r = 0; r < K; ++r) {
1414 if (s.off[r]) s.type_compat[t][r] =
false;
1416 const std::string where =
"station '" + st.name +
"', pool '" + pt.
name +
1417 "', class '" +
sn.classes[r].name +
"'";
1418 s.type_svc[t][r] = Sampler(pt.
service[r], where);
1419 s.type_has_svc[t][r] =
true;
1420 const double mu = s.type_svc[t][r].mean();
1422 throw InputError(
"SolverLDES (native engine): " + where +
1423 " has a non-positive mean service time");
1424 s.type_rate[t][r] = 1.0 / mu;
1425 }
else if (!s.off[r]) {
1427 (s.class_mean[r] > 0.0) ? 1.0 / s.class_mean[r] : 0.0;
1431 for (std::size_t r = 0; r < K; ++r) {
1432 if (s.off[r])
continue;
1433 bool served =
false;
1434 for (std::size_t t = 0; t < nT && !served; ++t) served = s.type_compat[t][r];
1436 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1437 "' declares no server pool compatible with class '" +
1438 sn.classes[r].name +
1439 "', so a job of that class would wait forever");
1445 s.util_peak =
static_cast<double>(total);
1447 s.server_type.assign(total, 0);
1448 for (std::size_t t = 0; t < nT; ++t)
1449 for (std::size_t j = 0; j < s.type_count[t]; ++j)
1450 s.server_type[s.type_first[t] + j] = t;
1451 s.type_order.resize(nT);
1452 for (std::size_t t = 0; t < nT; ++t) s.type_order[t] = t;
1455 s.alfs_order = s.type_order;
1456 std::stable_sort(s.alfs_order.begin(), s.alfs_order.end(),
1457 [&](std::size_t a, std::size_t b) {
1458 std::size_t ca = 0, cb = 0;
1459 for (std::size_t r = 0; r < K; ++r) {
1460 if (s.type_compat[a][r]) ++ca;
1461 if (s.type_compat[b][r]) ++cb;
1470 if (s.has_breakdown) {
1471 s.down_scale.assign(K, 0.0);
1472 for (std::size_t r = 0; r < K; ++r) {
1473 if (s.off[r] || !(s.class_mean[r] > 0.0))
continue;
1474 if (r < s.down_rate_raw.size() && s.down_rate_raw[r] > 0.0)
1475 s.down_scale[r] = s.down_rate_raw[r] * s.class_mean[r];
1479 s.cmp.sched = s.sched;
1480 s.cmp.class_mean = &s.class_mean;
1481 if (s.role == Role::Queue) {
1482 s.server.assign(s.nservers, Job());
1483 s.server_busy.assign(s.nservers,
false);
1484 s.server_start.assign(s.nservers, 0.0);
1485 s.server_tag.assign(s.nservers, 0);
1486 s.server_blocked.assign(s.nservers,
false);
1487 s.server_held.assign(s.nservers,
false);
1488 s.held_cls.assign(s.nservers, 0);
1489 s.blocked_job.assign(s.nservers, Job());
1490 s.blocked_dest.assign(s.nservers, 0);
1491 s.blocked_dest_cls.assign(s.nservers, 0);
1492 if (s.has_parallelism) s.held_extra.assign(s.nservers, std::vector<std::size_t>());
1508 for (std::size_t i = 0; i < M; ++i) {
1509 const auto& st =
sn.stations[i];
1510 std::size_t maxpar = 1;
1511 for (std::size_t r = 0; r < st.server_parallelism.size(); ++r)
1512 maxpar = std::max(maxpar, st.server_parallelism[r]);
1513 if (maxpar <= 1)
continue;
1514 StationState& s = S[i];
1515 const std::string where = std::string(
"SolverLDES (native engine): station '") +
1516 st.name +
"' declares server parallelism";
1517 if (s.role != Role::Queue)
1518 throw UnsupportedError(where +
" at an infinite server, which never makes a job "
1519 "wait for a slot; drop the declaration or give the "
1520 "station a finite server count");
1523 ", which shares its whole capacity rather than handing out "
1524 "slots, so a job cannot hold n of them; use a queueing "
1525 "discipline, or a class weight to slow the class down");
1526 if (s.polling || s.pas)
1528 ", whose controller picks the server itself and has no pool "
1529 "to seize n slots from");
1531 throw UnsupportedError(where +
" at a bulk (BMSP) server, which has only a "
1532 "station-level firing clock and no per-job server "
1535 throw UnsupportedError(where +
" together with heterogeneous server pools: a job is "
1536 "served at the law of the one pool it occupies, which "
1537 "n servers of mixed types cannot express");
1539 throw UnsupportedError(where +
" together with setup/delayoff: the slots of such a "
1540 "station power up one at a time, so 'n free at once' "
1541 "is not a state it passes through");
1542 if (maxpar > s.nservers)
1543 throw InputError(where +
" of " + std::to_string(maxpar) +
" above its " +
1544 std::to_string(s.nservers) +
" servers, so the job could never "
1561 const bool has_gd =
static_cast<bool>(sn.gdscaling);
1562 std::vector<std::vector<double> > gd_cache(M, std::vector<double>(K, 1.0));
1564 if (sn.gdscalingpeak.size() < M * K)
1566 "SolverLDES (native engine): a global dependence requires an explicit peak rate, "
1567 "because utilization under it is reported as T*E[S]/peak; pass one to "
1568 "setGlobalDependence");
1569 for (std::size_t i = 0; i < M; ++i) {
1570 StationState& s = S[i];
1571 const std::string& nm = sn.stations[i].name;
1574 "SolverLDES (native engine): station '" + nm +
1575 "' is a bulk (BMSP) server under a global dependence, which holds only a "
1576 "station-level firing clock and no per-job residual work to rescale");
1579 "SolverLDES (native engine): station '" + nm +
1580 "' is a pass-and-swap station under a global dependence, whose single "
1581 "aggregate clock is armed from its own list and carries no residual work "
1583 for (std::size_t r = 0; r < K; ++r) {
1584 const double pk = num_traits<T>::to_double(sn.gdscalingpeak[i * K + r]);
1585 if (!(pk > 0.0) || !std::isfinite(pk))
1586 throw InputError(
"SolverLDES (native engine): the global dependence declares "
1587 "a peak rate that is not finite and positive at station '" +
1595 if (s.role == Role::Queue) s.state_dependent =
true;
1605 const std::size_t nnodes = sn.nof_nodes();
1606 std::vector<std::size_t> node_to_station(nnodes + 1, M);
1607 for (std::size_t i = 0; i < M; ++i) node_to_station[sn.station_to_node[i]] = i;
1620 std::vector<std::vector<std::vector<RouteDest>>> nroute(
1621 nnodes + 1, std::vector<std::vector<RouteDest>>(K));
1630 std::vector<std::vector<lang::RoutingStrategy>> node_routing(
1632 std::vector<std::vector<std::size_t>> rr_counter(nnodes + 1, std::vector<std::size_t>(K, 0));
1633 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1634 for (std::size_t r = 0; r < K; ++r)
1635 if (sn.nodes[inode - 1].routing.size() > r)
1636 node_routing[inode][r] = sn.nodes[inode - 1].routing[r];
1637 for (std::size_t inode = 1; inode <= nnodes; ++inode) {
1644 if (sn.nodes[inode - 1].nodetype == NodeType::Sink)
continue;
1648 if (sn.nodes[inode - 1].nodetype == NodeType::Place ||
1649 sn.nodes[inode - 1].nodetype == NodeType::Transition)
1651 for (std::size_t r = 0; r < K; ++r) {
1654 for (std::size_t j = 1; j <= nnodes; ++j) {
1657 d.sink = (sn.nodes[j - 1].nodetype == NodeType::Sink);
1658 d.station = node_to_station[j];
1659 for (std::size_t s2 = 0; s2 < K; ++s2) {
1660 const double p = num_traits<T>::to_double(sn.route_eff(r + 1, s2 + 1, inode, j));
1661 if (!(p > 0.0))
continue;
1663 d.cls.push_back(std::make_pair(s2, p));
1665 if (d.cls.empty())
continue;
1672 if (!d.sink && d.station >= M && sn.nodes[j - 1].nodetype != NodeType::Fork &&
1673 sn.nodes[j - 1].nodetype != NodeType::Join &&
1674 sn.nodes[j - 1].nodetype != NodeType::Cache &&
1675 sn.nodes[j - 1].nodetype != NodeType::ClassSwitch &&
1676 sn.nodes[j - 1].nodetype != NodeType::Router &&
1677 sn.nodes[j - 1].nodetype != NodeType::Logger &&
1678 sn.nodes[j - 1].nodetype != NodeType::Place &&
1679 sn.nodes[j - 1].nodetype != NodeType::Transition)
1681 "SolverLDES (native engine): the routing crosses node '" +
1682 sn.nodes[j - 1].name +
1683 "', which the refresh did not fold into the station-to-station "
1685 if (d.station < M && S[d.station].role == Role::Source)
1686 throw InputError(
"SolverLDES (native engine): the routing sends a "
1687 "job back into the Source");
1688 nroute[inode][r].push_back(d);
1698 std::vector<int> join_of_fork(nnodes + 1, -1);
1699 std::vector<bool> is_fork(nnodes + 1,
false), is_join(nnodes + 1,
false);
1700 for (
const auto& fjp : sn.fj) {
1701 if (fjp.first > nnodes || fjp.second > nnodes)
continue;
1702 is_fork[fjp.first] =
true;
1703 is_join[fjp.second] =
true;
1704 join_of_fork[fjp.first] =
static_cast<int>(fjp.second);
1708 std::vector<bool> spawn_joins_at(K,
false);
1709 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1710 for (std::size_t r = 0; r < K; ++r)
1711 for (
const RouteDest& d : nroute[inode][r])
1712 if (d.node >= 1 && d.node <= nnodes &&
1713 sn.nodes[d.node - 1].nodetype == NodeType::Join)
1714 for (std::size_t c = 0; c < d.cls.size(); ++c)
1715 spawn_joins_at[d.cls[c].first] =
true;
1716 for (std::size_t nd = 1; nd <= nnodes; ++nd) {
1717 if (sn.nodes[nd - 1].nodetype == NodeType::Fork && !is_fork[nd])
1718 throw InputError(
"SolverLDES (native engine): Fork node '" + sn.nodes[nd - 1].name +
1719 "' has no matching Join");
1720 if (sn.nodes[nd - 1].nodetype == NodeType::Join && !is_join[nd])
1721 throw InputError(
"SolverLDES (native engine): Join node '" + sn.nodes[nd - 1].name +
1722 "' closes no Fork");
1734 std::size_t cls = 0;
1746 std::vector<std::pair<std::size_t, double>> siblings;
1748 std::map<std::uint64_t, ForkSync> fork_sync;
1749 std::uint64_t next_parent = 0;
1757 std::map<std::size_t, CacheState> caches;
1758 for (
const auto& np : sn.nodeparam) {
1759 const std::size_t nd = np.first;
1760 if (nd == 0 || nd > nnodes)
continue;
1761 if (sn.nodes[nd - 1].nodetype != NodeType::Cache)
continue;
1762 const auto& cp = np.second;
1765 cs.nitems = cp.nitems;
1766 cs.policy = cp.replacestrat;
1767 for (
int c : cp.itemcap)
1768 if (c > 0) cs.capacity.push_back(
static_cast<std::size_t
>(c));
1769 if (cs.capacity.empty()) cs.capacity.push_back(1);
1770 cs.lists.assign(cs.capacity.size(), std::list<std::size_t>());
1771 for (
int v : cp.itemsize) cs.item_size.push_back(v);
1772 for (
int v : cp.costcap) cs.cost_cap.push_back(v);
1773 cs.hits.assign(K, 0.0);
1774 cs.misses.assign(K, 0.0);
1775 cs.hit_class.assign(K, -1);
1776 cs.miss_class.assign(K, -1);
1777 for (std::size_t r = 0; r < K && r < cp.hitclass.size(); ++r)
1778 if (cp.hitclass[r] > 0) cs.hit_class[r] =
static_cast<int>(cp.hitclass[r] - 1);
1779 for (std::size_t r = 0; r < K && r < cp.missclass.size(); ++r)
1780 if (cp.missclass[r] > 0) cs.miss_class[r] =
static_cast<int>(cp.missclass[r] - 1);
1783 cs.popularity.assign(K, std::vector<double>());
1784 for (std::size_t r = 0; r < K && r < cp.pread.size(); ++r) {
1786 for (
const T& v : cp.pread[r]) {
1787 acc += num_traits<T>::to_double(v);
1788 cs.popularity[r].push_back(acc);
1790 if (!cs.popularity[r].empty()) cs.popularity[r].back() = 1.0;
1794 if (cp.retrieval_capacity > 0 && !cp.retrieval_classes.empty()) {
1795 cs.has_retrieval =
true;
1796 cs.retrieval_class = cp.retrieval_classes;
1797 cs.retrieval_class_to_item.assign(K, -1);
1798 for (std::size_t it = 0; it < cs.retrieval_class.size(); ++it)
1799 for (std::size_t r = 0; r < cs.retrieval_class[it].size(); ++r) {
1800 const std::size_t rc = cs.retrieval_class[it][r];
1801 if (rc > 0 && rc <= K) cs.retrieval_class_to_item[rc - 1] =
static_cast<int>(it);
1803 cs.in_flight.assign(cs.nitems, 0);
1804 cs.fetch_start.assign(cs.nitems, 0.0);
1805 cs.held.assign(cs.nitems, std::vector<CacheState::HeldRequest>());
1806 cs.delayed.assign(K, 0.0);
1816 std::vector<std::size_t> place_of(nnodes + 1, M);
1817 std::vector<std::size_t> place_nodes;
1818 for (std::size_t nd = 1; nd <= nnodes; ++nd)
1819 if (sn.nodes[nd - 1].nodetype == NodeType::Place) {
1820 place_of[nd] = place_nodes.size();
1821 place_nodes.push_back(nd);
1823 std::vector<std::size_t> place_station(place_nodes.size(), M);
1824 for (std::size_t p = 0; p < place_nodes.size(); ++p)
1825 place_station[p] = node_to_station[place_nodes[p]];
1838 std::vector<std::vector<double>> marking(place_nodes.size(), std::vector<double>(K, 0.0));
1839 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1840 auto im = sn.initmarking.find(place_nodes[p]);
1841 if (im != sn.initmarking.end())
1842 for (std::size_t r = 0; r < K && r < im->second.size(); ++r)
1843 marking[p][r] = num_traits<T>::to_double(im->second[r]);
1844 for (std::size_t r = 0; r < K; ++r) {
1846 const double n = sn.classes[r].population;
1847 if (!std::isfinite(n) || !(n > 0.0))
continue;
1848 if (sn.classes[r].refstat != place_station[p] + 1)
continue;
1849 marking[p][r] = std::max(marking[p][r], n);
1871 std::vector<std::vector<double>> avail(place_nodes.size(), std::vector<double>(K, 0.0));
1872 std::vector<bool> qplace(place_nodes.size(),
false);
1873 std::vector<std::size_t> place_servers(place_nodes.size(), 1);
1874 std::vector<std::deque<std::size_t>> place_wait(place_nodes.size());
1875 std::vector<std::size_t> place_busy(place_nodes.size(), 0);
1876 std::vector<std::vector<double>> place_insvc(place_nodes.size(), std::vector<double>(K, 0.0));
1877 std::vector<std::vector<Sampler>> place_svc(place_nodes.size(), std::vector<Sampler>(K));
1889 std::vector<std::vector<Rng>> g_place(place_nodes.size());
1890 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1891 const std::size_t ist = place_station[p];
1892 g_place[p].reserve(K);
1893 for (std::size_t k = 0; k < K; ++k) {
1894 const long long off =
1895 ((nsrc + nsvc +
static_cast<long long>(p)) * Kll +
static_cast<long long>(k)) *
1897 g_place[p].push_back(Rng(seed_ll, off));
1899 if (ist >= M)
continue;
1900 qplace[p] = sn.is_queueing_place(ist);
1902 for (std::size_t r = 0; r < K; ++r) avail[p][r] = marking[p][r];
1905 const auto& pst = sn.stations[ist];
1911 if (pst.sched != SchedStrategy::FCFS && pst.sched != SchedStrategy::LCFS &&
1912 pst.sched != SchedStrategy::SIRO && pst.sched != SchedStrategy::INF)
1914 "SolverLDES (native engine): queueing place '" + pst.name +
1916 ", which the embedded-queue algorithm does not support "
1917 "(supported: FCFS, LCFS, SIRO, INF)");
1918 place_servers[p] = (pst.sched == SchedStrategy::INF || !std::isfinite(pst.nservers))
1919 ? std::numeric_limits<std::size_t>::max()
1920 :
static_cast<std::size_t
>(pst.nservers + 0.5);
1921 for (std::size_t r = 0; r < K; ++r) {
1922 if (sn.service[ist][r].disabled)
continue;
1924 Sampler(sn.service[ist][r],
1925 "queueing place '" + pst.name +
"', class '" + sn.classes[r].name +
"'");
1931 for (std::size_t k = 0; k < K; ++k)
1932 for (
double t = 0.0; t + 0.5 < marking[p][k]; t += 1.0) place_wait[p].push_back(k);
1935 std::vector<SpnTransition> transitions;
1936 for (
const auto& tp : sn.transparam) {
1937 const std::size_t nd = tp.first;
1938 if (nd == 0 || nd > nnodes)
continue;
1941 tr.places = place_nodes;
1942 for (std::size_t m = 0; m < tp.second.nmodes; ++m) {
1946 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1947 const std::size_t pn = place_nodes[p];
1948 for (std::size_t r = 0; r < K; ++r) {
1949 mode.enabling.push_back(
1950 (m < tp.second.enabling.size() && pn - 1 < tp.second.enabling[m].rows() &&
1951 r < tp.second.enabling[m].cols())
1952 ? num_traits<T>::to_double(tp.second.enabling[m](pn - 1, r))
1954 mode.inhibiting.push_back(
1955 (m < tp.second.inhibiting.size() &&
1956 pn - 1 < tp.second.inhibiting[m].rows() &&
1957 r < tp.second.inhibiting[m].cols())
1958 ? num_traits<T>::to_double(tp.second.inhibiting[m](pn - 1, r))
1959 : std::numeric_limits<double>::infinity());
1960 mode.firing.push_back(
1961 (m < tp.second.firing.size() && pn - 1 < tp.second.firing[m].rows() &&
1962 r < tp.second.firing[m].cols())
1963 ? num_traits<T>::to_double(tp.second.firing[m](pn - 1, r))
1967 if (m < tp.second.nmodeservers.size()) mode.servers = tp.second.nmodeservers[m];
1968 if (m < tp.second.firingprio.size()) mode.priority = tp.second.firingprio[m];
1969 if (m < tp.second.fireweight.size())
1970 mode.weight = num_traits<T>::to_double(tp.second.fireweight[m]);
1971 mode.immediate = (m < tp.second.timing.size() &&
1973 if (m < tp.second.firingproc.size() && !tp.second.firingproc[m].disabled) {
1974 const double mean = num_traits<T>::to_double(tp.second.firingproc[m].mean);
1975 if (mean > 0.0) mode.rate = 1.0 / mean;
1977 if (m < tp.second.firingdep.size() && tp.second.firingdep[m]) {
1978 const auto f = tp.second.firingdep[m];
1979 const std::vector<std::size_t> pl = place_nodes;
1980 const std::size_t nn = nnodes, KK = K;
1986 mode.dep = [f, pl, nn, KK](
const std::vector<double>& tok) {
1987 std::vector<T> mk(nn, num_traits<T>::from_int(0));
1988 for (std::size_t p = 0; p < pl.size(); ++p) {
1990 for (std::size_t r = 0; r < KK; ++r)
1991 if (p * KK + r < tok.size()) s += tok[p * KK + r];
1992 if (pl[p] >= 1 && pl[p] - 1 < nn)
1993 mk[pl[p] - 1] = num_traits<T>::from_double(s);
1995 return num_traits<T>::to_double(f(mk));
1998 tr.modes.push_back(mode);
2000 tr.fired.assign(tr.modes.size(), 0.0);
2001 transitions.push_back(tr);
2006 for (std::size_t i = 0; i < M; ++i) acc.util_peak[i] = S[i].util_peak;
2011 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
2012 if (place_station[p] >= M)
continue;
2013 for (std::size_t r = 0; r < K; ++r) acc.qlen[place_station[p]][r] = marking[p][r];
2018 std::vector<double> sys_resp_sum(K, 0.0), sys_resp_cnt(K, 0.0), sys_completed(K, 0.0);
2021 std::vector<std::vector<double>> dropped(M, std::vector<double>(K, 0.0));
2022 std::vector<std::vector<double>> balked(M, std::vector<double>(K, 0.0));
2023 std::vector<std::vector<double>> reneged(M, std::vector<double>(K, 0.0));
2024 std::vector<std::vector<double>> blocked_count(M, std::vector<double>(K, 0.0));
2033 const bool want_respt = o.export_respt || o.export_trajectory;
2034 std::vector<std::vector<std::vector<double>>> resp_samples(
2035 want_respt ? M : 0, std::vector<std::vector<double>>(K));
2036 std::uint64_t job_id = 0;
2049 std::vector<bool> is_removal_signal(K,
false);
2050 bool has_removal_signal =
false;
2051 for (std::size_t r = 0; r < K; ++r) {
2052 if (r >= sn.issignal.size() || !sn.issignal[r])
continue;
2056 is_removal_signal[r] =
true;
2057 has_removal_signal =
true;
2076 const bool track_delay_jobs = has_removal_signal || has_gd;
2077 std::vector<std::vector<Job>> delay_live(track_delay_jobs ? M : 0);
2088 std::vector<std::size_t> sync_reply(K, K);
2089 std::vector<bool> is_reply_signal(K,
false);
2090 bool has_sync_call =
false;
2091 for (std::size_t r = 0; r < K; ++r) {
2092 if (r < sn.syncreply.size() && sn.syncreply[r] >= 1 && sn.syncreply[r] <= K) {
2093 sync_reply[r] = sn.syncreply[r] - 1;
2094 has_sync_call =
true;
2096 if (r < sn.issignal.size() && sn.issignal[r] && r < sn.signaltype.size() &&
2098 is_reply_signal[r] =
true;
2101 struct PendingCall {
2102 std::size_t station = 0;
2103 std::size_t slot = 0;
2104 std::size_t cls = 0;
2107 std::map<std::uint64_t, PendingCall> pending_reply;
2108 std::uint64_t next_call = 0;
2111 bool has_immfeed =
false;
2112 for (std::size_t i = 0; i < M && i < sn.immfeed.size(); ++i)
2113 for (std::size_t r = 0; r < sn.immfeed[i].size(); ++r)
2114 if (sn.immfeed[i][r]) has_immfeed =
true;
2124 std::vector<std::size_t> spawn_of(K, K);
2125 bool has_spawn =
false;
2126 for (std::size_t r = 0; r < K; ++r)
2127 if (sn.classes[r].spawn >= 1 && sn.classes[r].spawn <= K) {
2128 spawn_of[r] = sn.classes[r].spawn - 1;
2133 std::priority_queue<Event, std::vector<Event>, EventLater> evq;
2134 std::uint64_t seq = 0, ps_tag = 0;
2136 auto push = [&](Event e) {
2154 std::function<void(std::size_t)> place_try_start = [&](std::size_t p) {
2155 const std::size_t ist = place_station[p];
2156 if (ist >= M)
return;
2157 while (place_busy[p] < place_servers[p] && !place_wait[p].empty()) {
2159 if (S[ist].sched == SchedStrategy::LCFS) {
2160 r = place_wait[p].back();
2161 place_wait[p].pop_back();
2162 }
else if (S[ist].sched == SchedStrategy::SIRO) {
2163 const std::size_t at = std::min(
2164 place_wait[p].size() - 1,
2165 static_cast<std::size_t
>(uniform01(g_qplace) *
2166 static_cast<double>(place_wait[p].size())));
2167 r = place_wait[p][at];
2168 place_wait[p].erase(place_wait[p].begin() +
static_cast<std::ptrdiff_t
>(at));
2170 r = place_wait[p].front();
2171 place_wait[p].pop_front();
2178 if (place_svc[p][r].disabled())
2179 throw InputError(
"SolverLDES (native engine): a token of class '" +
2180 sn.classes[r].name +
"' entered queueing place '" +
2181 sn.stations[ist].name +
2182 "', which has no service process for that class; call "
2183 "setService for every token colour of a queueing place");
2184 acc.update_busy(ist, r, now);
2186 place_insvc[p][r] += 1.0;
2187 acc.busy[ist][r] = place_insvc[p][r];
2189 e.t = now + place_svc[p][r].next(g_place[p][r]);
2198 auto place_deposit = [&](std::size_t p, std::size_t r,
double n) {
2199 const std::size_t ist = place_station[p];
2201 acc.update_qlen(ist, r, now);
2203 acc.qlen[ist][r] = marking[p][r];
2204 acc.arrived[ist][r] += n;
2212 for (
double t = 0.0; t + 0.5 < n; t += 1.0) place_wait[p].push_back(r);
2224 auto place_take = [&](std::size_t p, std::size_t r,
double n) {
2225 const std::size_t ist = place_station[p];
2229 acc.update_qlen(ist, r, now);
2230 acc.qlen[ist][r] = marking[p][r];
2231 acc.completed[ist][r] += n;
2244 auto buffer_push = [&](std::size_t i,
const Job& job) {
2245 StationState& s = S[i];
2246 s.buffer.push_back(job);
2247 if (s.sched != SchedStrategy::FSP)
2248 std::push_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
2250 auto buffer_pop = [&](std::size_t i) -> Job {
2251 StationState& s = S[i];
2252 if (s.sched != SchedStrategy::FSP) {
2253 std::pop_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
2254 Job job = s.buffer.back();
2255 s.buffer.pop_back();
2258 std::vector<double> works;
2259 for (
const Job& j : s.buffer) works.push_back(j.remaining);
2260 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2261 if (s.server_busy[sl])
2262 works.push_back(std::max(0.0, s.server[sl].remaining -
2263 (now - s.server_start[sl])));
2264 std::size_t best = 0;
2265 double best_vft = std::numeric_limits<double>::infinity();
2266 for (std::size_t j = 0; j < s.buffer.size(); ++j) {
2268 static_cast<double>(s.nservers), now);
2274 Job job = s.buffer[best];
2275 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(best));
2290 auto slot_ok_for = [&](
const StationState& s, std::size_t sl, std::size_t cls) ->
bool {
2291 if (s.server_busy[sl] || s.server_blocked[sl] || s.server_held[sl])
return false;
2292 if (!s.has_pools)
return true;
2293 return s.type_compat[s.server_type[sl]][cls];
2297 auto free_slot_count = [&](std::size_t i) -> std::size_t {
2298 const StationState& s = S[i];
2300 for (std::size_t sl = 0; sl < s.server_busy.size(); ++sl)
2301 if (!s.server_busy[sl] && !s.server_blocked[sl] && !s.server_held[sl]) ++n;
2313 auto free_slot_for = [&](std::size_t i, std::size_t cls) -> std::size_t {
2314 StationState& s = S[i];
2318 if (s.has_parallelism && free_slot_count(i) < s.parallelism[cls])
return s.nservers;
2320 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2321 if (slot_ok_for(s, sl, cls))
return sl;
2325 std::vector<std::size_t> cand;
2326 for (std::size_t t = 0; t < s.type_count.size(); ++t) {
2327 if (!s.type_compat[t][cls])
continue;
2328 for (std::size_t j = 0; j < s.type_count[t]; ++j)
2329 if (slot_ok_for(s, s.type_first[t] + j, cls)) {
2334 if (cand.empty())
return s.nservers;
2335 std::size_t chosen = cand[0];
2336 if (cand.size() > 1) {
2337 switch (s.hetero_policy) {
2343 for (std::size_t k = 0; k < s.type_order.size(); ++k) {
2344 const std::size_t t = s.type_order[k];
2345 if (std::find(cand.begin(), cand.end(), t) == cand.end())
continue;
2347 s.type_order.erase(s.type_order.begin() +
2348 static_cast<std::ptrdiff_t
>(k));
2349 s.type_order.push_back(t);
2357 for (std::size_t k = 0; k < s.alfs_order.size(); ++k)
2358 if (std::find(cand.begin(), cand.end(), s.alfs_order[k]) != cand.end()) {
2359 chosen = s.alfs_order[k];
2367 for (std::size_t k = 0; k < cand.size(); ++k) {
2368 const double rt = s.type_rate[cand[k]][cls];
2379 std::size_t at =
static_cast<std::size_t
>(
2380 uniform01(g_routing) *
static_cast<double>(cand.size()));
2381 if (at >= cand.size()) at = cand.size() - 1;
2393 for (std::size_t j = 0; j < s.type_count[chosen]; ++j) {
2394 const std::size_t sl = s.type_first[chosen] + j;
2395 if (slot_ok_for(s, sl, cls))
return sl;
2401 auto buffer_has_for_slot = [&](std::size_t i, std::size_t sl) ->
bool {
2402 StationState& s = S[i];
2403 if (s.buffer.empty())
return false;
2404 if (!s.has_pools)
return true;
2405 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
2406 for (std::size_t j = 0; j < s.buffer.size(); ++j)
2407 if (ok[s.buffer[j].cls])
return true;
2422 auto buffer_pop_for_slot = [&](std::size_t i, std::size_t sl) -> Job {
2423 StationState& s = S[i];
2424 if (!s.has_pools)
return buffer_pop(i);
2425 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
2426 std::vector<Job> skipped;
2429 while (!s.buffer.empty()) {
2430 Job cand = buffer_pop(i);
2436 skipped.push_back(cand);
2438 for (std::size_t j = 0; j < skipped.size(); ++j) buffer_push(i, skipped[j]);
2440 throw InputError(
"SolverLDES (native engine): buffer_pop_for_slot was asked for a "
2441 "job no pool of this slot can serve; guard with buffer_has_for_slot");
2464 auto lld_factor = [&](std::size_t i) ->
double {
2465 StationState& s = S[i];
2466 if (s.lld.empty() || s.ps)
return 1.0;
2468 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
2469 if (!(total > 0.0))
return 1.0;
2470 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
2471 return s.lld[idx] > 0.0 ? s.lld[idx] : 1.0;
2481 auto cd_factor = [&](std::size_t i, std::size_t r) ->
double {
2482 StationState& s = S[i];
2483 if (!s.has_cd)
return 1.0;
2484 std::vector<double> nvec(K, 0.0);
2485 for (std::size_t k = 0; k < K; ++k) nvec[k] = acc.qlen[i][k];
2486 const std::vector<double> beta = s.cd(nvec);
2487 const double b = (beta.size() > 1) ? beta[r] : (beta.empty() ? 1.0 : beta[0]);
2488 return (b > 1e-10) ? b : 1.0;
2499 auto gd_refresh = [&]() {
2500 std::vector<T> nvec(M * K);
2501 for (std::size_t i = 0; i < M; ++i)
2502 for (std::size_t r = 0; r < K; ++r)
2503 nvec[i * K + r] = num_traits<T>::from_double(acc.qlen[i][r]);
2504 const std::vector<T> v = sn.gdscaling(nvec);
2506 throw InputError(
"SolverLDES (native engine): the global dependence handle returned "
2508 for (std::size_t i = 0; i < M; ++i)
2509 for (std::size_t r = 0; r < K; ++r) {
2512 const double f = num_traits<T>::to_double(
2513 (v.size() == 1) ? v[0] : ((v.size() == M) ? v[i] : v[i * K + r]));
2514 if (!std::isfinite(f) || f < 0.0)
2515 throw InputError(
"SolverLDES (native engine): the global dependence handle "
2516 "returned a scaling that is not finite and nonnegative at "
2517 "station '" + sn.stations[i].name +
"'");
2518 if (!(f > 0.0) && acc.qlen[i][r] > 0.0)
2520 "SolverLDES (native engine): the global dependence handle returned a "
2521 "zero scaling at station '" + sn.stations[i].name +
2522 "', which holds jobs of class '" + sn.classes[r].name +
2523 "'. A station that stops while it holds work has no completion left to "
2524 "schedule; use Queue.setBreakdown for a server that genuinely stops");
2529 auto rate_scaling = [&](std::size_t i, std::size_t r) ->
double {
2530 StationState& s = S[i];
2534 if (has_gd) factor *= gd_cache[i][r];
2539 if (s.has_breakdown && !s.up)
2540 factor *= (r < s.down_scale.size() ? s.down_scale[r] : 0.0);
2550 auto ps_servers = [&](std::size_t i) ->
double {
2551 StationState& s = S[i];
2552 if (s.lld.empty())
return static_cast<double>(s.nservers);
2554 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
2555 if (!(total > 0.0))
return 1.0;
2556 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
2566 auto ps_advance_one = [&](std::size_t i) {
2567 StationState& s = S[i];
2568 const double dt = now - s.ps_last_update;
2569 s.ps_last_update = now;
2577 for (std::size_t r = 0; r < K; ++r) acc.last_busy[i][r] = now;
2578 if (!(dt > 0.0) || s.ps_jobs.empty())
return;
2579 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
2581 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2582 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
2584 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2585 rates[j] *= gd_cache[i][s.ps_jobs[j].cls];
2586 if (s.has_breakdown && !s.up)
2587 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2588 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
2590 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
2591 if (rates[j] > 0.0) {
2592 acc.tot_busy[i][s.ps_jobs[j].cls] += rates[j] * dt;
2593 s.ps_jobs[j].remaining = std::max(0.0, s.ps_jobs[j].remaining - rates[j] * dt);
2596 auto ps_reschedule_one = [&](std::size_t i) {
2597 StationState& s = S[i];
2601 for (std::size_t r = 0; r < K; ++r) s.ps_cd[r] =
cd_factor(i, r);
2602 if (s.ps_jobs.empty())
return;
2603 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
2605 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2606 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
2608 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2609 rates[j] *= gd_cache[i][s.ps_jobs[j].cls];
2610 if (s.has_breakdown && !s.up)
2611 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
2612 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
2614 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j) {
2615 PsJob& pj = s.ps_jobs[j];
2618 const double rate = rates[j];
2619 if (!(rate > 0.0))
continue;
2621 e.t = now + ((pj.remaining <= 1e-12) ? 1e-12 : pj.remaining / rate);
2640 auto sd_advance_one = [&](std::size_t i) {
2641 StationState& s = S[i];
2642 if (!s.state_dependent || s.ps)
return;
2643 const double dt = now - s.sd_last_update;
2644 s.sd_last_update = now;
2645 if (!(dt > 0.0))
return;
2646 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2647 if (s.server_busy[sl]) {
2648 const double scale = rate_scaling(i, s.server[sl].cls);
2649 s.server[sl].remaining = std::max(0.0, s.server[sl].remaining - dt * scale);
2650 s.server[sl].elapsed += dt * scale;
2651 s.server_start[sl] = now;
2654 auto sd_reschedule_one = [&](std::size_t i) {
2655 StationState& s = S[i];
2656 if (!s.state_dependent || s.ps)
return;
2662 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2663 if (s.server_busy[sl]) {
2664 const double scale = rate_scaling(i, s.server[sl].cls);
2674 s.server_tag[sl] = ++ps_tag;
2675 if (!(scale > 0.0))
continue;
2677 e.t = now + s.server[sl].remaining / scale;
2680 e.cls = s.server[sl].cls;
2682 e.tag = s.server_tag[sl];
2697 auto gd_delay_advance = [&](std::size_t i) {
2698 StationState& s = S[i];
2699 const double dt = now - s.sd_last_update;
2700 s.sd_last_update = now;
2701 if (!(dt > 0.0))
return;
2702 for (std::size_t j = 0; j < delay_live[i].size(); ++j) {
2703 Job& d = delay_live[i][j];
2704 const double scale = gd_cache[i][d.cls];
2705 d.remaining = std::max(0.0, d.remaining - dt * scale);
2706 d.elapsed += dt * scale;
2709 auto gd_delay_reschedule = [&](std::size_t i) {
2710 for (std::size_t j = 0; j < delay_live[i].size(); ++j) {
2711 Job& d = delay_live[i][j];
2715 const double scale = gd_cache[i][d.cls];
2716 if (!(scale > 0.0))
continue;
2718 e.t = now + ((d.remaining <= 1e-12) ? 1e-12 : d.remaining / scale);
2722 e.slot = std::numeric_limits<std::size_t>::max();
2742 auto gd_advance_all = [&]() {
2743 for (std::size_t a = 0; a < M; ++a) {
2744 if (S[a].role == Role::Delay)
2745 gd_delay_advance(a);
2746 else if (S[a].role != Role::Queue)
2754 auto gd_reschedule_all = [&]() {
2756 for (std::size_t a = 0; a < M; ++a) {
2757 if (S[a].role == Role::Delay)
2758 gd_delay_reschedule(a);
2759 else if (S[a].role != Role::Queue)
2762 ps_reschedule_one(a);
2764 sd_reschedule_one(a);
2767 auto ps_advance = [&](std::size_t i) {
2768 if (has_gd) gd_advance_all();
else ps_advance_one(i);
2770 auto ps_reschedule = [&](std::size_t i) {
2771 if (has_gd) gd_reschedule_all();
else ps_reschedule_one(i);
2773 auto sd_advance = [&](std::size_t i) {
2774 if (has_gd) gd_advance_all();
else sd_advance_one(i);
2776 auto sd_reschedule = [&](std::size_t i) {
2777 if (has_gd) gd_reschedule_all();
else sd_reschedule_one(i);
2793 auto dest_has_room = [&](std::size_t j, std::size_t r) ->
bool {
2794 StationState& d = S[j];
2795 if (d.role != Role::Queue)
return true;
2797 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[j][k] - d.blocked_at[k];
2798 return (total + 1.0 <= d.cap) &&
2799 (acc.qlen[j][r] - d.blocked_at[r] + 1.0 <= d.classcap[r]);
2804 return (S[j].role == Role::Queue && r < S[j].droprule.size()) ? S[j].droprule[r]
2812 std::vector<Region> regions;
2813 std::vector<int> region_of(M, -1);
2814 for (
const auto& rg : sn.regions) {
2817 R.members.assign(M,
false);
2818 R.class_cap.assign(K, -1.0);
2819 R.class_size.assign(K, 1.0);
2820 R.class_weight.assign(K, 1.0);
2822 R.jobs.assign(K, 0.0);
2823 R.blocked.assign(K, 0.0);
2824 R.dropped.assign(K, 0.0);
2825 R.tot_jobs.assign(K, 0.0);
2826 R.tot_weight.assign(K, 0.0);
2827 R.tot_mem.assign(K, 0.0);
2828 R.completed.assign(K, 0.0);
2829 R.resp_sum.assign(K, 0.0);
2830 R.resp_cnt.assign(K, 0.0);
2831 for (std::size_t i = 0; i < M && i < rg.members.size(); ++i)
2832 if (rg.members[i]) {
2833 R.members[i] =
true;
2834 if (region_of[i] >= 0)
2836 "SolverLDES (native engine): station '" + sn.stations[i].name +
2837 "' belongs to more than one finite capacity region");
2838 region_of[i] =
static_cast<int>(regions.size());
2843 for (std::size_t i = 0; i < rg.cap.size(); ++i) {
2844 if (i >= M || !R.members[i])
continue;
2845 for (std::size_t r = 0; r < K && r < rg.cap[i].size(); ++r)
2846 if (rg.cap[i][r] >= 0.0)
2847 R.class_cap[r] = (R.class_cap[r] < 0.0) ? rg.cap[i][r]
2848 : std::min(R.class_cap[r], rg.cap[i][r]);
2849 if (rg.cap[i].size() > K && rg.cap[i][K] >= 0.0)
2850 R.global_cap = (R.global_cap < 0.0) ? rg.cap[i][K]
2851 : std::min(R.global_cap, rg.cap[i][K]);
2853 for (
double mm : rg.maxmem)
2854 if (mm >= 0.0) R.max_mem = (R.max_mem < 0.0) ? mm : std::min(R.max_mem, mm);
2855 for (std::size_t r = 0; r < K && r < rg.rule.size(); ++r) R.rule[r] = rg.rule[r];
2856 for (std::size_t r = 0; r < K && r < rg.size.size(); ++r)
2857 R.class_size[r] = num_traits<T>::to_double(rg.size[r]);
2858 for (std::size_t r = 0; r < K && r < rg.weight.size(); ++r)
2859 R.class_weight[r] = num_traits<T>::to_double(rg.weight[r]);
2860 for (std::size_t c = 0; c < rg.lincon_A.rows(); ++c) {
2861 std::vector<double> row;
2862 for (std::size_t r = 0; r < rg.lincon_A.cols(); ++r)
2863 row.push_back(num_traits<T>::to_double(rg.lincon_A(c, r)));
2864 R.lincon_A.push_back(row);
2865 R.lincon_b.push_back(c < rg.lincon_b.size() ? num_traits<T>::to_double(rg.lincon_b[c])
2868 regions.push_back(R);
2876 struct RegionWaiter {
2877 std::size_t station = 0;
2880 std::vector<std::vector<RegionWaiter>> region_wait(regions.size());
2896 auto draw_at_arrival = [&](std::size_t i, std::size_t r) ->
bool {
2897 const StationState& s = S[i];
2902 if (s.bmsp)
return false;
2903 if (s.role != Role::Queue || s.ps || s.size_ordered)
return true;
2904 return !s.svc[r].time_varying();
2922 auto bmsp_arm = [&](std::size_t i) {
2923 StationState& s = S[i];
2924 const std::size_t r = s.bmsp_cls;
2925 const double gap = slot_snap(s.svc[r].next_at(g_svc[i][r], now),
"firing interval");
2926 const int b = s.svc[r].last_batch();
2927 s.bmsp_pending = (b > 0) ?
static_cast<std::size_t
>(b) : 1;
2928 if (!(gap > 0.0) && s.svc[r].time_varying())
return;
2940 auto start_service = [&](std::size_t i, std::size_t slot,
const Job& job_in) {
2941 StationState& s = S[i];
2951 if (!draw_at_arrival(i, job.cls)) {
2952 job.service = slot_snap(s.svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2953 job.remaining = job.service;
2959 if (!drew && s.preemptive && !s.resume && job.elapsed > 0.0) {
2960 job.service = slot_snap(s.svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2961 job.remaining = job.service;
2971 if (s.has_pools && !(s.preemptive && s.resume && job.elapsed > 0.0)) {
2972 const std::size_t ty = s.server_type[slot];
2973 if (s.type_has_svc[ty][job.cls]) {
2974 job.service = slot_snap(
2975 s.type_svc[ty][job.cls].next_at(g_hsvc[i][ty][job.cls], now),
2977 job.remaining = job.service;
2981 s.server[slot] = job;
2982 s.server_busy[slot] =
true;
2983 s.server_start[slot] = now;
2984 s.server_tag[slot] = ++ps_tag;
2990 double occupied = 1.0;
2991 if (s.has_parallelism && s.parallelism[job.cls] > 1) {
2992 const std::size_t need = s.parallelism[job.cls];
2993 std::vector<std::size_t> extras;
2994 for (std::size_t sl = 0; sl < s.nservers && extras.size() + 1 < need; ++sl) {
2995 if (sl == slot || s.server_busy[sl] || s.server_blocked[sl] || s.server_held[sl])
2997 s.server_busy[sl] =
true;
2998 extras.push_back(sl);
3000 if (extras.size() + 1 < need)
3001 throw InputError(
"SolverLDES (native engine): station '" + sn.stations[i].name +
3002 "' started a job holding " + std::to_string(need) +
3003 " servers with only " + std::to_string(extras.size() + 1) +
3005 s.held_extra[slot] = extras;
3006 occupied =
static_cast<double>(need);
3008 acc.update_busy(i, job.cls, now);
3011 acc.busy[i][job.cls] += occupied;
3018 const double scale0 = rate_scaling(i, job.cls);
3019 if (!(scale0 > 0.0))
return;
3021 e.t = now + job.remaining / scale0;
3026 e.tag = s.server_tag[slot];
3039 auto release_slots = [&](std::size_t i, std::size_t slot) ->
double {
3040 StationState& s = S[i];
3041 if (!s.has_parallelism || slot >= s.held_extra.size())
return 1.0;
3042 std::vector<std::size_t>& extras = s.held_extra[slot];
3043 if (extras.empty())
return 1.0;
3044 for (std::size_t j = 0; j < extras.size(); ++j) s.server_busy[extras[j]] =
false;
3045 const double n =
static_cast<double>(extras.size() + 1);
3058 auto serve_next_on_slot = [&](std::size_t i, std::size_t sl) {
3059 if (!buffer_has_for_slot(i, sl))
return;
3060 Job nextjob = buffer_pop_for_slot(i, sl);
3061 if (S[i].has_parallelism && free_slot_count(i) < S[i].parallelism[nextjob.cls]) {
3062 buffer_push(i, nextjob);
3065 start_service(i, sl, nextjob);
3075 auto preempt = [&](std::size_t i, std::size_t slot) {
3076 StationState& s = S[i];
3077 Job job = s.server[slot];
3078 const double served = now - s.server_start[slot];
3079 job.remaining = std::max(0.0, job.remaining - served);
3080 job.elapsed += served;
3081 s.server_busy[slot] =
false;
3082 s.server_tag[slot] = 0;
3083 const double freed = release_slots(i, slot);
3084 acc.update_busy(i, job.cls, now);
3085 acc.busy[i][job.cls] -= freed;
3088 buffer_push(i, job);
3099 std::function<void(std::size_t)> poll_serve;
3100 std::function<void(std::size_t)> poll_advance;
3102 poll_serve = [&](std::size_t i) {
3103 StationState& s = S[i];
3104 if (s.server_busy[0] || s.server_blocked[0] || s.server_held[0])
return;
3106 std::size_t at = s.buffer.size();
3107 for (std::size_t j = 0; j < s.buffer.size(); ++j)
3108 if (s.buffer[j].cls == s.poll_at &&
3109 (at == s.buffer.size() || s.buffer[j].t_arr < s.buffer[at].t_arr))
3111 if (at == s.buffer.size() || s.poll_budget == 0) {
3115 Job job = s.buffer[at];
3116 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
3117 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
3119 start_service(i, 0, job);
3122 poll_advance = [&](std::size_t i) {
3123 StationState& s = S[i];
3124 for (std::size_t step = 0; step < K; ++step) {
3125 const std::size_t from = s.poll_at;
3126 const std::size_t next = (from + 1) % K;
3129 if (s.has_switchover[from]) {
3130 s.poll_switching =
true;
3132 e.t = now + s.switchover[from].next(g_aux[i]);
3141 ? std::numeric_limits<std::size_t>::max()
3143 bool has_work =
false;
3144 for (
const Job& j : s.buffer)
3145 if (j.cls == next) {
3157 s.poll_parked =
true;
3172 std::function<void(std::size_t)> pas_reschedule = [&](std::size_t i) {
3173 StationState& s = S[i];
3174 if (s.pas_list.empty())
return;
3175 std::vector<std::size_t> seq;
3176 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
3177 const double total = s.pas_rate(seq);
3178 if (!(total > 0.0))
return;
3179 s.pas_tag = ++ps_tag;
3181 e.t = now + (-std::log(uniform01(g_aux[i])) / total);
3186 e.slot = std::numeric_limits<std::size_t>::max();
3194 auto pas_complete = [&](std::size_t i) -> Job {
3195 StationState& s = S[i];
3196 std::vector<std::size_t> seq;
3197 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
3198 std::vector<double> inc(seq.size(), 0.0);
3199 double prev = 0.0, total = 0.0;
3200 for (std::size_t p = 0; p < seq.size(); ++p) {
3201 std::vector<std::size_t> pre(seq.begin(),
3202 seq.begin() +
static_cast<std::ptrdiff_t
>(p + 1));
3203 const double cur = s.pas_rate(pre);
3204 inc[p] = std::max(0.0, cur - prev);
3208 std::size_t pos = 0;
3210 const double u = uniform01(g_routing) * total;
3212 for (std::size_t p = 0; p < inc.size(); ++p) {
3224 std::vector<std::size_t> chain(1, pos);
3225 std::size_t moving = seq[pos], cur = pos;
3227 std::size_t nxt = seq.size();
3228 for (std::size_t j = cur + 1; j < seq.size(); ++j) {
3229 const bool swappable =
3230 s.pas_swap.empty() ||
3231 (moving < s.pas_swap.size() && seq[j] < s.pas_swap[moving].size() &&
3232 s.pas_swap[moving][seq[j]]);
3238 if (nxt >= seq.size())
break;
3239 chain.push_back(nxt);
3243 Job departing = s.pas_list[chain.back()];
3244 std::vector<Job> arr = s.pas_list;
3245 for (std::size_t k = 0; k + 1 < chain.size(); ++k)
3246 arr[chain[k + 1]] = s.pas_list[chain[k]];
3247 std::vector<Job> survivors;
3248 for (std::size_t k = 0; k < arr.size(); ++k)
3249 if (k != chain.front()) survivors.push_back(arr[k]);
3250 s.pas_list = survivors;
3269 std::size_t hop_old_cls = K;
3270 std::function<void(std::size_t)> release_region;
3272 double pending_region_wait = 0.0;
3273 bool hop_internal =
false;
3274 std::function<bool(std::size_t, Job, std::size_t)> admit =
3275 [&](std::size_t i, Job job, std::size_t from) ->
bool {
3276 StationState& s = S[i];
3277 const double rwait = pending_region_wait;
3278 pending_region_wait = 0.0;
3280 throw InputError(
"SolverLDES (native engine): a job of class '" +
3281 sn.classes[job.cls].name +
"' reached station '" +
3282 sn.stations[i].name +
"', which does not serve it");
3290 int rgi = region_of[i];
3291 if (rgi >= 0 && from < M && region_of[from] == rgi) {
3292 const std::size_t oc = hop_old_cls;
3297 hop_internal =
true;
3298 if (oc != job.cls) {
3299 const std::size_t rg =
static_cast<std::size_t
>(rgi);
3300 regions[rg].completed[oc] += 1.0;
3308 Region& R = regions[
static_cast<std::size_t
>(rgi)];
3309 if (R.would_exceed(job.cls)) {
3311 if (R.drops(job.cls)) {
3312 R.dropped[job.cls] += 1.0;
3313 dropped[i][job.cls] += 1.0;
3321 w.job.id = ++job_id;
3322 region_wait[
static_cast<std::size_t
>(rgi)].push_back(w);
3323 R.blocked[job.cls] += 1.0;
3330 if (s.role == Role::Queue) {
3332 for (std::size_t r = 0; r < K; ++r) total += acc.qlen[i][r] - s.blocked_at[r];
3333 if (total + 1.0 > s.cap ||
3334 acc.qlen[i][job.cls] - s.blocked_at[job.cls] + 1.0 > s.classcap[job.cls]) {
3339 if (s.has_retrial[job.cls] &&
3340 (s.max_attempts[job.cls] <= 0 ||
3341 job.attempts < s.max_attempts[job.cls])) {
3342 const double dt = now - s.orbit_last;
3344 for (std::size_t r = 0; r < K; ++r) s.tot_orbit[r] += s.orbit_size[r] * dt;
3346 s.orbit_size[job.cls] += 1.0;
3348 e.t = now + s.retrial[job.cls].next(g_svc[i][job.cls]);
3353 e.job.attempts = job.attempts + 1;
3357 if (s.has_retrial[job.cls]) s.retrial_lost[job.cls] += 1.0;
3358 dropped[i][job.cls] += 1.0;
3364 if (!s.balk[job.cls].empty()) {
3366 for (
const StationState::BalkRule& br : s.balk[job.cls])
3367 if (total >= br.min_jobs && (br.max_jobs < 0.0 || total <= br.max_jobs))
3368 p = std::max(p, br.probability);
3369 if (p > 0.0 && uniform01(g_routing) < p) {
3370 balked[i][job.cls] += 1.0;
3376 job.region_wait = rwait;
3377 job.priority = classprio[job.cls];
3383 if (draw_at_arrival(i, job.cls)) {
3384 job.service = slot_snap(S[i].svc[job.cls].next_at(g_svc[i][job.cls], now),
3386 job.remaining = job.service;
3389 job.rank = uniform01(g_routing);
3390 job.deadline = now + classdeadline[job.cls];
3393 Region& R = regions[
static_cast<std::size_t
>(rgi)];
3398 acc.update_qlen(i, job.cls, now);
3399 acc.qlen[i][job.cls] += 1.0;
3402 acc.arrived[i][job.cls] += 1.0;
3403 bp.track(i, job.cls, +1, now);
3405 if (s.role == Role::Delay) {
3406 if (track_delay_jobs) {
3408 delay_live[i].push_back(job);
3411 e.t = now + job.service;
3415 e.slot = std::numeric_limits<std::size_t>::max();
3423 if (has_gd) sd_reschedule(i);
3430 if (s.lps_limit > 0 && s.ps_jobs.size() >= s.lps_limit) {
3431 buffer_push(i, job);
3436 pj.priority = job.priority;
3437 pj.t_arr = job.t_arr;
3438 pj.region_wait = job.region_wait;
3439 pj.t_sys = job.t_sys;
3440 pj.total = job.service;
3441 pj.remaining = job.service;
3442 pj.parent = job.parent;
3443 s.ps_jobs.push_back(pj);
3450 if (s.has_setup && !s.setup_on) {
3451 buffer_push(i, job);
3452 if (!s.setup_running) {
3453 s.setup_running =
true;
3455 e.t = now + s.setup_time.next(g_aux[i]);
3464 s.pas_list.push_back(job);
3465 acc.update_busy(i, job.cls, now);
3466 acc.busy[i][job.cls] += 1.0;
3471 buffer_push(i, job);
3472 if (s.poll_parked && !s.poll_switching) {
3473 s.poll_parked =
false;
3474 if (job.cls == s.poll_at)
3478 }
else if (!s.server_busy[0] && !s.poll_switching) {
3487 buffer_push(i, job);
3488 if (!s.server_busy[0]) {
3489 s.server_busy[0] =
true;
3490 s.server_start[0] = now;
3491 acc.update_busy(i, job.cls, now);
3492 acc.busy[i][job.cls] += 1.0;
3498 const std::size_t sl = free_slot_for(i, job.cls);
3499 if (sl < s.nservers) {
3500 start_service(i, sl, job);
3515 (!s.has_parallelism || free_slot_count(i) + 1 >= s.parallelism[job.cls])) {
3516 std::vector<Job> held;
3517 std::vector<std::size_t> slot_of;
3518 for (std::size_t sl = 0; sl < s.nservers; ++sl)
3519 if (s.server_busy[sl] && !s.server_blocked[sl]) {
3524 if (s.has_pools && !s.type_compat[s.server_type[sl]][job.cls])
continue;
3525 Job h = s.server[sl];
3529 h.remaining = std::max(0.0, h.remaining - (now - s.server_start[sl]));
3530 h.elapsed += now - s.server_start[sl];
3532 slot_of.push_back(sl);
3535 static_cast<double>(s.nservers), now);
3536 if (v < held.size()) {
3537 const std::size_t sl = slot_of[v];
3539 start_service(i, sl, job);
3544 buffer_push(i, job);
3549 if (s.has_patience[job.cls]) {
3551 e.t = now + s.patience[job.cls].next(g_svc[i][job.cls]);
3562 const std::map<std::size_t, double> kNoWeights;
3564 std::vector<std::size_t> tab_all;
3574 auto draw_prob = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
3576 for (std::size_t k = 0; k < tab.size(); ++k) total += tab[k].mass;
3577 const double u = uniform01(g_routing) * total;
3579 std::size_t pick = 0;
3580 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3581 cum += tab[pick].mass;
3582 if (u <= cum)
break;
3598 const bool has_sdr = !sn.sdr.empty() && !sn.sdr_nodes.empty();
3599 pfqn::SdrCoeff sdr_coeff;
3601 std::vector<double> sdr_pop, sdr_mass;
3613 auto draw_sdr = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
3614 sdr_pop.assign(M, 0.0);
3615 for (std::size_t i = 0; i < M; ++i) {
3616 if (S[i].role == Role::Source)
continue;
3618 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[i][q];
3624 sdr_mass.assign(tab.size(), 0.0);
3626 for (std::size_t i = 0; i < tab.size(); ++i) {
3627 const std::size_t dnode = tab[i].node - 1;
3629 for (std::size_t b = 1; b < sn.sdr_nodes.branch.size(); ++b)
3630 if (sn.sdr_nodes.entryOf[b] == dnode) p += Pb[b];
3631 if (sn.sdr_nodes.departure == dnode) p += Ped;
3634 if (p < 0.0) p = 0.0;
3643 if (!(total > 0.0))
return draw_prob(tab);
3644 const double u = uniform01(g_routing) * total;
3646 std::size_t pick = 0;
3647 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3648 cum += sdr_mass[pick];
3649 if (u <= cum)
break;
3662 auto shortest_queue = [&](
const std::vector<RouteDest>& tab,
3663 const std::vector<std::size_t>& cand) -> std::size_t {
3664 double best = std::numeric_limits<double>::infinity();
3665 std::vector<std::size_t> tied;
3666 for (std::size_t c = 0; c < cand.size(); ++c) {
3667 const std::size_t k = cand[c];
3668 if (tab[k].station >= M || S[tab[k].station].role == Role::Source)
continue;
3670 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[tab[k].station][q];
3675 }
else if (held == best) {
3679 if (tied.empty())
return cand.empty() ? 0 : cand[0];
3680 if (tied.size() == 1)
return tied[0];
3681 std::size_t at =
static_cast<std::size_t
>(uniform01(g_routing) *
3682 static_cast<double>(tied.size()));
3683 if (at >= tied.size()) at = tied.size() - 1;
3697 auto draw_node_route = [&](std::size_t inode, std::size_t r) -> RouteEntry {
3698 const std::vector<RouteDest>& tab = nroute[inode][r];
3700 throw InputError(
"SolverLDES (native engine): node '" + sn.nodes[inode - 1].name +
3701 "' has no routing for class '" + sn.classes[r].name +
"'");
3702 std::size_t pick = 0;
3703 if (tab.size() > 1) {
3704 if (tab_all.size() != tab.size()) {
3705 tab_all.resize(tab.size());
3706 for (std::size_t k = 0; k < tab.size(); ++k) tab_all[k] = k;
3708 switch (node_routing[inode][r]) {
3710 pick =
static_cast<std::size_t
>(uniform01(g_routing) *
3711 static_cast<double>(tab.size()));
3712 if (pick >= tab.size()) pick = tab.size() - 1;
3716 pick = rr_counter[inode][r] % tab.size();
3717 ++rr_counter[inode][r];
3726 const std::map<std::size_t, double>& w =
3727 (sn.nodes[inode - 1].routing_weights.size() > r)
3728 ? sn.nodes[inode - 1].routing_weights[r]
3730 std::vector<double> raw(tab.size(), 0.0);
3731 bool integral =
true;
3732 for (std::size_t k = 0; k < tab.size(); ++k) {
3733 const std::map<std::size_t, double>::const_iterator it = w.find(tab[k].node);
3734 raw[k] = (it == w.end()) ? 0.0 : it->second;
3735 if (raw[k] < 0.0 || raw[k] != std::floor(raw[k])) integral =
false;
3737 std::vector<std::size_t> quota(tab.size(), 0);
3738 std::size_t total = 0;
3739 for (std::size_t k = 0; k < tab.size(); ++k) {
3740 double v = integral ? raw[k] : raw[k] * 1000.0;
3741 std::size_t q = (v > 0.0) ?
static_cast<std::size_t
>(v) : 0;
3742 if (raw[k] > 0.0 && q < 1) q = 1;
3749 pick = rr_counter[inode][r] % tab.size();
3750 ++rr_counter[inode][r];
3753 std::size_t counter = rr_counter[inode][r] % total;
3754 ++rr_counter[inode][r];
3755 std::size_t cum = 0;
3756 for (pick = 0; pick + 1 < tab.size(); ++pick) {
3758 if (counter < cum)
break;
3770 pick = has_sdr ? draw_sdr(tab) : draw_prob(tab);
3774 pick = shortest_queue(tab, tab_all);
3781 std::size_t d = (sn.nodes[inode - 1].routing_param.size() > r &&
3782 sn.nodes[inode - 1].routing_param[r] > 0)
3783 ?
static_cast<std::size_t
>(
3784 sn.nodes[inode - 1].routing_param[r])
3786 if (tab.size() <= d) {
3787 pick = shortest_queue(tab, tab_all);
3790 std::vector<std::size_t> avail(tab.size());
3791 for (std::size_t k = 0; k < tab.size(); ++k) avail[k] = k;
3792 std::vector<std::size_t> cand;
3793 for (std::size_t k = 0; k < d && !avail.empty(); ++k) {
3794 std::size_t at =
static_cast<std::size_t
>(
3795 uniform01(g_routing) *
static_cast<double>(avail.size()));
3796 if (at >= avail.size()) at = avail.size() - 1;
3797 cand.push_back(avail[at]);
3798 avail.erase(avail.begin() +
static_cast<std::ptrdiff_t
>(at));
3800 pick = shortest_queue(tab, cand);
3804 pick = draw_prob(tab);
3809 const RouteDest& d = tab[pick];
3812 e.station = d.station;
3814 e.cls = d.cls[0].first;
3815 if (d.cls.size() > 1) {
3817 for (std::size_t k = 0; k < d.cls.size(); ++k) total += d.cls[k].second;
3818 const double u = uniform01(g_routing) * total;
3820 for (std::size_t k = 0; k < d.cls.size(); ++k) {
3821 cum += d.cls[k].second;
3822 if (u <= cum || k + 1 == d.cls.size()) {
3823 e.cls = d.cls[k].first;
3852 auto resolve_final_cls = [&](std::size_t node, std::size_t cls) -> std::size_t {
3853 std::size_t at_node = node, at_cls = cls;
3854 for (std::size_t hop = 0; hop <= nnodes && at_node != 0; ++hop) {
3855 const NodeType dt = sn.nodes[at_node - 1].nodetype;
3856 if (dt != NodeType::ClassSwitch && dt != NodeType::Router && dt != NodeType::Logger)
3858 const RouteEntry e = draw_node_route(at_node, at_cls);
3891 auto resolve_forced_dest = [&](
const RouteEntry& re) -> RouteEntry {
3893 for (std::size_t hop = 0; hop <= nnodes; ++hop) {
3894 if (at.node == 0 || at.sink)
break;
3895 const NodeType dt = sn.nodes[at.node - 1].nodetype;
3896 if (dt != NodeType::ClassSwitch && dt != NodeType::Router && dt != NodeType::Logger)
3898 const std::vector<RouteDest>& tab = nroute[at.node][at.cls];
3899 if (tab.size() != 1 || tab[0].cls.size() != 1)
break;
3901 nxt.node = tab[0].node;
3902 nxt.station = tab[0].station;
3903 nxt.sink = tab[0].sink;
3904 nxt.cls = tab[0].cls[0].first;
3915 if (o.busy_period_orders > 0) {
3916 std::vector<std::vector<std::size_t>> sets;
3917 std::vector<std::string> set_names;
3918 for (std::size_t i = 0; i < M; ++i) {
3919 if (S[i].role == Role::Source || S[i].role == Role::Synchronization)
continue;
3920 sets.push_back(std::vector<std::size_t>(1, i));
3921 set_names.push_back(sn.stations[i].name);
3923 for (
const std::vector<std::size_t>& sub : o.busy_period_subnets) {
3925 for (std::size_t s2 : sub) {
3926 if (s2 >= M || S[s2].role == Role::Source)
3927 throw InputError(
"SolverLDES (native engine): station " +
3928 std::to_string(s2) +
3929 " of a busy period subnetwork is not a service station");
3930 nm += (nm.empty() ?
"" :
"+") + sn.stations[s2].name;
3932 sets.push_back(sub);
3933 set_names.push_back(nm);
3935 std::vector<BpTarget> targets;
3936 for (std::size_t si = 0; si < sets.size(); ++si) {
3938 t.stations = sets[si];
3940 t.name = set_names[si];
3941 targets.push_back(t);
3942 for (std::size_t r = 0; r < K; ++r) {
3944 tc.stations = sets[si];
3945 tc.job_class =
static_cast<int>(r);
3946 tc.name = set_names[si] +
":" + sn.classes[r].name;
3947 targets.push_back(tc);
3950 bp.init(targets, o.busy_period_orders, M);
3963 std::function<void(std::size_t)> release_blocked = [&](std::size_t j) {
3964 bool progress =
true;
3967 std::size_t best_i = M, best_sl = 0;
3968 double oldest = std::numeric_limits<double>::infinity();
3969 for (std::size_t a = 0; a < M; ++a) {
3973 if (S[a].role != Role::Queue || S[a].ps || S[a].server_blocked.empty())
continue;
3974 for (std::size_t sl = 0; sl < S[a].nservers; ++sl)
3975 if (S[a].server_blocked[sl] && S[a].blocked_dest[sl] == j &&
3976 S[a].blocked_job[sl].t_arr < oldest) {
3977 oldest = S[a].blocked_job[sl].t_arr;
3982 if (best_i >= M)
break;
3983 const std::size_t dcls = S[best_i].blocked_dest_cls[best_sl];
3984 if (!dest_has_room(j, dcls))
break;
3986 StationState& src = S[best_i];
3987 Job moved = src.blocked_job[best_sl];
3988 src.server_blocked[best_sl] =
false;
3989 src.server_busy[best_sl] =
false;
3990 src.server_tag[best_sl] = 0;
3991 acc.update_qlen(j, dcls, now);
3992 acc.qlen[j][dcls] -= 1.0;
3993 S[j].blocked_at[dcls] -= 1.0;
3996 acc.update_busy(best_i, moved.cls, now);
3997 if (acc.busy[best_i][moved.cls] > 0.0) acc.busy[best_i][moved.cls] -= 1.0;
4001 fresh.t_sys = moved.t_sys;
4002 fresh.parent = moved.parent;
4003 admit(j, fresh, best_i);
4007 serve_next_on_slot(best_i, best_sl);
4026 release_region = [&](std::size_t rg) {
4027 std::vector<RegionWaiter>& q = region_wait[rg];
4028 while (!q.empty()) {
4030 const std::size_t w = 0;
4032 if (R.would_exceed(q[w].job.cls))
break;
4033 RegionWaiter taken = q[w];
4035 R.blocked[taken.job.cls] -= 1.0;
4036 q.erase(q.begin() +
static_cast<std::ptrdiff_t
>(w));
4038 fresh.cls = taken.job.cls;
4039 fresh.t_sys = taken.job.t_sys;
4042 fresh.parent = taken.job.parent;
4043 fresh.call = taken.job.call;
4047 pending_region_wait = now - taken.job.t_arr;
4048 admit(taken.station, fresh, M);
4056 auto signal_held = [&](std::size_t i) -> std::size_t {
4057 const StationState& s = S[i];
4058 if (s.role == Role::Delay)
return delay_live[i].size();
4059 if (s.pas)
return s.pas_list.size();
4060 std::size_t n = s.buffer.size();
4061 if (s.ps)
return n + s.ps_jobs.size();
4062 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4063 if (s.server_busy[sl]) ++n;
4084 StationState& s = S[i];
4087 if (s.role == Role::Delay) {
4088 std::vector<Job>& live = delay_live[i];
4089 if (live.empty())
return K;
4092 for (std::size_t j = 1; j < live.size(); ++j)
4093 if (live[j].t_arr < live[at].t_arr) at = j;
4095 for (std::size_t j = 1; j < live.size(); ++j)
4096 if (live[j].t_arr > live[at].t_arr) at = j;
4098 at =
static_cast<std::size_t
>(uniform01(g_routing) *
4099 static_cast<double>(live.size()));
4100 if (at >= live.size()) at = live.size() - 1;
4102 const std::size_t rc = live[at].cls;
4103 live.erase(live.begin() +
static_cast<std::ptrdiff_t
>(at));
4111 if (s.pas_list.empty())
return K;
4114 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
4115 if (s.pas_list[j].t_arr < s.pas_list[at].t_arr) at = j;
4117 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
4118 if (s.pas_list[j].t_arr > s.pas_list[at].t_arr) at = j;
4120 at =
static_cast<std::size_t
>(uniform01(g_routing) *
4121 static_cast<double>(s.pas_list.size()));
4122 if (at >= s.pas_list.size()) at = s.pas_list.size() - 1;
4124 const std::size_t rc = s.pas_list[at].cls;
4125 s.pas_list.erase(s.pas_list.begin() +
static_cast<std::ptrdiff_t
>(at));
4126 acc.update_busy(i, rc, now);
4127 if (acc.busy[i][rc] > 0.0) acc.busy[i][rc] -= 1.0;
4132 const std::size_t waiting = s.buffer.size();
4137 const std::size_t inservice = s.ps_jobs.size();
4138 if (waiting + inservice == 0)
return K;
4141 const std::size_t pick =
static_cast<std::size_t
>(
4142 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
4143 from_wait = pick < waiting;
4145 from_wait = waiting > 0;
4150 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4151 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
4153 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4154 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
4156 at = std::min(waiting - 1,
4157 static_cast<std::size_t
>(uniform01(g_routing) *
4158 static_cast<double>(waiting)));
4160 const std::size_t rc = s.buffer[at].cls;
4161 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
4162 if (s.sched != SchedStrategy::FSP)
4163 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
4169 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
4170 if (s.ps_jobs[j].t_arr < s.ps_jobs[at].t_arr) at = j;
4172 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
4173 if (s.ps_jobs[j].t_arr > s.ps_jobs[at].t_arr) at = j;
4175 at = std::min(inservice - 1,
4176 static_cast<std::size_t
>(uniform01(g_routing) *
4177 static_cast<double>(inservice)));
4179 const std::size_t rc = s.ps_jobs[at].cls;
4180 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(at));
4182 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
4183 Job nextjob = buffer_pop(i);
4185 pj.cls = nextjob.cls;
4186 pj.priority = nextjob.priority;
4187 pj.t_arr = nextjob.t_arr;
4188 pj.region_wait = nextjob.region_wait;
4189 pj.t_sys = nextjob.t_sys;
4190 pj.total = nextjob.service;
4191 pj.remaining = nextjob.service;
4192 pj.parent = nextjob.parent;
4193 s.ps_jobs.push_back(pj);
4202 std::size_t inservice = 0;
4203 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4204 if (s.server_busy[sl]) ++inservice;
4205 if (waiting + inservice == 0)
return K;
4208 const std::size_t pick =
static_cast<std::size_t
>(
4209 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
4210 from_wait = pick < waiting;
4212 from_wait = waiting > 0;
4218 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4219 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
4221 for (std::size_t j = 1; j < s.buffer.size(); ++j)
4222 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
4224 at = std::min(waiting - 1,
4225 static_cast<std::size_t
>(uniform01(g_routing) *
4226 static_cast<double>(waiting)));
4228 const std::size_t rc = s.buffer[at].cls;
4229 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
4230 if (s.sched != SchedStrategy::FSP)
4231 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
4235 std::size_t slot = s.nservers;
4236 for (std::size_t sl = 0; sl < s.nservers; ++sl) {
4237 if (!s.server_busy[sl])
continue;
4238 if (slot == s.nservers) {
4243 if (s.server[sl].t_arr < s.server[slot].t_arr) slot = sl;
4245 if (s.server[sl].t_arr > s.server[slot].t_arr) slot = sl;
4249 std::size_t pick = std::min(inservice - 1,
4250 static_cast<std::size_t
>(uniform01(g_routing) *
4251 static_cast<double>(inservice)));
4252 for (std::size_t sl = 0; sl < s.nservers; ++sl)
4253 if (s.server_busy[sl]) {
4261 if (slot == s.nservers)
return K;
4263 const std::size_t rc = s.server[slot].cls;
4266 s.server_busy[slot] =
false;
4267 s.server_tag[slot] = 0;
4268 const double freed = release_slots(i, slot);
4269 acc.update_busy(i, rc, now);
4270 acc.busy[i][rc] -= freed;
4275 }
else if (!s.server_blocked[slot] && !s.server_held[slot]) {
4276 serve_next_on_slot(i, slot);
4298 auto apply_signal = [&](std::size_t i,
const Job& sig) {
4299 StationState& s = S[i];
4300 const std::size_t held = signal_held(i);
4302 std::size_t to_remove = 1;
4303 if (sig.cls < sn.signaltype.size() &&
4306 }
else if (sig.cls < sn.signalremdist.size() && !sn.signalremdist[sig.cls].empty()) {
4307 const std::vector<T>& pmf = sn.signalremdist[sig.cls];
4308 const double u = uniform01(g_routing);
4311 for (; b + 1 < pmf.size(); ++b) {
4312 cum +=
static_cast<double>(pmf[b]);
4315 to_remove = std::min(b, held);
4317 to_remove = std::min<std::size_t>(1, held);
4320 if (sig.cls < sn.signalrempolicy.size()) policy = sn.signalrempolicy[sig.cls];
4321 for (std::size_t rep = 0; rep < to_remove; ++rep) {
4322 if (signal_held(i) == 0)
break;
4323 const std::size_t rc = signal_remove_one(i, policy);
4326 acc.update_qlen(i, rc, now);
4327 acc.qlen[i][rc] -= 1.0;
4328 bp.track(i, rc, -1, now);
4332 if (region_of[i] >= 0) {
4333 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
4334 regions[rg].update(now);
4335 regions[rg].leave(rc);
4344 sys_resp_sum[sig.cls] += now - sig.t_sys;
4345 sys_resp_cnt[sig.cls] += 1.0;
4346 sys_completed[sig.cls] += 1.0;
4363 std::uint64_t total_completions = 0;
4375 std::function<void(CacheState&, std::size_t, std::size_t)> release_delayed_hits;
4391 std::function<void(std::size_t, Job, std::size_t)> deliver =
4392 [&](std::size_t node, Job job, std::size_t from) {
4393 const NodeType nt = sn.nodes[node - 1].nodetype;
4395 if (nt == NodeType::Sink) {
4396 sys_resp_sum[job.cls] += now - job.t_sys;
4397 sys_resp_cnt[job.cls] += 1.0;
4398 sys_completed[job.cls] += 1.0;
4402 if (nt == NodeType::Cache) {
4403 auto ci = caches.find(node);
4404 if (ci == caches.end())
4405 throw InputError(
"SolverLDES (native engine): Cache node '" +
4406 sn.nodes[node - 1].name +
"' carries no parameters");
4407 CacheState& cs = ci->second;
4415 if (cs.has_retrieval) {
4416 const int fetched = cs.fetches_item(job.cls);
4418 const std::size_t fi =
static_cast<std::size_t
>(fetched);
4419 cache_miss(cs, fi, uniform01(g_routing), uniform01(g_routing));
4420 cs.in_flight[fi] = 0;
4421 cs.total_fetch_time += now - cs.fetch_start[fi];
4422 cs.completed_fetches += 1.0;
4423 release_delayed_hits(cs, fi, node);
4424 if (cs.miss_class[job.cls] >= 0)
4425 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
4426 const RouteEntry re = draw_node_route(node, out.cls);
4428 if (re.node != 0 && sn.nodes[re.node - 1].nodetype == NodeType::Sink)
4429 ++total_completions;
4430 deliver(re.node, out, M);
4435 const std::vector<double>& pop = cs.popularity[job.cls];
4437 throw InputError(
"SolverLDES (native engine): class '" +
4438 sn.classes[job.cls].name +
"' reads cache '" +
4439 sn.nodes[node - 1].name +
"' with no item popularity");
4440 const double u = uniform01(g_routing);
4441 std::size_t item = 0;
4442 while (item + 1 < pop.size() && u >= pop[item]) ++item;
4444 const int at = cs.find(item);
4446 cs.hits[job.cls] += 1.0;
4447 cache_hit(cs, item,
static_cast<std::size_t
>(at), uniform01(g_routing));
4448 if (cs.hit_class[job.cls] >= 0)
4449 out.cls =
static_cast<std::size_t
>(cs.hit_class[job.cls]);
4450 }
else if (cs.has_retrieval && cs.retrieval_of(item, job.cls) > 0) {
4451 if (cs.in_flight[item]) {
4455 CacheState::HeldRequest hr;
4458 hr.t_sys = job.t_sys;
4459 cs.held[item].push_back(hr);
4465 cs.misses[job.cls] += 1.0;
4466 cs.in_flight[item] = 1;
4467 cs.fetch_start[item] = now;
4468 out.cls = cs.retrieval_of(item, job.cls) - 1;
4470 cs.misses[job.cls] += 1.0;
4471 cache_miss(cs, item, uniform01(g_routing), uniform01(g_routing));
4472 if (cs.miss_class[job.cls] >= 0)
4473 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
4477 const RouteEntry e = draw_node_route(node, out.cls);
4481 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
4482 ++total_completions;
4483 deliver(e.node, out, M);
4487 if (nt == NodeType::ClassSwitch || nt == NodeType::Router || nt == NodeType::Logger) {
4503 std::size_t at = node;
4504 for (std::size_t hop = 0; hop <= nnodes; ++hop) {
4505 const RouteEntry e = draw_node_route(at, out.cls);
4507 if (e.node == 0)
return;
4508 const NodeType dt = sn.nodes[e.node - 1].nodetype;
4509 if (dt == NodeType::ClassSwitch || dt == NodeType::Router ||
4510 dt == NodeType::Logger) {
4514 deliver(e.node, out, from);
4517 throw InputError(
"SolverLDES (native engine): the routing out of node '" +
4518 sn.nodes[node - 1].name +
4519 "' cycles through pass-through nodes without reaching a station");
4522 if (nt == NodeType::Fork) {
4526 const std::vector<RouteDest>& tab = nroute[node][job.cls];
4527 if (tab.empty())
return;
4528 std::vector<RouteEntry> links;
4529 for (std::size_t di = 0; di < tab.size(); ++di)
4530 for (std::size_t ci = 0; ci < tab[di].cls.size(); ++ci) {
4532 e.node = tab[di].node;
4533 e.station = tab[di].station;
4534 e.sink = tab[di].sink;
4535 e.cls = tab[di].cls[ci].first;
4538 const qn::NodeDef& fk = sn.nodes[node - 1];
4539 const qn::ForkParam<double>* fp = sn.fork_param_of(node);
4540 const int per_link = std::max(1,
static_cast<int>(fk.tasks_per_link + 0.5));
4548 std::vector<int> per_branch(links.size(), per_link);
4550 const bool variable = fork_is_variable(node);
4551 for (std::size_t li = 0; li < links.size(); ++li) {
4553 const std::size_t k0 = links[li].node - 1, r0 = job.cls;
4554 const double p = fp->fan_out_prob(k0, r0);
4555 if (p < 1.0 && g_fork.aux.next_double() >= p) {
4557 }
else if (!fp->fan_out_dist[k0][r0].disabled) {
4558 per_branch[li] = sample_fork_degree(fp->fan_out_dist[k0][r0]);
4560 per_branch[li] =
static_cast<int>(fp->fan_out_link(k0, r0) + 0.5);
4563 total += per_branch[li];
4566 throw InputError(
"SolverLDES (native engine): Fork '" + fk.name +
4567 "' emitted no sibling; at least one branch must be certain "
4568 "to emit at least one task");
4572 fs.t_sys = job.t_sys;
4574 fs.required = fs.total;
4575 const int jn = join_of_fork[node];
4577 auto jd = sn.joindecl.find(
static_cast<std::size_t
>(jn));
4578 if (jd != sn.joindecl.end() &&
4584 fs.required = std::min(fs.total,
4585 std::max(1,
static_cast<int>(jd->second.quorum + 0.5)));
4588 const std::uint64_t pid = ++next_parent;
4589 fork_sync[pid] = fs;
4590 for (std::size_t li = 0; li < links.size(); ++li)
4591 for (
int t = 0; t < per_branch[li]; ++t) {
4593 sib.cls = links[li].cls;
4594 sib.t_sys = job.t_sys;
4596 deliver(links[li].node, sib, M);
4601 if (nt == NodeType::Join) {
4602 auto it = fork_sync.find(job.parent);
4603 if (it == fork_sync.end()) {
4615 const std::size_t jd = node_to_station[node];
4617 acc.arrived[jd][job.cls] += 1.0;
4618 acc.join_dropped[jd][job.cls] += 1.0;
4622 ForkSync& fs = it->second;
4631 const std::size_t js = node_to_station[node];
4633 acc.update_qlen(js, job.cls, now);
4634 acc.qlen[js][job.cls] += 1.0;
4635 acc.arrived[js][job.cls] += 1.0;
4637 fs.siblings.push_back(std::make_pair(job.cls, now));
4638 if (
static_cast<int>(fs.siblings.size()) < fs.required)
return;
4642 for (std::size_t si = 0; si < fs.siblings.size(); ++si) {
4643 const std::size_t sc = fs.siblings[si].first;
4644 acc.update_qlen(js, sc, now);
4645 acc.qlen[js][sc] -= 1.0;
4648 acc.resp_sum[js][sc] += now - fs.siblings[si].second;
4649 acc.resp_cnt[js][sc] += 1.0;
4651 acc.completed[js][fs.cls] += 1.0;
4654 merged.cls = fs.cls;
4655 merged.t_sys = fs.t_sys;
4656 fork_sync.erase(it);
4657 const RouteEntry e = draw_node_route(node, merged.cls);
4659 deliver(e.node, merged, M);
4664 const std::size_t j = node_to_station[node];
4668 if (has_removal_signal && j < M && is_removal_signal[job.cls]) {
4669 apply_signal(j, job);
4682 if (has_sync_call && j < M && is_reply_signal[job.cls]) {
4683 acc.completed[j][job.cls] += 1.0;
4684 typename std::map<std::uint64_t, PendingCall>::iterator pc =
4685 pending_reply.find(job.call);
4686 if (pc != pending_reply.end()) {
4687 const std::size_t bi = pc->second.station, bs = pc->second.slot;
4688 const std::size_t bc = pc->second.cls;
4689 pending_reply.erase(pc);
4690 StationState& bst = S[bi];
4691 bst.server_held[bs] =
false;
4692 acc.update_busy(bi, bc, now);
4693 if (acc.busy[bi][bc] > 0.0) acc.busy[bi][bc] -= 1.0;
4694 acc.update_qlen(bi, bc, now);
4695 acc.held[bi][bc] -= 1.0;
4696 if (!bst.server_blocked[bs]) {
4697 serve_next_on_slot(bi, bs);
4700 release_blocked(bi);
4702 const RouteEntry re2 = draw_node_route(node, job.cls);
4704 onward.cls = re2.cls;
4705 onward.t_sys = job.t_sys;
4706 onward.parent = job.parent;
4707 deliver(re2.node, onward, M);
4715 j + 1 == sn.classes[job.cls].refstat) {
4716 sys_resp_sum[job.cls] += now - job.t_sys;
4717 sys_resp_cnt[job.cls] += 1.0;
4718 sys_completed[job.cls] += 1.0;
4722 throw InputError(
"SolverLDES (native engine): a closed job was dropped at "
4723 "station '" + sn.stations[j].name +
4724 "'; a closed class cannot lose population");
4727 release_delayed_hits = [&](CacheState& cs, std::size_t item, std::size_t node) {
4728 if (item >= cs.held.size() || cs.held[item].empty())
return;
4729 std::vector<CacheState::HeldRequest> freed;
4730 freed.swap(cs.held[item]);
4731 for (std::size_t i = 0; i < freed.size(); ++i) {
4732 const CacheState::HeldRequest& hr = freed[i];
4733 cs.delayed[hr.cls] += 1.0;
4734 cs.delayed_wait += now - hr.hold_time;
4736 out.cls = (cs.hit_class[hr.cls] >= 0) ?
static_cast<std::size_t
>(cs.hit_class[hr.cls])
4738 out.t_sys = hr.t_sys;
4739 const RouteEntry e = draw_node_route(node, out.cls);
4741 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
4742 ++total_completions;
4743 deliver(e.node, out, M);
4755 auto flat_marking = [&]() {
4756 std::vector<double> tok(place_nodes.size() * K, 0.0);
4757 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4758 for (std::size_t r = 0; r < K; ++r) tok[p * K + r] = marking[p][r];
4772 auto flat_avail = [&]() {
4773 std::vector<double> tok(place_nodes.size() * K, 0.0);
4774 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4775 for (std::size_t r = 0; r < K; ++r) tok[p * K + r] = avail[p][r];
4788 auto spn_apply = [&](
const SpnMode& m) {
4789 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4790 for (std::size_t r = 0; r < K; ++r) {
4791 const double e = m.enabling[p * K + r];
4792 if (e > 0.0) place_take(p, r, e);
4794 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4795 for (std::size_t r = 0; r < K; ++r) {
4796 const double f = m.firing[p * K + r];
4797 if (f > 0.0) place_deposit(p, r, f);
4810 std::uint64_t fire_tag = 0;
4811 std::vector<std::uint64_t> live_fire(transitions.size(), 0);
4812 std::function<void()> spn_settle = [&]() {
4813 if (transitions.empty())
return;
4814 for (
int guard = 0; guard < 1000000; ++guard) {
4815 bool fired_any =
false;
4816 std::vector<double> tok = flat_avail();
4817 for (std::size_t t = 0; t < transitions.size(); ++t) {
4819 if (k < 0)
continue;
4820 const SpnMode& m = transitions[t].modes[
static_cast<std::size_t
>(k)];
4824 transitions[t].fired[
static_cast<std::size_t
>(k)] += 1.0;
4825 ++total_completions;
4829 if (!fired_any)
break;
4834 const std::vector<double> tok = flat_avail();
4835 const std::vector<double> mk = flat_marking();
4836 for (std::size_t t = 0; t < transitions.size(); ++t) {
4837 double best = std::numeric_limits<double>::infinity();
4839 for (std::size_t k = 0; k < transitions[t].modes.size(); ++k) {
4840 const SpnMode& m = transitions[t].modes[k];
4841 if (m.immediate || !
spn_enabled(m, tok))
continue;
4842 const double rate =
spn_rate(m, mk);
4843 if (!(rate > 0.0))
continue;
4844 const double d = -std::log(uniform01(g_spn)) / rate;
4847 best_mode =
static_cast<int>(k);
4850 live_fire[t] = ++fire_tag;
4851 if (best_mode < 0)
continue;
4856 e.cls =
static_cast<std::size_t
>(best_mode);
4857 e.tag = live_fire[t];
4880 const bool warm_start = !o.init_sol.empty() && place_nodes.empty();
4882 for (std::size_t i = 0; i < M; ++i) {
4883 if (S[i].role != Role::Queue && S[i].role != Role::Delay)
continue;
4884 for (std::size_t k = K; k-- > 0;) {
4885 const std::size_t idx = i * K + k;
4886 if (idx >= o.init_sol.size())
continue;
4887 const double v = o.init_sol[idx];
4888 if (!(v > 0.0))
continue;
4889 const std::size_t count =
static_cast<std::size_t
>(v);
4890 for (std::size_t j = 0; j < count; ++j) {
4894 if (!admit(i, job, M))
4895 throw InputError(
"SolverLDES (native engine): the warm-start placement "
4896 "of class '" + sn.classes[k].name +
"' does not fit "
4897 "station '" + sn.stations[i].name +
"'");
4903 for (std::size_t r = 0; r < K; ++r) {
4906 for (std::size_t i = 0; i < M; ++i) held += acc.qlen[i][r];
4907 const double want = sn.classes[r].population;
4908 if (std::fabs(held - want) > 0.5)
4909 throw InputError(
"SolverLDES (native engine): the warm-start placement holds " +
4910 std::to_string(
static_cast<long>(held + 0.5)) +
" jobs of class '" +
4911 sn.classes[r].name +
"' against a population of " +
4912 std::to_string(
static_cast<long>(want + 0.5)));
4918 for (std::size_t i = 0; i < M; ++i) {
4919 if (!S[i].has_breakdown)
continue;
4921 e.t = S[i].failure_time.next_at(g_aux[i], 0.0);
4927 std::vector<Sampler> arrival(K);
4928 std::vector<double> lambda(K, 0.0);
4937 std::vector<Sampler> arr_batch(K);
4938 std::vector<bool> has_arr_batch(K,
false);
4939 std::vector<Rng> g_arr_batch;
4940 g_arr_batch.reserve(K);
4941 for (std::size_t k = 0; k < K; ++k)
4942 g_arr_batch.push_back(Rng(seed_ll, 991LL + 97LL * (
static_cast<long long>(k) + 1LL)));
4943 if (source_st < M) {
4944 const auto& sb = sn.stations[source_st].arrival_batch;
4945 for (std::size_t r = 0; r < K && r < sb.size(); ++r) {
4946 if (sb[r].disabled)
continue;
4947 arr_batch[r] = Sampler(sb[r],
"the arrival batch of class '" + sn.classes[r].name +
4948 "' at station '" + sn.stations[source_st].name +
"'");
4949 has_arr_batch[r] =
true;
4966 std::vector<std::size_t> mark_class;
4967 std::vector<double> mark_rate;
4968 std::size_t mark_carrier = K;
4969 if (source_st < M) {
4970 for (std::size_t c : sn.stations[source_st].marked_classes)
4971 if (c >= 1 && c <= K) mark_class.push_back(c - 1);
4980 auto nominal_of = [](
const std::vector<T>& bp,
const std::vector<Matrix<T>>& segs) {
4981 const T zero = num_traits<T>::from_int(0);
4982 Matrix<T> acc(segs[0].rows(), segs[0].cols(), zero);
4984 for (std::size_t k = 0; k + 1 < bp.size(); ++k) total += T(bp[k + 1] - bp[k]);
4985 for (std::size_t k = 0; k < segs.size(); ++k) {
4986 const T w = T(T(bp[k + 1] - bp[k]) / total);
4987 for (std::size_t a = 0; a < acc.rows(); ++a)
4988 for (std::size_t b = 0; b < acc.cols(); ++b) acc(a, b) += w * segs[k](a, b);
5003 auto mean_batch_of = [&](
const lang::Distrib<T>& cd) ->
double {
5004 std::vector<Matrix<T>> blocks;
5005 std::vector<std::size_t> sizes;
5007 for (std::size_t b = 0; b < cd.Dmark.size(); ++b) {
5008 blocks.push_back(cd.Dmark[b]);
5009 sizes.push_back(b + 1);
5011 }
else if (cd.has_batch_schedule()) {
5012 for (std::size_t c = 0; c < cd.sched_Dbatch.size(); ++c)
5013 for (std::size_t b = 0; b < cd.sched_Dbatch[c].size(); ++b) {
5014 blocks.push_back(nominal_of(cd.sched_bp, cd.sched_Dbatch[c][b]));
5015 sizes.push_back(b + 1);
5018 if (blocks.empty())
return 1.0;
5024 double epochs = 0.0, jobs = 0.0;
5025 for (std::size_t b = 0; b < blocks.size(); ++b) {
5026 const double v = num_traits<T>::to_double(lam[b]);
5028 jobs +=
static_cast<double>(sizes[b]) * v;
5030 return (epochs > 0.0) ? (jobs / epochs) : 1.0;
5032 if (!mark_class.empty()) {
5033 mark_carrier = mark_class[0];
5034 const lang::Distrib<T>& cd = sn.service[source_st][mark_carrier];
5035 if (cd.Dmark.size() < mark_class.size())
5036 throw InputError(
"SolverLDES (native engine): the Source binds " +
5037 std::to_string(mark_class.size()) +
5038 " marks but its arrival process carries only " +
5039 std::to_string(cd.Dmark.size()) +
" marked blocks");
5040 if (cd.has_batch_schedule()) {
5045 const std::size_t B = cd.sched_Dbatch[0].size();
5046 std::vector<Matrix<T>> blocks;
5047 for (std::size_t c = 0; c < cd.sched_Dbatch.size(); ++c)
5048 for (std::size_t b = 0; b < B; ++b)
5049 blocks.push_back(nominal_of(cd.sched_bp, cd.sched_Dbatch[c][b]));
5055 for (std::size_t k = 0; k < mark_class.size(); ++k) {
5057 for (std::size_t b = 0; b < B; ++b)
5058 jobs +=
static_cast<double>(b + 1) *
5059 num_traits<T>::to_double(lam[k * B + b]);
5060 mark_rate.push_back(jobs);
5068 for (std::size_t k = 0; k < mark_class.size(); ++k)
5069 mark_rate.push_back(num_traits<T>::to_double(lam[k]));
5073 const std::vector<std::size_t>& mkc = mark_class;
5074 auto marked_non_carrier = [&mkc, &mark_carrier](std::size_t r) {
5075 if (mkc.empty() || r == mark_carrier)
return false;
5076 for (std::size_t c : mkc)
5077 if (c == r)
return true;
5081 for (std::size_t r = 0; r < K; ++r) {
5084 throw InputError(
"SolverLDES (native engine): an open class with no Source");
5085 if (S[source_st].off[r])
continue;
5086 arrival[r] = S[source_st].svc[r];
5090 lambda[r] = mean_batch_of(sn.service[source_st][r]) / arrival[r].mean();
5091 if (has_arr_batch[r]) lambda[r] = arr_batch[r].mean() / arrival[r].mean();
5092 for (std::size_t k = 0; k < mark_class.size(); ++k)
5093 if (mark_class[k] == r) lambda[r] = mark_rate[k];
5095 if (marked_non_carrier(r))
continue;
5097 e.t = slot_snap(arrival[r].next_at(g_arr[r], 0.0),
"interarrival time");
5099 e.station = source_st;
5103 if (warm_start)
continue;
5104 const double n = sn.classes[r].population;
5105 if (!(n >= 0.0) || !std::isfinite(n))
5106 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
5107 "' has a population that is not a finite count");
5108 const std::size_t refst = sn.classes[r].refstat;
5109 if (refst == 0 || refst > M)
5110 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
5111 "' has no reference station");
5121 if (sn.stations[refst - 1].nodetype == NodeType::Place)
continue;
5122 const std::size_t count =
static_cast<std::size_t
>(n + 0.5);
5123 for (std::size_t j = 0; j < count; ++j) {
5127 if (!admit(refst - 1, job, M))
5128 throw InputError(
"SolverLDES (native engine): the initial population of "
5129 "class '" + sn.classes[r].name +
5130 "' does not fit its reference station's capacity");
5137 cnvg.init(M, K, o, max_events);
5138 std::vector<std::size_t> servers_of(M, 1);
5139 std::vector<std::vector<bool>> off_of(M, std::vector<bool>(K,
true));
5140 for (std::size_t i = 0; i < M; ++i) {
5141 servers_of[i] = S[i].nservers;
5142 for (std::size_t r = 0; r < K; ++r)
5143 off_of[i][r] = S[i].off[r] || S[i].role == Role::Source ||
5144 S[i].role == Role::Synchronization;
5146 std::uint64_t last_cnvg_events = 0;
5147 bool converged =
false;
5152 for (std::size_t p = 0; p < place_nodes.size(); ++p)
5153 if (qplace[p]) place_try_start(p);
5158 Observations obs(M, K, o.tranfilter ==
"mser5", o.mserbatch > 0 ? o.mserbatch : 5);
5159 for (std::size_t i = 0; i < M; ++i)
5160 if (S[i].role == Role::Source || S[i].role == Role::Synchronization) obs.in_mser[i] = 0;
5161 std::uint64_t mser_interval = max_events / 1000ULL;
5162 if (mser_interval < 1) mser_interval = 1;
5163 std::uint64_t last_mser_events = 0;
5176 const bool transient_run = o.has_timespan && std::isfinite(o.t1) && o.t1 > o.t0;
5177 const double horizon = transient_run ? o.t1 : std::numeric_limits<double>::infinity();
5180 const double tran_points = (o.tranobs > 0) ?
static_cast<double>(o.tranobs) : 1000.0;
5181 const double tran_interval = transient_run ? (o.t1 - o.t0) / tran_points : 0.0;
5182 double next_tran_sample = transient_run ? o.t0 + tran_interval : 0.0;
5183 double last_tran_time = transient_run ? o.t0 : 0.0;
5184 std::vector<double> tran_times;
5185 std::vector<std::vector<std::vector<double>>> tran_q(M, std::vector<std::vector<double>>(K));
5186 std::vector<std::vector<std::vector<double>>> tran_u(M, std::vector<std::vector<double>>(K));
5187 std::vector<std::vector<std::vector<double>>> tran_t(M, std::vector<std::vector<double>>(K));
5188 std::vector<std::vector<double>> last_tran_q(M, std::vector<double>(K, 0.0));
5189 std::vector<std::vector<double>> last_tran_b(M, std::vector<double>(K, 0.0));
5190 std::vector<std::vector<double>> last_tran_c(M, std::vector<double>(K, 0.0));
5201 std::map<std::vector<int>,
double> histogram;
5202 std::vector<std::pair<double, std::vector<int>>> trajectory;
5203 double hist_last = 0.0;
5206 const bool want_traj = transient_run || o.export_trajectory;
5207 const bool want_hist = transient_run || o.export_histogram || want_traj;
5209 auto joint_state = [&]() {
5210 std::vector<int> row(M * K, 0);
5211 for (std::size_t i = 0; i < M; ++i)
5212 for (std::size_t r = 0; r < K; ++r)
5213 row[i * K + r] =
static_cast<int>(acc.qlen[i][r] + acc.held[i][r] + 0.5);
5216 auto hist_accumulate = [&]() {
5217 if (!want_hist)
return;
5218 const double dt = now - hist_last;
5220 const std::vector<int> row = joint_state();
5221 histogram[row] += dt;
5222 if (want_traj) trajectory.push_back(std::make_pair(hist_last, row));
5227 auto tran_sample = [&]() {
5228 const double dt = now - last_tran_time;
5229 tran_times.push_back(now);
5230 for (std::size_t i = 0; i < M; ++i)
5231 for (std::size_t r = 0; r < K; ++r) {
5233 tran_q[i][r].push_back((acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt);
5234 const double c = S[i].util_peak;
5235 tran_u[i][r].push_back(
5236 (S[i].role == Role::Delay)
5237 ? (acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt
5238 : (acc.tot_busy[i][r] - last_tran_b[i][r]) / (dt * c));
5239 tran_t[i][r].push_back((acc.completed[i][r] - last_tran_c[i][r]) / dt);
5241 tran_q[i][r].push_back(acc.qlen[i][r] + acc.held[i][r]);
5242 tran_u[i][r].push_back(0.0);
5243 tran_t[i][r].push_back(0.0);
5245 last_tran_q[i][r] = acc.tot_qlen[i][r];
5246 last_tran_b[i][r] = acc.tot_busy[i][r];
5247 last_tran_c[i][r] = acc.completed[i][r];
5249 last_tran_time = now;
5253 while (!evq.empty() && (transient_run || total_completions < max_events)) {
5254 const Event ev = evq.top();
5255 if (transient_run && ev.t > horizon)
break;
5262 if (want_hist) hist_accumulate();
5263 if (transient_run) {
5264 while (now >= next_tran_sample && next_tran_sample <= horizon) {
5265 const double save = now;
5266 now = next_tran_sample;
5267 for (std::size_t a = 0; a < M; ++a) {
5268 if (S[a].ps) ps_advance(a);
5269 for (std::size_t r = 0; r < K; ++r) {
5270 acc.update_qlen(a, r, now);
5271 acc.update_busy(a, r, now);
5275 next_tran_sample += tran_interval;
5280 if (ev.kind == EV_BREAKDOWN) {
5285 StationState& s = S[ev.station];
5287 ps_advance(ev.station);
5289 sd_advance(ev.station);
5292 ps_reschedule(ev.station);
5294 sd_reschedule(ev.station);
5304 nxt.t = now + (s.up ? s.failure_time.next_at(g_aux[ev.station], now)
5305 : s.repair_time.next_at(g_aux[ev.station], now));
5307 nxt.station = ev.station;
5312 if (ev.kind == EV_FIRING) {
5315 if (ev.station >= transitions.size() || live_fire[ev.station] != ev.tag)
continue;
5316 SpnTransition& tr = transitions[ev.station];
5317 const std::vector<double> tok = flat_avail();
5318 const SpnMode& m = tr.modes[ev.cls];
5321 tr.fired[ev.cls] += 1.0;
5322 ++total_completions;
5336 if (ev.kind == EV_PLACE_SVC) {
5337 const std::size_t p = ev.station;
5338 const std::size_t r = ev.cls;
5339 const std::size_t ist = place_station[p];
5341 acc.update_busy(ist, r, now);
5342 place_insvc[p][r] -= 1.0;
5343 acc.busy[ist][r] = place_insvc[p][r];
5352 if (ev.kind == EV_SWITCHOVER) {
5353 StationState& sp = S[ev.station];
5354 sp.poll_switching =
false;
5355 sp.poll_at = ev.cls;
5357 ? std::numeric_limits<std::size_t>::max()
5360 for (
const Job& j : sp.buffer)
5361 if (j.cls == sp.poll_at) {
5366 poll_serve(ev.station);
5368 poll_advance(ev.station);
5372 if (ev.kind == EV_RETRIAL) {
5373 StationState& sr = S[ev.station];
5374 const double dt = now - sr.orbit_last;
5376 for (std::size_t r = 0; r < K; ++r) sr.tot_orbit[r] += sr.orbit_size[r] * dt;
5377 sr.orbit_last = now;
5378 sr.orbit_size[ev.cls] -= 1.0;
5379 sr.retried[ev.cls] += 1.0;
5382 admit(ev.station, ev.job, M);
5386 if (ev.kind == EV_SETUP) {
5387 StationState& ss = S[ev.station];
5392 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[ev.station][r];
5393 if (held > 0.0 || now + 1e-12 < ss.delayoff_at)
continue;
5394 ss.setup_on =
false;
5395 ss.delayoff_at = std::numeric_limits<double>::infinity();
5398 if (ss.setup_on)
continue;
5399 ss.setup_running =
false;
5401 ss.delayoff_at = std::numeric_limits<double>::infinity();
5402 for (std::size_t sl = 0; sl < ss.nservers && !ss.buffer.empty(); ++sl)
5403 if (!ss.server_busy[sl] && !ss.server_blocked[sl] && !ss.server_held[sl] &&
5404 buffer_has_for_slot(ev.station, sl)) {
5405 Job nextjob = buffer_pop_for_slot(ev.station, sl);
5406 start_service(ev.station, sl, nextjob);
5408 sd_reschedule(ev.station);
5412 if (ev.kind == EV_RENEGE) {
5416 StationState& sr = S[ev.station];
5417 std::size_t at = sr.buffer.size();
5418 for (std::size_t j = 0; j < sr.buffer.size(); ++j)
5419 if (sr.buffer[j].id == ev.tag) {
5423 if (at == sr.buffer.size())
continue;
5424 const std::size_t rc = sr.buffer[at].cls;
5425 sd_advance(ev.station);
5426 acc.update_qlen(ev.station, rc, now);
5427 acc.qlen[ev.station][rc] -= 1.0;
5428 bp.track(ev.station, rc, -1, now);
5429 sr.buffer.erase(sr.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
5430 if (sr.sched != SchedStrategy::FSP)
5431 std::make_heap(sr.buffer.begin(), sr.buffer.end(), sr.cmp);
5432 reneged[ev.station][rc] += 1.0;
5433 sd_reschedule(ev.station);
5437 if (ev.kind == EV_ARRIVAL) {
5442 const int mark = arrival[ev.cls].last_mark();
5447 std::size_t batch = 1;
5448 if (arrival[ev.cls].batched()) {
5449 const int b = arrival[ev.cls].last_batch();
5450 if (b > 0) batch =
static_cast<std::size_t
>(b);
5451 }
else if (has_arr_batch[ev.cls]) {
5452 const double sampled = arr_batch[ev.cls].next(g_arr_batch[ev.cls]);
5453 const double rounded = std::floor(sampled + 0.5);
5454 if (!(rounded >= 1.0) || std::fabs(sampled - rounded) > 1e-9)
5456 "SolverLDES (native engine): the arrival batch of class '" +
5457 sn.classes[ev.cls].name +
"' sampled " + std::to_string(sampled) +
5458 ", which is not a positive integer; a batch-size law must be "
5459 "supported on {1,2,...}");
5460 batch =
static_cast<std::size_t
>(rounded);
5463 const double gap = slot_snap(arrival[ev.cls].next_at(g_arr[ev.cls], now),
5464 "interarrival time");
5467 if (!(gap > 0.0) && arrival[ev.cls].time_varying())
continue;
5470 nxt.station = ev.station;
5476 std::size_t arv_cls = ev.cls;
5477 if (mark > 0 &&
static_cast<std::size_t
>(mark) <= mark_class.size())
5478 arv_cls = mark_class[
static_cast<std::size_t
>(mark) - 1];
5485 for (std::size_t b = 0; b < batch; ++b) {
5486 const RouteEntry e = draw_node_route(sn.station_to_node[ev.station], arv_cls);
5490 deliver(e.node, job, M);
5496 const std::size_t i = ev.station;
5497 StationState& s = S[i];
5511 if (s.bmsp_tag != ev.tag)
continue;
5512 const std::size_t r = s.bmsp_cls;
5513 const std::size_t want = s.bmsp_pending;
5514 std::vector<Job> served;
5515 for (std::size_t d = 0; d < want && !s.buffer.empty(); ++d)
5516 served.push_back(buffer_pop(i));
5517 for (std::size_t d = 0; d < served.size(); ++d) {
5518 const Job& sj = served[d];
5519 acc.update_qlen(i, sj.cls, now);
5520 acc.qlen[i][sj.cls] -= 1.0;
5521 bp.track(i, sj.cls, -1, now);
5522 acc.completed[i][sj.cls] += 1.0;
5523 acc.resp_sum[i][sj.cls] += now - sj.t_arr + sj.region_wait;
5524 if (want_respt) resp_samples[i][sj.cls].push_back(now - sj.t_arr + sj.region_wait);
5525 acc.resp_cnt[i][sj.cls] += 1.0;
5526 ++total_completions;
5531 if (!s.buffer.empty()) {
5534 acc.update_busy(i, r, now);
5535 if (acc.busy[i][r] > 0.0) acc.busy[i][r] -= 1.0;
5536 s.server_busy[0] =
false;
5541 for (std::size_t d = 0; d < served.size(); ++d) {
5542 const Job& sj = served[d];
5543 const RouteEntry re2 = draw_node_route(sn.station_to_node[i], sj.cls);
5544 if (region_of[i] >= 0) {
5545 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
5546 const bool inside = !re2.sink && re2.station < M &&
5547 region_of[re2.station] == region_of[i];
5549 regions[rg].update(now);
5550 regions[rg].leave(sj.cls);
5551 regions[rg].completed[sj.cls] += 1.0;
5556 moved.cls = re2.cls;
5557 moved.t_sys = sj.t_sys;
5558 moved.parent = sj.parent;
5559 moved.call = sj.call;
5560 deliver(re2.node, moved, i);
5562 if (total_completions >= max_events)
break;
5566 if (s.role == Role::Delay) {
5568 if (track_delay_jobs) {
5574 std::size_t at = delay_live[i].size();
5575 for (std::size_t j = 0; j < delay_live[i].size(); ++j)
5576 if (delay_live[i][j].
id == job.id) {
5580 if (at == delay_live[i].size())
continue;
5581 if (delay_live[i][at].tag != ev.tag)
continue;
5582 delay_live[i].erase(delay_live[i].begin() +
static_cast<std::ptrdiff_t
>(at));
5586 std::size_t idx = s.ps_jobs.size();
5587 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
5588 if (s.ps_jobs[j].tag == ev.tag) {
5595 if (idx == s.ps_jobs.size())
continue;
5596 const PsJob& pj = s.ps_jobs[idx];
5598 job.t_arr = pj.t_arr;
5599 job.region_wait = pj.region_wait;
5600 job.t_sys = pj.t_sys;
5601 job.parent = pj.parent;
5602 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(idx));
5606 if (s.pas_tag != ev.tag || s.pas_list.empty())
continue;
5607 job = pas_complete(i);
5608 acc.update_busy(i, job.cls, now);
5609 if (acc.busy[i][job.cls] > 0.0) acc.busy[i][job.cls] -= 1.0;
5614 if (!s.server_busy[ev.slot] || s.server_tag[ev.slot] != ev.tag)
continue;
5616 job = s.server[ev.slot];
5617 s.server_busy[ev.slot] =
false;
5618 s.server_tag[ev.slot] = 0;
5619 const double freed = release_slots(i, ev.slot);
5620 acc.update_busy(i, job.cls, now);
5621 acc.busy[i][job.cls] -= freed;
5625 acc.update_qlen(i, job.cls, now);
5626 acc.qlen[i][job.cls] -= 1.0;
5627 bp.track(i, job.cls, -1, now);
5628 acc.completed[i][job.cls] += 1.0;
5629 acc.resp_sum[i][job.cls] += now - job.t_arr + job.region_wait;
5630 if (want_respt) resp_samples[i][job.cls].push_back(now - job.t_arr + job.region_wait);
5631 acc.resp_cnt[i][job.cls] += 1.0;
5632 ++total_completions;
5635 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
5636 Job nextjob = buffer_pop(i);
5638 pj.cls = nextjob.cls;
5639 pj.priority = nextjob.priority;
5640 pj.t_arr = nextjob.t_arr;
5641 pj.region_wait = nextjob.region_wait;
5642 pj.t_sys = nextjob.t_sys;
5643 pj.total = nextjob.service;
5644 pj.remaining = nextjob.service;
5645 pj.parent = nextjob.parent;
5646 s.ps_jobs.push_back(pj);
5651 const RouteEntry re = draw_node_route(sn.station_to_node[i], job.cls);
5652 const bool to_sink = re.sink;
5653 const std::size_t dst = re.station, dcls = re.cls;
5667 const RouteEntry fb = has_immfeed ? resolve_forced_dest(re) : re;
5668 const std::size_t fcls = fb.cls;
5669 if (has_immfeed && !fb.sink && fb.station == i && s.role == Role::Queue && !s.ps &&
5670 !s.pas && ev.slot < s.nservers && fcls < sn.immfeed[i].size() && sn.immfeed[i][fcls]) {
5673 fed.t_sys = job.t_sys;
5674 fed.parent = job.parent;
5676 fed.priority = classprio[fcls];
5677 if (draw_at_arrival(i, fcls)) {
5678 fed.service = slot_snap(s.svc[fcls].next_at(g_svc[i][fcls], now),
"service time");
5679 fed.remaining = fed.service;
5682 fed.rank = uniform01(g_routing);
5683 fed.deadline = now + classdeadline[fcls];
5686 acc.update_qlen(i, fcls, now);
5687 acc.qlen[i][fcls] += 1.0;
5693 acc.arrived[i][fcls] += 1.0;
5694 bp.track(i, fcls, +1, now);
5695 start_service(i, ev.slot, fed);
5697 if (total_completions >= max_events)
break;
5716 if (has_sync_call && sync_reply[job.cls] < K &&
5717 !is_reply_signal[resolve_final_cls(re.node, dcls)] &&
5718 s.role == Role::Queue && !s.ps && !s.pas && ev.slot < s.nservers) {
5719 const std::uint64_t call = ++next_call;
5725 pending_reply[call] = pc;
5726 s.server_held[ev.slot] =
true;
5727 s.held_cls[ev.slot] = job.cls;
5728 acc.update_busy(i, job.cls, now);
5729 acc.busy[i][job.cls] += 1.0;
5732 acc.update_qlen(i, job.cls, now);
5733 acc.held[i][job.cls] += 1.0;
5736 moved.t_sys = job.t_sys;
5737 moved.parent = job.parent;
5739 deliver(re.node, moved, i);
5740 if (total_completions >= max_events)
break;
5749 if (!to_sink && dst < M && s.role == Role::Queue && !s.ps && ev.slot < s.nservers &&
5750 !dest_has_room(dst, dcls)) {
5755 s.server_blocked[ev.slot] =
true;
5756 s.blocked_job[ev.slot] = held;
5757 s.blocked_dest[ev.slot] = dst;
5758 s.blocked_dest_cls[ev.slot] = dcls;
5765 acc.update_qlen(dst, dcls, now);
5766 acc.qlen[dst][dcls] += 1.0;
5767 S[dst].blocked_at[dcls] += 1.0;
5773 acc.update_busy(i, job.cls, now);
5774 acc.busy[i][job.cls] += 1.0;
5775 blocked_count[i][job.cls] += 1.0;
5787 }
else if (s.role == Role::Queue && !s.ps && !s.pas && !s.server_blocked.empty() &&
5788 ev.slot < s.nservers && !s.server_blocked[ev.slot] &&
5789 !s.server_held[ev.slot]) {
5790 serve_next_on_slot(i, ev.slot);
5797 std::uint64_t spawn_parent = 0;
5798 if (has_spawn && spawn_of[job.cls] < K && spawn_joins_at[spawn_of[job.cls]]) {
5799 spawn_parent = job.parent;
5806 const int dep_rg = region_of[i];
5808 regions[
static_cast<std::size_t
>(dep_rg)].update(now);
5809 regions[
static_cast<std::size_t
>(dep_rg)].leave(job.cls);
5815 if (has_spawn && spawn_of[job.cls] < K) {
5816 const std::size_t scls = spawn_of[job.cls];
5819 spawned.t_sys = now;
5824 spawned.parent = spawn_parent;
5825 admit(i, spawned, M);
5830 moved.t_sys = job.t_sys;
5831 moved.parent = job.parent;
5835 moved.call = job.call;
5836 hop_old_cls = job.cls;
5837 hop_internal =
false;
5838 deliver(re.node, moved, i);
5844 const bool stays_inside = hop_internal;
5845 hop_internal =
false;
5846 if (!stays_inside) {
5847 regions[
static_cast<std::size_t
>(dep_rg)].completed[job.cls] += 1.0;
5848 release_region(
static_cast<std::size_t
>(dep_rg));
5853 if (s.has_setup && s.setup_on) {
5855 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[i][r];
5856 if (!(held > 0.0)) {
5858 s.delayoff_time.disabled() ? 0.0 : s.delayoff_time.next(g_aux[i]);
5859 s.delayoff_at = now + idle;
5861 e.t = s.delayoff_at;
5871 if (total_completions >= max_events)
break;
5872 if (cnvg.enabled() && (total_completions - last_cnvg_events) >= cnvg.interval()) {
5873 for (std::size_t a = 0; a < M; ++a) {
5874 if (S[a].ps) ps_advance(a);
5875 for (std::size_t r = 0; r < K; ++r) {
5876 acc.update_qlen(a, r, now);
5877 acc.update_busy(a, r, now);
5880 cnvg.finalize_batch(acc, servers_of, now);
5881 last_cnvg_events = total_completions;
5882 if (cnvg.converged(off_of)) {
5887 if ((total_completions - last_mser_events) >= mser_interval) {
5888 for (std::size_t a = 0; a < M; ++a) {
5889 if (S[a].ps) ps_advance(a);
5890 for (std::size_t r = 0; r < K; ++r) {
5891 acc.update_qlen(a, r, now);
5892 acc.update_busy(a, r, now);
5895 obs.collect(acc, now);
5896 last_mser_events = total_completions;
5901 for (std::size_t i = 0; i < M; ++i) {
5902 if (S[i].ps) ps_advance(i);
5903 for (std::size_t r = 0; r < K; ++r) {
5904 acc.update_qlen(i, r, now);
5905 acc.update_busy(i, r, now);
5910 const Truncation tr = obs.truncate();
5911 const double sim_time = now - tr.warmup_end;
5912 const double elapsed = tr.applied ? (now - obs.time[tr.index]) : sim_time;
5918 res.nchains = sn.nchains;
5919 for (std::size_t i = 0; i < M; ++i) res.station_names.push_back(sn.stations[i].name);
5920 for (std::size_t r = 0; r < K; ++r) res.class_names.push_back(sn.classes[r].name);
5921 res.QN = Matrix<double>(M, K, 0.0);
5922 res.UN = Matrix<double>(M, K, 0.0);
5923 res.RN = Matrix<double>(M, K, 0.0);
5924 res.TN = Matrix<double>(M, K, 0.0);
5925 res.CN = Matrix<double>(1, K, 0.0);
5926 res.XN = Matrix<double>(1, K, 0.0);
5927 res.AN = Matrix<double>(M, K, 0.0);
5928 res.WN = Matrix<double>(M, K, 0.0);
5930 for (std::size_t i = 0; i < M; ++i) {
5931 for (std::size_t r = 0; r < K; ++r) {
5932 if (S[i].role == Role::Source) {
5933 res.TN(i, r) = lambda[r];
5936 if (S[i].off[r])
continue;
5937 if (elapsed > 0.0) {
5938 const double q0 = tr.applied ? obs.qt[i][r][tr.index] : 0.0;
5939 const double b0 = tr.applied ? obs.bt[i][r][tr.index] : 0.0;
5940 const double c0 = tr.applied ? obs.cmp[i][r][tr.index] : 0.0;
5941 res.QN(i, r) = (acc.tot_qlen[i][r] - q0) / elapsed;
5942 res.TN(i, r) = (acc.completed[i][r] - c0) / elapsed;
5943 if (S[i].role == Role::Delay) {
5952 res.UN(i, r) = res.TN(i, r) * S[i].class_mean[r] / S[i].gd_peak[r];
5953 }
else if (S[i].has_cd || has_gd) {
5973 (S[i].has_cd || !S[i].lld.empty()) ? S[i].util_peak : 1.0;
5974 const double peak = base * S[i].gd_peak[r];
5976 (peak > 0.0) ? res.TN(i, r) * S[i].class_mean[r] / peak : 0.0;
5978 res.UN(i, r) = (acc.tot_busy[i][r] - b0) / (elapsed * S[i].util_peak);
5981 if (acc.resp_cnt[i][r] > 0.0) {
5982 res.RN(i, r) = acc.resp_sum[i][r] / acc.resp_cnt[i][r];
5983 }
else if (sn.stations[i].nodetype == NodeType::Place && res.TN(i, r) > 0.0) {
5991 res.RN(i, r) = res.QN(i, r) / res.TN(i, r);
5998 if (elapsed > 0.0) res.AN(i, r) = acc.arrived[i][r] / elapsed;
5999 res.WN(i, r) = res.RN(i, r);
6007 if (!sn.fj.empty()) {
6008 res.DropRateJoin = Matrix<double>(M, K, 0.0);
6009 for (std::size_t i = 0; i < M; ++i)
6010 for (std::size_t r = 0; r < K; ++r) {
6011 const double d0 = tr.applied ? obs.drp[i][r][tr.index] : 0.0;
6013 res.DropRateJoin(i, r) = (acc.join_dropped[i][r] - d0) / elapsed;
6016 for (std::size_t r = 0; r < K; ++r) {
6017 if (sim_time > 0.0) res.XN(0, r) = sys_completed[r] / sim_time;
6018 if (sys_resp_cnt[r] > 0.0) res.CN(0, r) = sys_resp_sum[r] / sys_resp_cnt[r];
6028 const std::size_t rs = sn.classes[r].refstat;
6029 if (res.XN(0, r) == 0.0 && rs != 0 && rs <= M &&
6030 sn.stations[rs - 1].nodetype == NodeType::Place)
6031 res.XN(0, r) = res.TN(rs - 1, r);
6035 for (std::size_t ti = 0; ti < bp.targets().size(); ++ti) {
6037 out.
name = bp.targets()[ti].name;
6038 out.stations = bp.targets()[ti].stations;
6039 out.job_class = bp.targets()[ti].job_class;
6040 for (std::size_t oi = 0; oi < static_cast<std::size_t>(bp.orders()); ++oi) {
6041 out.mean.push_back(bp.mean(ti, oi));
6042 out.count.push_back(bp.count(ti, oi));
6044 res.busy_periods.push_back(out);
6048 if (transient_run) {
6049 now = std::min(now, horizon);
6052 res.QNt.assign(M, std::vector<Matrix<double>>(K));
6053 res.UNt.assign(M, std::vector<Matrix<double>>(K));
6054 res.TNt.assign(M, std::vector<Matrix<double>>(K));
6055 for (std::size_t i = 0; i < M; ++i)
6056 for (std::size_t r = 0; r < K; ++r) {
6057 const std::size_t n = tran_times.size();
6060 Matrix<double> q(n, 2, 0.0), u(n, 2, 0.0), t(n, 2, 0.0);
6061 for (std::size_t k = 0; k < n; ++k) {
6062 q(k, 0) = tran_q[i][r][k];
6063 q(k, 1) = tran_times[k];
6064 u(k, 0) = tran_u[i][r][k];
6065 u(k, 1) = tran_times[k];
6066 t(k, 0) = tran_t[i][r][k];
6067 t(k, 1) = tran_times[k];
6073 res.stopping_reason =
"max_time";
6077 res.respTimeSamples.assign(M, std::vector<std::vector<double>>(K));
6078 for (std::size_t i = 0; i < M; ++i)
6079 for (std::size_t r = 0; r < K; ++r) res.respTimeSamples[i][r] = resp_samples[i][r];
6087 if (want_hist && !transient_run) hist_accumulate();
6088 if (!histogram.empty()) {
6089 res.histogram_space = Matrix<double>(histogram.size(), M * K, 0.0);
6090 res.histogram_time = Matrix<double>(histogram.size(), 1, 0.0);
6091 std::size_t row = 0;
6092 for (
const auto& kv : histogram) {
6093 for (std::size_t c = 0; c < kv.first.size(); ++c)
6094 res.histogram_space(row, c) = kv.first[c];
6095 res.histogram_time(row, 0) = kv.second;
6099 if (!trajectory.empty()) {
6100 res.traj_space = Matrix<double>(trajectory.size(), M * K, 0.0);
6101 res.traj_time = Matrix<double>(trajectory.size(), 1, 0.0);
6102 for (std::size_t k = 0; k < trajectory.size(); ++k) {
6103 for (std::size_t c = 0; c < trajectory[k].second.size(); ++c)
6104 res.traj_space(k, c) = trajectory[k].second[c];
6105 res.traj_time(k, 0) = trajectory[k].first;
6110 for (
const auto& kv : caches) {
6111 const CacheState& cs = kv.second;
6113 cm.
hit = Matrix<double>(1, K, 0.0);
6114 cm.miss = Matrix<double>(1, K, 0.0);
6119 if (cs.has_retrieval) cm.delayed = Matrix<double>(1, K, 0.0);
6120 for (std::size_t r = 0; r < K; ++r) {
6121 const double dl = cs.has_retrieval ? cs.delayed[r] : 0.0;
6122 const double tot = cs.hits[r] + cs.misses[r] + dl;
6124 cm.hit(0, r) = cs.hits[r] / tot;
6125 cm.miss(0, r) = cs.misses[r] / tot;
6126 if (cs.has_retrieval) cm.delayed(0, r) = dl / tot;
6145 const double released = std::accumulate(cs.delayed.begin(), cs.delayed.end(), 0.0);
6146 if (cs.has_retrieval && cs.completed_fetches + released > 0.0) {
6147 cm.latency = Matrix<double>(1, K, 0.0);
6148 const double mean_wait = (cs.total_fetch_time + cs.delayed_wait)
6149 / (cs.completed_fetches + released);
6150 for (std::size_t r = 0; r < K; ++r)
6151 cm.latency(0, r) = (cs.hits[r] + cs.misses[r] + cs.delayed[r] > 0.0)
6153 : std::numeric_limits<double>::quiet_NaN();
6155 res.cache_metrics[sn.nodes[cs.node - 1].name] = cm;
6159 if (!regions.empty()) {
6160 const std::size_t NR = regions.size();
6162 res.QNfcr = Matrix<double>(NR, K, 0.0);
6163 res.TNfcr = Matrix<double>(NR, K, 0.0);
6164 res.WeightNfcr = Matrix<double>(NR, K, 0.0);
6165 res.MemOccNfcr = Matrix<double>(NR, K, 0.0);
6166 res.DropRateNfcr = Matrix<double>(NR, K, 0.0);
6167 for (std::size_t g = 0; g < NR; ++g) {
6168 regions[g].update(now);
6169 for (std::size_t r = 0; r < K; ++r) {
6170 if (sim_time > 0.0) {
6174 res.QNfcr(g, r) = regions[g].tot_jobs[r] / sim_time;
6175 res.WeightNfcr(g, r) = regions[g].tot_weight[r] / sim_time;
6176 res.MemOccNfcr(g, r) = regions[g].tot_mem[r] / sim_time;
6177 res.TNfcr(g, r) = regions[g].completed[r] / sim_time;
6178 res.DropRateNfcr(g, r) = regions[g].dropped[r] / sim_time;
6189 res.total_simulated_events =
static_cast<long long>(total_completions);
6194 double any_balk = 0.0, any_renege = 0.0;
6195 for (std::size_t i = 0; i < M; ++i)
6196 for (std::size_t r = 0; r < K; ++r) {
6197 any_balk += balked[i][r];
6198 any_renege += reneged[i][r];
6200 double any_orbit = 0.0;
6201 for (std::size_t i = 0; i < M; ++i)
6202 for (std::size_t r = 0; r < K; ++r) any_orbit += S[i].retried[r];
6203 if (any_orbit > 0.0) {
6204 res.retriedCustomers = Matrix<double>(M, K, 0.0);
6205 res.retrialDropped = Matrix<double>(M, K, 0.0);
6206 res.avgOrbitSize = Matrix<double>(M, K, 0.0);
6207 for (std::size_t i = 0; i < M; ++i) {
6208 const double dt = now - S[i].orbit_last;
6209 for (std::size_t r = 0; r < K; ++r) {
6210 double tot = S[i].tot_orbit[r];
6211 if (dt > 0.0) tot += S[i].orbit_size[r] * dt;
6212 res.retriedCustomers(i, r) = S[i].retried[r];
6213 res.retrialDropped(i, r) = S[i].retrial_lost[r];
6214 if (sim_time > 0.0) res.avgOrbitSize(i, r) = tot / sim_time;
6219 if (any_balk > 0.0 || any_renege > 0.0) {
6220 res.balkedCustomers = Matrix<double>(M, K, 0.0);
6221 res.renegedCustomers = Matrix<double>(M, K, 0.0);
6222 res.renegingRate = Matrix<double>(M, K, 0.0);
6223 for (std::size_t i = 0; i < M; ++i)
6224 for (std::size_t r = 0; r < K; ++r) {
6225 res.balkedCustomers(i, r) = balked[i][r];
6226 res.renegedCustomers(i, r) = reneged[i][r];
6227 if (sim_time > 0.0) res.renegingRate(i, r) = reneged[i][r] / sim_time;
6231 res.converged = converged;
6232 res.convergence_batches = cnvg.batches();
6233 if (!transient_run) res.stopping_reason = converged ?
"convergence" :
"max_events";
6234 res.engine =
"native";
6235 res.method = o.method;