687 ldes_engine_reject(
sn, o);
689 const std::size_t M =
sn.nstations, K =
sn.nclasses;
690 if (M == 0 || K == 0)
throw InputError(
"SolverLDES (native engine): empty model");
692 const std::uint64_t max_events =
695 throw InputError(
"SolverLDES (native engine): the completion budget is zero");
698 const std::uint64_t base =
699 (o.
seed >= 0) ?
static_cast<std::uint64_t
>(o.
seed) : std::random_device{}();
709 const bool slotted = o.
slotted;
711 auto slot_snap = [&](
double v,
const char* what) ->
double {
712 if (!slotted || v == 0.0)
return v;
713 const double slots = v / slot_len;
714 const double rounded = std::floor(slots + 0.5);
715 if (rounded < 1.0 || std::fabs(slots - rounded) > 1e-9 * std::max(1.0, slots))
716 throw InputError(std::string(
"SolverLDES (native engine): slotted mode sampled a ") +
717 what +
" of " + std::to_string(v) +
718 ", which is not a positive multiple of the slot length " +
719 std::to_string(slot_len) +
720 "; a discrete-time model needs lattice-valued interarrival and "
721 "service times, e.g. Geometric or Det on an integral slot count");
722 return rounded * slot_len;
749 const long long seed_ll =
static_cast<long long>(base);
750 const long long Kll =
static_cast<long long>(K);
751 std::size_t num_sources = 0, num_service_nodes = 0;
752 std::vector<std::size_t> svc_index(M, 0);
753 for (std::size_t i = 0; i < M; ++i) {
754 if (
sn.stations[i].nodetype == NodeType::Source) {
757 svc_index[i] = num_service_nodes++;
760 const long long nsrc =
static_cast<long long>(num_sources);
761 const long long nsvc =
static_cast<long long>(num_service_nodes);
763 std::vector<Rng> g_arr;
765 for (std::size_t k = 0; k < K; ++k) {
766 const long long off = (
static_cast<long long>(k)) * 10;
767 g_arr.push_back(Rng(seed_ll, off, off + 2000));
769 std::vector<std::vector<Rng>> g_svc;
771 for (std::size_t i = 0; i < M; ++i) {
772 std::vector<Rng> row;
774 for (std::size_t k = 0; k < K; ++k) {
775 const long long off =
776 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
static_cast<long long>(k)) *
778 row.push_back(Rng(seed_ll, off));
780 g_svc.push_back(row);
785 std::vector<std::vector<std::vector<Rng>>> g_hsvc(M);
786 for (std::size_t i = 0; i < M; ++i) {
787 const std::size_t nT =
sn.stations[i].server_types.size();
788 g_hsvc[i].resize(nT);
789 for (std::size_t t = 0; t < nT; ++t) {
790 g_hsvc[i][t].reserve(K);
791 for (std::size_t k = 0; k < K; ++k) {
792 const long long off =
793 ((nsrc +
static_cast<long long>(svc_index[i])) * Kll +
794 static_cast<long long>(k)) * 10 +
795 600000 +
static_cast<long long>(t) * 137;
796 g_hsvc[i][t].push_back(Rng(seed_ll, off));
800 std::vector<Rng> g_aux;
802 for (std::size_t i = 0; i < M; ++i) {
803 const long long off = ((nsrc + nsvc +
static_cast<long long>(svc_index[i])) * Kll) * 10;
804 g_aux.push_back(Rng(seed_ll, off));
806 Rng g_routing(seed_ll, 900000);
809 Rng g_spn(seed_ll, 5000);
814 Rng g_fork(seed_ll, 88888);
822 auto fork_is_variable = [&](std::size_t node) ->
bool {
824 if (fp == 0)
return false;
825 const double tpl =
sn.nodes[node - 1].tasks_per_link;
826 for (std::size_t k = 0; k < fp->
fan_out_link.rows(); ++k)
827 for (std::size_t r = 0; r < fp->
fan_out_link.cols(); ++r) {
829 if (p == 0.0)
continue;
830 if (p != 1.0)
return true;
840 for (std::size_t e = 0; e < d.params.size(); ++e) tot += d.params[e];
841 const double u = g_fork.aux.next_double() * tot;
843 for (std::size_t e = 0; e < d.params.size(); ++e) {
846 return static_cast<int>((d.trace.empty() ?
static_cast<double>(e + 1) : d.trace[e]) +
849 return static_cast<int>(
850 (d.trace.empty() ?
static_cast<double>(d.params.size()) : d.trace.back()) + 0.5);
854 std::vector<StationState> S(M);
855 std::size_t source_st = M;
856 std::vector<int> classprio(K, 0);
857 std::vector<double> classdeadline(K, std::numeric_limits<double>::infinity());
858 for (std::size_t r = 0; r < K; ++r) {
859 classprio[r] =
sn.classes[r].prio;
860 classdeadline[r] =
sn.classes[r].deadline;
863 for (std::size_t i = 0; i < M; ++i) {
864 const auto& st =
sn.stations[i];
865 StationState& s = S[i];
867 s.off.assign(K,
true);
868 s.class_mean.assign(K, 0.0);
869 s.weight.assign(K, 1.0);
870 for (std::size_t r = 0; r < K; ++r)
873 if (st.nodetype == NodeType::Source) {
874 s.role = Role::Source;
876 }
else if (st.nodetype == NodeType::Fork || st.nodetype == NodeType::Join ||
877 st.nodetype == NodeType::Place || st.nodetype == NodeType::Transition) {
880 s.role = Role::Synchronization;
886 if (st.nodetype == NodeType::Join) s.off.assign(K,
false);
887 }
else if (st.nodetype == NodeType::Delay || st.sched == SchedStrategy::INF) {
888 s.role = Role::Delay;
890 s.role = Role::Queue;
891 s.ps = is_ps_family(st.sched);
892 s.preemptive = is_preemptive(st.sched);
893 s.resume = is_preemptive_resume(st.sched);
894 const double c = st.nservers;
895 if (!(c >= 1.0) || !std::isfinite(c))
896 throw InputError(
"SolverLDES (native engine): station '" + st.name +
897 "' has a server count that is neither finite nor at least one");
898 s.nservers =
static_cast<std::size_t
>(c + 0.5);
900 s.classcap.assign(K, std::numeric_limits<double>::infinity());
901 s.blocked_at.assign(K, 0.0);
903 for (std::size_t r = 0; r < K; ++r)
904 if (i <
sn.droprule.size() && r <
sn.droprule[i].size())
905 s.droprule[r] =
sn.droprule[i][r];
906 for (std::size_t r = 0; r < K; ++r)
907 if (i <
sn.classcap.size() && r <
sn.classcap[i].size())
908 s.classcap[r] =
sn.classcap[i][r];
909 if (st.sched == SchedStrategy::LPS) s.lps_limit = s.nservers;
914 s.ps_cd.assign(K, 1.0);
920 s.util_peak =
static_cast<double>(s.nservers);
921 for (
double a : s.lld) s.util_peak = std::max(s.util_peak, a);
929 if (st.cdscaling || st.jdscaling) {
939 double dep_peak = 1.0;
940 const std::vector<T>* pks[2] = {&st.cdscalingpeak, &st.jdscalingpeak};
941 const bool on[2] = {
static_cast<bool>(st.cdscaling),
static_cast<bool>(st.jdscaling)};
942 const char* names[2] = {
"setClassDependence",
"setJointDependence"};
943 for (std::size_t h = 0; h < 2; ++h) {
944 if (!on[h])
continue;
947 std::string(
"SolverLDES: station '") + st.name +
948 "' declares a dependent scaling with no declared peak rate. Utilization "
949 "there is T*E[S]/peak, so pass the peak to " + names[h]);
953 throw InputError(std::string(
"SolverLDES: station '") + st.name +
954 "' declares a non-positive peak rate for " + names[h]);
957 s.util_peak = dep_peak;
958 const auto& beta = st.cdscaling;
959 const auto& eta = st.jdscaling;
960 s.cd = [beta, eta](
const std::vector<double>& n) {
961 std::vector<T> nt(n.size());
963 std::vector<double> bd, ed;
965 const std::vector<T> b = beta(nt);
970 const std::vector<T> e = eta(nt);
974 if (bd.empty())
return ed;
975 if (ed.empty())
return bd;
978 const std::size_t n2 = std::max(bd.size(), ed.size());
979 std::vector<double> out(n2, 1.0);
980 for (std::size_t k = 0; k < n2; ++k)
981 out[k] = bd[bd.size() > 1 ? k : 0] * ed[ed.size() > 1 ? k : 0];
985 if (st.sched == SchedStrategy::PAS || st.sched == SchedStrategy::OI) {
987 auto pp =
sn.pasparam.find(i + 1);
988 if (pp !=
sn.pasparam.end()) {
989 if (pp->second.svc_rate_fun) {
990 const auto f = pp->second.svc_rate_fun;
999 s.pas_rate = [f](
const std::vector<std::size_t>& seq) {
1000 std::vector<std::size_t> tags(seq.size());
1001 for (std::size_t k = 0; k < seq.size(); ++k) tags[k] = seq[k] + 1;
1005 s.pas_swap = pp->second.swap_graph;
1008 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1009 "' is a pass-and-swap station with no service rate function");
1011 if (st.sched == SchedStrategy::POLLING) {
1013 const auto pp =
sn.effective_polling(i + 1);
1014 s.poll_type = pp.ptype;
1015 s.poll_k = (pp.pk >= 1) ? pp.pk : 1;
1017 ? std::numeric_limits<std::size_t>::max()
1019 s.poll_parked =
true;
1020 s.switchover.resize(K);
1021 s.has_switchover.assign(K,
false);
1022 for (std::size_t r = 0; r < K && r < pp.switchover.size(); ++r)
1023 if (!pp.switchover[r].disabled &&
1025 s.switchover[r] = Sampler(pp.switchover[r],
1026 "the switchover of station '" + st.name +
1027 "', class '" +
sn.classes[r].name +
"'");
1028 s.has_switchover[r] =
true;
1031 s.retrial.resize(K);
1032 s.has_retrial.assign(K,
false);
1033 s.max_attempts.assign(K, 0);
1034 s.orbit_size.assign(K, 0.0);
1035 s.tot_orbit.assign(K, 0.0);
1036 s.retried.assign(K, 0.0);
1037 s.retrial_lost.assign(K, 0.0);
1039 auto rp =
sn.retrialparam.find(i + 1);
1040 if (rp !=
sn.retrialparam.end())
1041 for (std::size_t r = 0; r < K && r < rp->second.retrial_proc.size(); ++r)
1042 if (!rp->second.retrial_proc[r].disabled) {
1043 s.retrial[r] = Sampler(rp->second.retrial_proc[r],
1044 "the retrial process of station '" + st.name +
1045 "', class '" +
sn.classes[r].name +
"'");
1046 s.has_retrial[r] =
true;
1047 if (r < rp->second.max_attempts.size())
1048 s.max_attempts[r] = rp->second.max_attempts[r];
1051 s.patience.resize(K);
1052 s.has_patience.assign(K,
false);
1053 s.balk.assign(K, std::vector<StationState::BalkRule>());
1054 for (std::size_t r = 0; r < K; ++r) {
1056 r < st.patience.size() && !st.patience[r].disabled) {
1057 s.patience[r] = Sampler(st.patience[r],
"the patience of station '" + st.name +
1058 "', class '" +
sn.classes[r].name +
"'");
1059 s.has_patience[r] =
true;
1061 if (r < st.balking.size() &&
1065 "SolverLDES (native engine): station '" + st.name +
1066 "' declares a balking rule that is not QUEUE_LENGTH; the "
1067 "expected-wait rules are not ported yet");
1068 for (
const auto& th : st.balking[r].thresholds) {
1069 StationState::BalkRule br;
1070 br.min_jobs = th.min_jobs;
1071 br.max_jobs = th.max_jobs;
1073 s.balk[r].push_back(br);
1079 auto sp =
sn.setupparam.find(i + 1);
1080 if (sp !=
sn.setupparam.end()) {
1082 if (sp->second.last(su, doff) && !su.
disabled) {
1084 s.setup_time = Sampler(su,
"the setup time of station '" + st.name +
"'");
1091 Sampler(doff,
"the delay-off time of station '" + st.name +
"'");
1093 s.delayoff_time = Sampler();
1098 typename std::map<std::size_t, qn::BreakdownParam<T> >::const_iterator bp =
1099 sn.breakdownparam.find(i + 1);
1100 if (bp !=
sn.breakdownparam.end()) {
1101 s.has_breakdown =
true;
1104 Sampler(bp->second.failure,
"the failure time of station '" + st.name +
"'");
1106 Sampler(bp->second.repair,
"the repair time of station '" + st.name +
"'");
1107 s.down_scale.assign(K, 0.0);
1108 s.down_rate_raw.assign(K, 0.0);
1109 for (std::size_t r = 0; r < K && r < bp->second.down_service_rates.size(); ++r)
1115 s.state_dependent = (!s.lld.empty() || s.has_cd || s.has_breakdown);
1118 if (s.role == Role::Synchronization)
continue;
1119 for (std::size_t r = 0; r < K; ++r) {
1120 s.off[r] =
sn.disabled[i][r];
1121 if (s.off[r])
continue;
1122 const std::string where =
"station '" + st.name +
"', class '" +
sn.classes[r].name +
"'";
1123 s.svc[r] = Sampler(
sn.service[i][r], where);
1124 s.class_mean[r] = s.svc[r].mean();
1125 if (!(s.class_mean[r] > 0.0))
1126 throw InputError(
"SolverLDES (native engine): " + where +
1127 " has a non-positive mean service time");
1138 if (!st.server_types.empty()) {
1139 const std::size_t nT = st.server_types.size();
1141 s.hetero_policy = st.hetero_policy;
1142 s.type_count.assign(nT, 0);
1143 s.type_first.assign(nT, 0);
1144 s.type_compat.assign(nT, std::vector<bool>(K,
true));
1145 s.type_svc.assign(nT, std::vector<Sampler>(K));
1146 s.type_has_svc.assign(nT, std::vector<bool>(K,
false));
1147 s.type_rate.assign(nT, std::vector<double>(K, 0.0));
1148 std::size_t total = 0;
1149 for (std::size_t t = 0; t < nT; ++t) {
1151 const double c = pt.
count;
1153 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1154 "' declares server pool '" + pt.
name +
1155 "' with fewer than one server");
1156 s.type_first[t] = total;
1157 s.type_count[t] =
static_cast<std::size_t
>(c + 0.5);
1158 total += s.type_count[t];
1159 for (std::size_t r = 0; r < K; ++r) {
1165 if (s.off[r]) s.type_compat[t][r] =
false;
1167 const std::string where =
"station '" + st.name +
"', pool '" + pt.
name +
1168 "', class '" +
sn.classes[r].name +
"'";
1169 s.type_svc[t][r] = Sampler(pt.
service[r], where);
1170 s.type_has_svc[t][r] =
true;
1171 const double mu = s.type_svc[t][r].mean();
1173 throw InputError(
"SolverLDES (native engine): " + where +
1174 " has a non-positive mean service time");
1175 s.type_rate[t][r] = 1.0 / mu;
1176 }
else if (!s.off[r]) {
1178 (s.class_mean[r] > 0.0) ? 1.0 / s.class_mean[r] : 0.0;
1182 for (std::size_t r = 0; r < K; ++r) {
1183 if (s.off[r])
continue;
1184 bool served =
false;
1185 for (std::size_t t = 0; t < nT && !served; ++t) served = s.type_compat[t][r];
1187 throw InputError(
"SolverLDES (native engine): station '" + st.name +
1188 "' declares no server pool compatible with class '" +
1189 sn.classes[r].name +
1190 "', so a job of that class would wait forever");
1196 s.util_peak =
static_cast<double>(total);
1198 s.server_type.assign(total, 0);
1199 for (std::size_t t = 0; t < nT; ++t)
1200 for (std::size_t j = 0; j < s.type_count[t]; ++j)
1201 s.server_type[s.type_first[t] + j] = t;
1202 s.type_order.resize(nT);
1203 for (std::size_t t = 0; t < nT; ++t) s.type_order[t] = t;
1206 s.alfs_order = s.type_order;
1207 std::stable_sort(s.alfs_order.begin(), s.alfs_order.end(),
1208 [&](std::size_t a, std::size_t b) {
1209 std::size_t ca = 0, cb = 0;
1210 for (std::size_t r = 0; r < K; ++r) {
1211 if (s.type_compat[a][r]) ++ca;
1212 if (s.type_compat[b][r]) ++cb;
1221 if (s.has_breakdown) {
1222 s.down_scale.assign(K, 0.0);
1223 for (std::size_t r = 0; r < K; ++r) {
1224 if (s.off[r] || !(s.class_mean[r] > 0.0))
continue;
1225 if (r < s.down_rate_raw.size() && s.down_rate_raw[r] > 0.0)
1226 s.down_scale[r] = s.down_rate_raw[r] * s.class_mean[r];
1230 s.cmp.sched = s.sched;
1231 s.cmp.class_mean = &s.class_mean;
1232 if (s.role == Role::Queue) {
1233 s.server.assign(s.nservers, Job());
1234 s.server_busy.assign(s.nservers,
false);
1235 s.server_start.assign(s.nservers, 0.0);
1236 s.server_tag.assign(s.nservers, 0);
1237 s.server_blocked.assign(s.nservers,
false);
1238 s.server_held.assign(s.nservers,
false);
1239 s.held_cls.assign(s.nservers, 0);
1240 s.blocked_job.assign(s.nservers, Job());
1241 s.blocked_dest.assign(s.nservers, 0);
1242 s.blocked_dest_cls.assign(s.nservers, 0);
1253 const std::size_t nnodes =
sn.nof_nodes();
1254 std::vector<std::size_t> node_to_station(nnodes + 1, M);
1255 for (std::size_t i = 0; i < M; ++i) node_to_station[
sn.station_to_node[i]] = i;
1268 std::vector<std::vector<std::vector<RouteDest>>> nroute(
1269 nnodes + 1, std::vector<std::vector<RouteDest>>(K));
1278 std::vector<std::vector<lang::RoutingStrategy>> node_routing(
1280 std::vector<std::vector<std::size_t>> rr_counter(nnodes + 1, std::vector<std::size_t>(K, 0));
1281 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1282 for (std::size_t r = 0; r < K; ++r)
1283 if (
sn.nodes[inode - 1].routing.size() > r)
1284 node_routing[inode][r] =
sn.nodes[inode - 1].routing[r];
1285 for (std::size_t inode = 1; inode <= nnodes; ++inode) {
1292 if (
sn.nodes[inode - 1].nodetype == NodeType::Sink)
continue;
1296 if (
sn.nodes[inode - 1].nodetype == NodeType::Place ||
1297 sn.nodes[inode - 1].nodetype == NodeType::Transition)
1299 for (std::size_t r = 0; r < K; ++r) {
1302 for (std::size_t j = 1; j <= nnodes; ++j) {
1305 d.sink = (
sn.nodes[j - 1].nodetype == NodeType::Sink);
1306 d.station = node_to_station[j];
1307 for (std::size_t s2 = 0; s2 < K; ++s2) {
1309 if (!(p > 0.0))
continue;
1311 d.cls.push_back(std::make_pair(s2, p));
1313 if (d.cls.empty())
continue;
1320 if (!d.sink && d.station >= M && sn.nodes[j - 1].nodetype != NodeType::Fork &&
1321 sn.nodes[j - 1].nodetype != NodeType::Join &&
1322 sn.nodes[j - 1].nodetype != NodeType::Cache &&
1323 sn.nodes[j - 1].nodetype != NodeType::ClassSwitch &&
1324 sn.nodes[j - 1].nodetype != NodeType::Router &&
1325 sn.nodes[j - 1].nodetype != NodeType::Logger &&
1326 sn.nodes[j - 1].nodetype != NodeType::Place &&
1327 sn.nodes[j - 1].nodetype != NodeType::Transition)
1329 "SolverLDES (native engine): the routing crosses node '" +
1330 sn.nodes[j - 1].name +
1331 "', which the refresh did not fold into the station-to-station "
1333 if (d.station < M && S[d.station].role == Role::Source)
1334 throw InputError(
"SolverLDES (native engine): the routing sends a "
1335 "job back into the Source");
1336 nroute[inode][r].push_back(d);
1346 std::vector<int> join_of_fork(nnodes + 1, -1);
1347 std::vector<bool> is_fork(nnodes + 1,
false), is_join(nnodes + 1,
false);
1348 for (
const auto& fjp : sn.fj) {
1349 if (fjp.first > nnodes || fjp.second > nnodes)
continue;
1350 is_fork[fjp.first] =
true;
1351 is_join[fjp.second] =
true;
1352 join_of_fork[fjp.first] =
static_cast<int>(fjp.second);
1356 std::vector<bool> spawn_joins_at(K,
false);
1357 for (std::size_t inode = 1; inode <= nnodes; ++inode)
1358 for (std::size_t r = 0; r < K; ++r)
1359 for (
const RouteDest& d : nroute[inode][r])
1360 if (d.node >= 1 && d.node <= nnodes &&
1361 sn.nodes[d.node - 1].nodetype == NodeType::Join)
1362 for (std::size_t c = 0; c < d.cls.size(); ++c)
1363 spawn_joins_at[d.cls[c].first] =
true;
1364 for (std::size_t nd = 1; nd <= nnodes; ++nd) {
1365 if (sn.nodes[nd - 1].nodetype == NodeType::Fork && !is_fork[nd])
1366 throw InputError(
"SolverLDES (native engine): Fork node '" + sn.nodes[nd - 1].name +
1367 "' has no matching Join");
1368 if (sn.nodes[nd - 1].nodetype == NodeType::Join && !is_join[nd])
1369 throw InputError(
"SolverLDES (native engine): Join node '" + sn.nodes[nd - 1].name +
1370 "' closes no Fork");
1382 std::size_t cls = 0;
1394 std::vector<std::pair<std::size_t, double>> siblings;
1396 std::map<std::uint64_t, ForkSync> fork_sync;
1397 std::uint64_t next_parent = 0;
1405 std::map<std::size_t, CacheState> caches;
1406 for (
const auto& np : sn.nodeparam) {
1407 const std::size_t nd = np.first;
1408 if (nd == 0 || nd > nnodes)
continue;
1409 if (sn.nodes[nd - 1].nodetype != NodeType::Cache)
continue;
1410 const auto& cp = np.second;
1413 cs.nitems = cp.nitems;
1414 cs.policy = cp.replacestrat;
1415 for (
int c : cp.itemcap)
1416 if (c > 0) cs.capacity.push_back(
static_cast<std::size_t
>(c));
1417 if (cs.capacity.empty()) cs.capacity.push_back(1);
1418 cs.lists.assign(cs.capacity.size(), std::list<std::size_t>());
1419 for (
int v : cp.itemsize) cs.item_size.push_back(v);
1420 for (
int v : cp.costcap) cs.cost_cap.push_back(v);
1421 cs.hits.assign(K, 0.0);
1422 cs.misses.assign(K, 0.0);
1423 cs.hit_class.assign(K, -1);
1424 cs.miss_class.assign(K, -1);
1425 for (std::size_t r = 0; r < K && r < cp.hitclass.size(); ++r)
1426 if (cp.hitclass[r] > 0) cs.hit_class[r] =
static_cast<int>(cp.hitclass[r] - 1);
1427 for (std::size_t r = 0; r < K && r < cp.missclass.size(); ++r)
1428 if (cp.missclass[r] > 0) cs.miss_class[r] =
static_cast<int>(cp.missclass[r] - 1);
1431 cs.popularity.assign(K, std::vector<double>());
1432 for (std::size_t r = 0; r < K && r < cp.pread.size(); ++r) {
1434 for (
const T& v : cp.pread[r]) {
1435 acc += num_traits<T>::to_double(v);
1436 cs.popularity[r].push_back(acc);
1438 if (!cs.popularity[r].empty()) cs.popularity[r].back() = 1.0;
1442 if (cp.retrieval_capacity > 0 && !cp.retrieval_classes.empty()) {
1443 cs.has_retrieval =
true;
1444 cs.retrieval_class = cp.retrieval_classes;
1445 cs.retrieval_class_to_item.assign(K, -1);
1446 for (std::size_t it = 0; it < cs.retrieval_class.size(); ++it)
1447 for (std::size_t r = 0; r < cs.retrieval_class[it].size(); ++r) {
1448 const std::size_t rc = cs.retrieval_class[it][r];
1449 if (rc > 0 && rc <= K) cs.retrieval_class_to_item[rc - 1] =
static_cast<int>(it);
1451 cs.in_flight.assign(cs.nitems, 0);
1452 cs.fetch_start.assign(cs.nitems, 0.0);
1453 cs.held.assign(cs.nitems, std::vector<CacheState::HeldRequest>());
1454 cs.delayed.assign(K, 0.0);
1464 std::vector<std::size_t> place_of(nnodes + 1, M);
1465 std::vector<std::size_t> place_nodes;
1466 for (std::size_t nd = 1; nd <= nnodes; ++nd)
1467 if (sn.nodes[nd - 1].nodetype == NodeType::Place) {
1468 place_of[nd] = place_nodes.size();
1469 place_nodes.push_back(nd);
1471 std::vector<std::vector<double>> marking(place_nodes.size(), std::vector<double>(K, 0.0));
1472 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1473 auto im = sn.initmarking.find(place_nodes[p]);
1474 if (im != sn.initmarking.end())
1475 for (std::size_t r = 0; r < K && r < im->second.size(); ++r)
1476 marking[p][r] = num_traits<T>::to_double(im->second[r]);
1479 std::vector<SpnTransition> transitions;
1480 for (
const auto& tp : sn.transparam) {
1481 const std::size_t nd = tp.first;
1482 if (nd == 0 || nd > nnodes)
continue;
1485 tr.places = place_nodes;
1486 for (std::size_t m = 0; m < tp.second.nmodes; ++m) {
1490 for (std::size_t p = 0; p < place_nodes.size(); ++p) {
1491 const std::size_t pn = place_nodes[p];
1492 for (std::size_t r = 0; r < K; ++r) {
1493 mode.enabling.push_back(
1494 (m < tp.second.enabling.size() && pn - 1 < tp.second.enabling[m].rows() &&
1495 r < tp.second.enabling[m].cols())
1496 ? num_traits<T>::to_double(tp.second.enabling[m](pn - 1, r))
1498 mode.inhibiting.push_back(
1499 (m < tp.second.inhibiting.size() &&
1500 pn - 1 < tp.second.inhibiting[m].rows() &&
1501 r < tp.second.inhibiting[m].cols())
1502 ? num_traits<T>::to_double(tp.second.inhibiting[m](pn - 1, r))
1503 : std::numeric_limits<double>::infinity());
1504 mode.firing.push_back(
1505 (m < tp.second.firing.size() && pn - 1 < tp.second.firing[m].rows() &&
1506 r < tp.second.firing[m].cols())
1507 ? num_traits<T>::to_double(tp.second.firing[m](pn - 1, r))
1511 if (m < tp.second.nmodeservers.size()) mode.servers = tp.second.nmodeservers[m];
1512 if (m < tp.second.firingprio.size()) mode.priority = tp.second.firingprio[m];
1513 if (m < tp.second.fireweight.size())
1514 mode.weight = num_traits<T>::to_double(tp.second.fireweight[m]);
1515 mode.immediate = (m < tp.second.timing.size() &&
1517 if (m < tp.second.firingproc.size() && !tp.second.firingproc[m].disabled) {
1518 const double mean = num_traits<T>::to_double(tp.second.firingproc[m].mean);
1519 if (mean > 0.0) mode.rate = 1.0 / mean;
1521 if (m < tp.second.firingdep.size() && tp.second.firingdep[m]) {
1522 const auto f = tp.second.firingdep[m];
1523 const std::vector<std::size_t> pl = place_nodes;
1524 const std::size_t nn = nnodes, KK = K;
1530 mode.dep = [f, pl, nn, KK](
const std::vector<double>& tok) {
1531 std::vector<T> mk(nn, num_traits<T>::from_int(0));
1532 for (std::size_t p = 0; p < pl.size(); ++p) {
1534 for (std::size_t r = 0; r < KK; ++r)
1535 if (p * KK + r < tok.size()) s += tok[p * KK + r];
1536 if (pl[p] >= 1 && pl[p] - 1 < nn)
1537 mk[pl[p] - 1] = num_traits<T>::from_double(s);
1539 return num_traits<T>::to_double(f(mk));
1542 tr.modes.push_back(mode);
1544 tr.fired.assign(tr.modes.size(), 0.0);
1545 transitions.push_back(tr);
1550 for (std::size_t i = 0; i < M; ++i) acc.util_peak[i] = S[i].util_peak;
1554 std::vector<double> sys_resp_sum(K, 0.0), sys_resp_cnt(K, 0.0), sys_completed(K, 0.0);
1557 std::vector<std::vector<double>> dropped(M, std::vector<double>(K, 0.0));
1558 std::vector<std::vector<double>> balked(M, std::vector<double>(K, 0.0));
1559 std::vector<std::vector<double>> reneged(M, std::vector<double>(K, 0.0));
1560 std::vector<std::vector<double>> blocked_count(M, std::vector<double>(K, 0.0));
1569 const bool want_respt = o.export_respt || o.export_trajectory;
1570 std::vector<std::vector<std::vector<double>>> resp_samples(
1571 want_respt ? M : 0, std::vector<std::vector<double>>(K));
1572 std::uint64_t job_id = 0;
1585 std::vector<bool> is_removal_signal(K,
false);
1586 bool has_removal_signal =
false;
1587 for (std::size_t r = 0; r < K; ++r) {
1588 if (r >= sn.issignal.size() || !sn.issignal[r])
continue;
1592 is_removal_signal[r] =
true;
1593 has_removal_signal =
true;
1606 std::vector<std::vector<Job>> delay_live(has_removal_signal ? M : 0);
1617 std::vector<std::size_t> sync_reply(K, K);
1618 std::vector<bool> is_reply_signal(K,
false);
1619 bool has_sync_call =
false;
1620 for (std::size_t r = 0; r < K; ++r) {
1621 if (r < sn.syncreply.size() && sn.syncreply[r] >= 1 && sn.syncreply[r] <= K) {
1622 sync_reply[r] = sn.syncreply[r] - 1;
1623 has_sync_call =
true;
1625 if (r < sn.issignal.size() && sn.issignal[r] && r < sn.signaltype.size() &&
1627 is_reply_signal[r] =
true;
1630 struct PendingCall {
1631 std::size_t station = 0;
1632 std::size_t slot = 0;
1633 std::size_t cls = 0;
1636 std::map<std::uint64_t, PendingCall> pending_reply;
1637 std::uint64_t next_call = 0;
1640 bool has_immfeed =
false;
1641 for (std::size_t i = 0; i < M && i < sn.immfeed.size(); ++i)
1642 for (std::size_t r = 0; r < sn.immfeed[i].size(); ++r)
1643 if (sn.immfeed[i][r]) has_immfeed =
true;
1653 std::vector<std::size_t> spawn_of(K, K);
1654 bool has_spawn =
false;
1655 for (std::size_t r = 0; r < K; ++r)
1656 if (sn.classes[r].spawn >= 1 && sn.classes[r].spawn <= K) {
1657 spawn_of[r] = sn.classes[r].spawn - 1;
1662 std::priority_queue<Event, std::vector<Event>, EventLater> evq;
1663 std::uint64_t seq = 0, ps_tag = 0;
1665 auto push = [&](Event e) {
1679 auto buffer_push = [&](std::size_t i,
const Job& job) {
1680 StationState& s = S[i];
1681 s.buffer.push_back(job);
1682 if (s.sched != SchedStrategy::FSP)
1683 std::push_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
1685 auto buffer_pop = [&](std::size_t i) -> Job {
1686 StationState& s = S[i];
1687 if (s.sched != SchedStrategy::FSP) {
1688 std::pop_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
1689 Job job = s.buffer.back();
1690 s.buffer.pop_back();
1693 std::vector<double> works;
1694 for (
const Job& j : s.buffer) works.push_back(j.remaining);
1695 for (std::size_t sl = 0; sl < s.nservers; ++sl)
1696 if (s.server_busy[sl])
1697 works.push_back(std::max(0.0, s.server[sl].remaining -
1698 (now - s.server_start[sl])));
1699 std::size_t best = 0;
1700 double best_vft = std::numeric_limits<double>::infinity();
1701 for (std::size_t j = 0; j < s.buffer.size(); ++j) {
1703 static_cast<double>(s.nservers), now);
1709 Job job = s.buffer[best];
1710 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(best));
1725 auto slot_ok_for = [&](
const StationState& s, std::size_t sl, std::size_t cls) ->
bool {
1726 if (s.server_busy[sl] || s.server_blocked[sl] || s.server_held[sl])
return false;
1727 if (!s.has_pools)
return true;
1728 return s.type_compat[s.server_type[sl]][cls];
1739 auto free_slot_for = [&](std::size_t i, std::size_t cls) -> std::size_t {
1740 StationState& s = S[i];
1742 for (std::size_t sl = 0; sl < s.nservers; ++sl)
1743 if (slot_ok_for(s, sl, cls))
return sl;
1747 std::vector<std::size_t> cand;
1748 for (std::size_t t = 0; t < s.type_count.size(); ++t) {
1749 if (!s.type_compat[t][cls])
continue;
1750 for (std::size_t j = 0; j < s.type_count[t]; ++j)
1751 if (slot_ok_for(s, s.type_first[t] + j, cls)) {
1756 if (cand.empty())
return s.nservers;
1757 std::size_t chosen = cand[0];
1758 if (cand.size() > 1) {
1759 switch (s.hetero_policy) {
1765 for (std::size_t k = 0; k < s.type_order.size(); ++k) {
1766 const std::size_t t = s.type_order[k];
1767 if (std::find(cand.begin(), cand.end(), t) == cand.end())
continue;
1769 s.type_order.erase(s.type_order.begin() +
1770 static_cast<std::ptrdiff_t
>(k));
1771 s.type_order.push_back(t);
1779 for (std::size_t k = 0; k < s.alfs_order.size(); ++k)
1780 if (std::find(cand.begin(), cand.end(), s.alfs_order[k]) != cand.end()) {
1781 chosen = s.alfs_order[k];
1789 for (std::size_t k = 0; k < cand.size(); ++k) {
1790 const double rt = s.type_rate[cand[k]][cls];
1801 std::size_t at =
static_cast<std::size_t
>(
1802 uniform01(g_routing) *
static_cast<double>(cand.size()));
1803 if (at >= cand.size()) at = cand.size() - 1;
1815 for (std::size_t j = 0; j < s.type_count[chosen]; ++j) {
1816 const std::size_t sl = s.type_first[chosen] + j;
1817 if (slot_ok_for(s, sl, cls))
return sl;
1823 auto buffer_has_for_slot = [&](std::size_t i, std::size_t sl) ->
bool {
1824 StationState& s = S[i];
1825 if (s.buffer.empty())
return false;
1826 if (!s.has_pools)
return true;
1827 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
1828 for (std::size_t j = 0; j < s.buffer.size(); ++j)
1829 if (ok[s.buffer[j].cls])
return true;
1844 auto buffer_pop_for_slot = [&](std::size_t i, std::size_t sl) -> Job {
1845 StationState& s = S[i];
1846 if (!s.has_pools)
return buffer_pop(i);
1847 const std::vector<bool>& ok = s.type_compat[s.server_type[sl]];
1848 std::vector<Job> skipped;
1851 while (!s.buffer.empty()) {
1852 Job cand = buffer_pop(i);
1858 skipped.push_back(cand);
1860 for (std::size_t j = 0; j < skipped.size(); ++j) buffer_push(i, skipped[j]);
1862 throw InputError(
"SolverLDES (native engine): buffer_pop_for_slot was asked for a "
1863 "job no pool of this slot can serve; guard with buffer_has_for_slot");
1886 auto lld_factor = [&](std::size_t i) ->
double {
1887 StationState& s = S[i];
1888 if (s.lld.empty() || s.ps)
return 1.0;
1890 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
1891 if (!(total > 0.0))
return 1.0;
1892 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
1893 return s.lld[idx] > 0.0 ? s.lld[idx] : 1.0;
1903 auto cd_factor = [&](std::size_t i, std::size_t r) ->
double {
1904 StationState& s = S[i];
1905 if (!s.has_cd)
return 1.0;
1906 std::vector<double> nvec(K, 0.0);
1907 for (std::size_t k = 0; k < K; ++k) nvec[k] = acc.qlen[i][k];
1908 const std::vector<double> beta = s.cd(nvec);
1909 const double b = (beta.size() > 1) ? beta[r] : (beta.empty() ? 1.0 : beta[0]);
1910 return (b > 1e-10) ? b : 1.0;
1912 auto rate_scaling = [&](std::size_t i, std::size_t r) ->
double {
1913 StationState& s = S[i];
1919 if (s.has_breakdown && !s.up)
1920 factor *= (r < s.down_scale.size() ? s.down_scale[r] : 0.0);
1930 auto ps_servers = [&](std::size_t i) ->
double {
1931 StationState& s = S[i];
1932 if (s.lld.empty())
return static_cast<double>(s.nservers);
1934 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[i][k];
1935 if (!(total > 0.0))
return 1.0;
1936 const std::size_t idx = std::min(
static_cast<std::size_t
>(total) - 1, s.lld.size() - 1);
1946 auto ps_advance = [&](std::size_t i) {
1947 StationState& s = S[i];
1948 const double dt = now - s.ps_last_update;
1949 s.ps_last_update = now;
1957 for (std::size_t r = 0; r < K; ++r) acc.last_busy[i][r] = now;
1958 if (!(dt > 0.0) || s.ps_jobs.empty())
return;
1959 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
1961 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
1962 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
1963 if (s.has_breakdown && !s.up)
1964 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
1965 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
1967 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
1968 if (rates[j] > 0.0) {
1969 acc.tot_busy[i][s.ps_jobs[j].cls] += rates[j] * dt;
1970 s.ps_jobs[j].remaining = std::max(0.0, s.ps_jobs[j].remaining - rates[j] * dt);
1973 auto ps_reschedule = [&](std::size_t i) {
1974 StationState& s = S[i];
1978 for (std::size_t r = 0; r < K; ++r) s.ps_cd[r] =
cd_factor(i, r);
1979 if (s.ps_jobs.empty())
return;
1980 std::vector<double> rates =
ps_shares(s.sched, s.ps_jobs, ps_servers(i), s.weight, K);
1982 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
1983 rates[j] *= s.ps_cd[s.ps_jobs[j].cls];
1984 if (s.has_breakdown && !s.up)
1985 for (std::size_t j = 0; j < rates.size() && j < s.ps_jobs.size(); ++j)
1986 rates[j] *= (s.ps_jobs[j].cls < s.down_scale.size() ? s.down_scale[s.ps_jobs[j].cls]
1988 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j) {
1989 PsJob& pj = s.ps_jobs[j];
1992 const double rate = rates[j];
1993 if (!(rate > 0.0))
continue;
1995 e.t = now + ((pj.remaining <= 1e-12) ? 1e-12 : pj.remaining / rate);
2014 auto sd_advance = [&](std::size_t i) {
2015 StationState& s = S[i];
2016 if (!s.state_dependent || s.ps)
return;
2017 const double dt = now - s.sd_last_update;
2018 s.sd_last_update = now;
2019 if (!(dt > 0.0))
return;
2020 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2021 if (s.server_busy[sl]) {
2022 const double scale = rate_scaling(i, s.server[sl].cls);
2023 s.server[sl].remaining = std::max(0.0, s.server[sl].remaining - dt * scale);
2024 s.server[sl].elapsed += dt * scale;
2025 s.server_start[sl] = now;
2028 auto sd_reschedule = [&](std::size_t i) {
2029 StationState& s = S[i];
2030 if (!s.state_dependent || s.ps)
return;
2036 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2037 if (s.server_busy[sl]) {
2038 const double scale = rate_scaling(i, s.server[sl].cls);
2048 s.server_tag[sl] = ++ps_tag;
2049 if (!(scale > 0.0))
continue;
2051 e.t = now + s.server[sl].remaining / scale;
2054 e.cls = s.server[sl].cls;
2056 e.tag = s.server_tag[sl];
2075 auto dest_has_room = [&](std::size_t j, std::size_t r) ->
bool {
2076 StationState& d = S[j];
2077 if (d.role != Role::Queue)
return true;
2079 for (std::size_t k = 0; k < K; ++k) total += acc.qlen[j][k] - d.blocked_at[k];
2080 return (total + 1.0 <= d.cap) &&
2081 (acc.qlen[j][r] - d.blocked_at[r] + 1.0 <= d.classcap[r]);
2086 return (S[j].role == Role::Queue && r < S[j].droprule.size()) ? S[j].droprule[r]
2094 std::vector<Region> regions;
2095 std::vector<int> region_of(M, -1);
2096 for (
const auto& rg : sn.regions) {
2099 R.members.assign(M,
false);
2100 R.class_cap.assign(K, -1.0);
2101 R.class_size.assign(K, 1.0);
2102 R.class_weight.assign(K, 1.0);
2104 R.jobs.assign(K, 0.0);
2105 R.blocked.assign(K, 0.0);
2106 R.dropped.assign(K, 0.0);
2107 R.tot_jobs.assign(K, 0.0);
2108 R.tot_weight.assign(K, 0.0);
2109 R.tot_mem.assign(K, 0.0);
2110 R.completed.assign(K, 0.0);
2111 R.resp_sum.assign(K, 0.0);
2112 R.resp_cnt.assign(K, 0.0);
2113 for (std::size_t i = 0; i < M && i < rg.members.size(); ++i)
2114 if (rg.members[i]) {
2115 R.members[i] =
true;
2116 if (region_of[i] >= 0)
2118 "SolverLDES (native engine): station '" + sn.stations[i].name +
2119 "' belongs to more than one finite capacity region");
2120 region_of[i] =
static_cast<int>(regions.size());
2125 for (std::size_t i = 0; i < rg.cap.size(); ++i) {
2126 if (i >= M || !R.members[i])
continue;
2127 for (std::size_t r = 0; r < K && r < rg.cap[i].size(); ++r)
2128 if (rg.cap[i][r] >= 0.0)
2129 R.class_cap[r] = (R.class_cap[r] < 0.0) ? rg.cap[i][r]
2130 : std::min(R.class_cap[r], rg.cap[i][r]);
2131 if (rg.cap[i].size() > K && rg.cap[i][K] >= 0.0)
2132 R.global_cap = (R.global_cap < 0.0) ? rg.cap[i][K]
2133 : std::min(R.global_cap, rg.cap[i][K]);
2135 for (
double mm : rg.maxmem)
2136 if (mm >= 0.0) R.max_mem = (R.max_mem < 0.0) ? mm : std::min(R.max_mem, mm);
2137 for (std::size_t r = 0; r < K && r < rg.rule.size(); ++r) R.rule[r] = rg.rule[r];
2138 for (std::size_t r = 0; r < K && r < rg.size.size(); ++r)
2139 R.class_size[r] = num_traits<T>::to_double(rg.size[r]);
2140 for (std::size_t r = 0; r < K && r < rg.weight.size(); ++r)
2141 R.class_weight[r] = num_traits<T>::to_double(rg.weight[r]);
2142 for (std::size_t c = 0; c < rg.lincon_A.rows(); ++c) {
2143 std::vector<double> row;
2144 for (std::size_t r = 0; r < rg.lincon_A.cols(); ++r)
2145 row.push_back(num_traits<T>::to_double(rg.lincon_A(c, r)));
2146 R.lincon_A.push_back(row);
2147 R.lincon_b.push_back(c < rg.lincon_b.size() ? num_traits<T>::to_double(rg.lincon_b[c])
2150 regions.push_back(R);
2158 struct RegionWaiter {
2159 std::size_t station = 0;
2162 std::vector<std::vector<RegionWaiter>> region_wait(regions.size());
2164 auto start_service = [&](std::size_t i, std::size_t slot,
const Job& job_in) {
2165 StationState& s = S[i];
2169 if (s.preemptive && !s.resume && job.elapsed > 0.0) {
2170 job.service = slot_snap(s.svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2171 job.remaining = job.service;
2181 if (s.has_pools && !(s.preemptive && s.resume && job.elapsed > 0.0)) {
2182 const std::size_t ty = s.server_type[slot];
2183 if (s.type_has_svc[ty][job.cls]) {
2184 job.service = slot_snap(
2185 s.type_svc[ty][job.cls].next_at(g_hsvc[i][ty][job.cls], now),
2187 job.remaining = job.service;
2191 s.server[slot] = job;
2192 s.server_busy[slot] =
true;
2193 s.server_start[slot] = now;
2194 s.server_tag[slot] = ++ps_tag;
2195 acc.update_busy(i, job.cls, now);
2196 acc.busy[i][job.cls] += 1.0;
2203 const double scale0 = rate_scaling(i, job.cls);
2204 if (!(scale0 > 0.0))
return;
2206 e.t = now + job.remaining / scale0;
2211 e.tag = s.server_tag[slot];
2222 auto preempt = [&](std::size_t i, std::size_t slot) {
2223 StationState& s = S[i];
2224 Job job = s.server[slot];
2225 const double served = now - s.server_start[slot];
2226 job.remaining = std::max(0.0, job.remaining - served);
2227 job.elapsed += served;
2228 s.server_busy[slot] =
false;
2229 s.server_tag[slot] = 0;
2230 acc.update_busy(i, job.cls, now);
2231 acc.busy[i][job.cls] -= 1.0;
2234 buffer_push(i, job);
2245 std::function<void(std::size_t)> poll_serve;
2246 std::function<void(std::size_t)> poll_advance;
2248 poll_serve = [&](std::size_t i) {
2249 StationState& s = S[i];
2250 if (s.server_busy[0] || s.server_blocked[0] || s.server_held[0])
return;
2252 std::size_t at = s.buffer.size();
2253 for (std::size_t j = 0; j < s.buffer.size(); ++j)
2254 if (s.buffer[j].cls == s.poll_at &&
2255 (at == s.buffer.size() || s.buffer[j].t_arr < s.buffer[at].t_arr))
2257 if (at == s.buffer.size() || s.poll_budget == 0) {
2261 Job job = s.buffer[at];
2262 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
2263 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
2265 start_service(i, 0, job);
2268 poll_advance = [&](std::size_t i) {
2269 StationState& s = S[i];
2270 for (std::size_t step = 0; step < K; ++step) {
2271 const std::size_t from = s.poll_at;
2272 const std::size_t next = (from + 1) % K;
2275 if (s.has_switchover[from]) {
2276 s.poll_switching =
true;
2278 e.t = now + s.switchover[from].next(g_aux[i]);
2287 ? std::numeric_limits<std::size_t>::max()
2289 bool has_work =
false;
2290 for (
const Job& j : s.buffer)
2291 if (j.cls == next) {
2303 s.poll_parked =
true;
2318 std::function<void(std::size_t)> pas_reschedule = [&](std::size_t i) {
2319 StationState& s = S[i];
2320 if (s.pas_list.empty())
return;
2321 std::vector<std::size_t> seq;
2322 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
2323 const double total = s.pas_rate(seq);
2324 if (!(total > 0.0))
return;
2325 s.pas_tag = ++ps_tag;
2327 e.t = now + (-std::log(uniform01(g_aux[i])) / total);
2332 e.slot = std::numeric_limits<std::size_t>::max();
2340 auto pas_complete = [&](std::size_t i) -> Job {
2341 StationState& s = S[i];
2342 std::vector<std::size_t> seq;
2343 for (
const Job& j : s.pas_list) seq.push_back(j.cls);
2344 std::vector<double> inc(seq.size(), 0.0);
2345 double prev = 0.0, total = 0.0;
2346 for (std::size_t p = 0; p < seq.size(); ++p) {
2347 std::vector<std::size_t> pre(seq.begin(),
2348 seq.begin() +
static_cast<std::ptrdiff_t
>(p + 1));
2349 const double cur = s.pas_rate(pre);
2350 inc[p] = std::max(0.0, cur - prev);
2354 std::size_t pos = 0;
2356 const double u = uniform01(g_routing) * total;
2358 for (std::size_t p = 0; p < inc.size(); ++p) {
2370 std::vector<std::size_t> chain(1, pos);
2371 std::size_t moving = seq[pos], cur = pos;
2373 std::size_t nxt = seq.size();
2374 for (std::size_t j = cur + 1; j < seq.size(); ++j) {
2375 const bool swappable =
2376 s.pas_swap.empty() ||
2377 (moving < s.pas_swap.size() && seq[j] < s.pas_swap[moving].size() &&
2378 s.pas_swap[moving][seq[j]]);
2384 if (nxt >= seq.size())
break;
2385 chain.push_back(nxt);
2389 Job departing = s.pas_list[chain.back()];
2390 std::vector<Job> arr = s.pas_list;
2391 for (std::size_t k = 0; k + 1 < chain.size(); ++k)
2392 arr[chain[k + 1]] = s.pas_list[chain[k]];
2393 std::vector<Job> survivors;
2394 for (std::size_t k = 0; k < arr.size(); ++k)
2395 if (k != chain.front()) survivors.push_back(arr[k]);
2396 s.pas_list = survivors;
2411 std::function<bool(std::size_t, Job, std::size_t)> admit =
2412 [&](std::size_t i, Job job, std::size_t from) ->
bool {
2413 StationState& s = S[i];
2415 throw InputError(
"SolverLDES (native engine): a job of class '" +
2416 sn.classes[job.cls].name +
"' reached station '" +
2417 sn.stations[i].name +
"', which does not serve it");
2425 int rgi = region_of[i];
2426 if (rgi >= 0 && from < M && region_of[from] == rgi) rgi = -1;
2428 Region& R = regions[
static_cast<std::size_t
>(rgi)];
2429 if (R.would_exceed(job.cls)) {
2431 if (R.drops(job.cls)) {
2432 R.dropped[job.cls] += 1.0;
2433 dropped[i][job.cls] += 1.0;
2441 w.job.id = ++job_id;
2442 region_wait[
static_cast<std::size_t
>(rgi)].push_back(w);
2443 R.blocked[job.cls] += 1.0;
2450 if (s.role == Role::Queue) {
2452 for (std::size_t r = 0; r < K; ++r) total += acc.qlen[i][r] - s.blocked_at[r];
2453 if (total + 1.0 > s.cap ||
2454 acc.qlen[i][job.cls] - s.blocked_at[job.cls] + 1.0 > s.classcap[job.cls]) {
2459 if (s.has_retrial[job.cls] &&
2460 (s.max_attempts[job.cls] <= 0 ||
2461 job.attempts < s.max_attempts[job.cls])) {
2462 const double dt = now - s.orbit_last;
2464 for (std::size_t r = 0; r < K; ++r) s.tot_orbit[r] += s.orbit_size[r] * dt;
2466 s.orbit_size[job.cls] += 1.0;
2468 e.t = now + s.retrial[job.cls].next(g_svc[i][job.cls]);
2473 e.job.attempts = job.attempts + 1;
2477 if (s.has_retrial[job.cls]) s.retrial_lost[job.cls] += 1.0;
2478 dropped[i][job.cls] += 1.0;
2484 if (!s.balk[job.cls].empty()) {
2486 for (
const StationState::BalkRule& br : s.balk[job.cls])
2487 if (total >= br.min_jobs && (br.max_jobs < 0.0 || total <= br.max_jobs))
2488 p = std::max(p, br.probability);
2489 if (p > 0.0 && uniform01(g_routing) < p) {
2490 balked[i][job.cls] += 1.0;
2496 job.priority = classprio[job.cls];
2500 job.service = slot_snap(S[i].svc[job.cls].next_at(g_svc[i][job.cls], now),
"service time");
2501 job.remaining = job.service;
2503 job.rank = uniform01(g_routing);
2504 job.deadline = now + classdeadline[job.cls];
2507 Region& R = regions[
static_cast<std::size_t
>(rgi)];
2512 acc.update_qlen(i, job.cls, now);
2513 acc.qlen[i][job.cls] += 1.0;
2516 acc.arrived[i][job.cls] += 1.0;
2517 bp.track(i, job.cls, +1, now);
2519 if (s.role == Role::Delay) {
2520 if (has_removal_signal) delay_live[i].push_back(job);
2522 e.t = now + job.service;
2526 e.slot = std::numeric_limits<std::size_t>::max();
2535 if (s.lps_limit > 0 && s.ps_jobs.size() >= s.lps_limit) {
2536 buffer_push(i, job);
2541 pj.priority = job.priority;
2542 pj.t_arr = job.t_arr;
2543 pj.t_sys = job.t_sys;
2544 pj.total = job.service;
2545 pj.remaining = job.service;
2546 pj.parent = job.parent;
2547 s.ps_jobs.push_back(pj);
2554 if (s.has_setup && !s.setup_on) {
2555 buffer_push(i, job);
2556 if (!s.setup_running) {
2557 s.setup_running =
true;
2559 e.t = now + s.setup_time.next(g_aux[i]);
2568 s.pas_list.push_back(job);
2569 acc.update_busy(i, job.cls, now);
2570 acc.busy[i][job.cls] += 1.0;
2575 buffer_push(i, job);
2576 if (s.poll_parked && !s.poll_switching) {
2577 s.poll_parked =
false;
2578 if (job.cls == s.poll_at)
2582 }
else if (!s.server_busy[0] && !s.poll_switching) {
2588 const std::size_t sl = free_slot_for(i, job.cls);
2589 if (sl < s.nservers) {
2590 start_service(i, sl, job);
2601 std::vector<Job> held;
2602 std::vector<std::size_t> slot_of;
2603 for (std::size_t sl = 0; sl < s.nservers; ++sl)
2604 if (s.server_busy[sl] && !s.server_blocked[sl]) {
2609 if (s.has_pools && !s.type_compat[s.server_type[sl]][job.cls])
continue;
2610 Job h = s.server[sl];
2614 h.remaining = std::max(0.0, h.remaining - (now - s.server_start[sl]));
2615 h.elapsed += now - s.server_start[sl];
2617 slot_of.push_back(sl);
2620 static_cast<double>(s.nservers), now);
2621 if (v < held.size()) {
2622 const std::size_t sl = slot_of[v];
2624 start_service(i, sl, job);
2629 buffer_push(i, job);
2634 if (s.has_patience[job.cls]) {
2636 e.t = now + s.patience[job.cls].next(g_svc[i][job.cls]);
2647 const std::map<std::size_t, double> kNoWeights;
2649 std::vector<std::size_t> tab_all;
2659 auto draw_prob = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
2661 for (std::size_t k = 0; k < tab.size(); ++k) total += tab[k].mass;
2662 const double u = uniform01(g_routing) * total;
2664 std::size_t pick = 0;
2665 for (pick = 0; pick + 1 < tab.size(); ++pick) {
2666 cum += tab[pick].mass;
2667 if (u <= cum)
break;
2683 const bool has_sdr = !sn.sdr.empty() && !sn.sdr_nodes.empty();
2684 pfqn::SdrCoeff sdr_coeff;
2686 std::vector<double> sdr_pop, sdr_mass;
2698 auto draw_sdr = [&](
const std::vector<RouteDest>& tab) -> std::size_t {
2699 sdr_pop.assign(M, 0.0);
2700 for (std::size_t i = 0; i < M; ++i) {
2701 if (S[i].role == Role::Source)
continue;
2703 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[i][q];
2709 sdr_mass.assign(tab.size(), 0.0);
2711 for (std::size_t i = 0; i < tab.size(); ++i) {
2712 const std::size_t dnode = tab[i].node - 1;
2714 for (std::size_t b = 1; b < sn.sdr_nodes.branch.size(); ++b)
2715 if (sn.sdr_nodes.entryOf[b] == dnode) p += Pb[b];
2716 if (sn.sdr_nodes.departure == dnode) p += Ped;
2719 if (p < 0.0) p = 0.0;
2728 if (!(total > 0.0))
return draw_prob(tab);
2729 const double u = uniform01(g_routing) * total;
2731 std::size_t pick = 0;
2732 for (pick = 0; pick + 1 < tab.size(); ++pick) {
2733 cum += sdr_mass[pick];
2734 if (u <= cum)
break;
2747 auto shortest_queue = [&](
const std::vector<RouteDest>& tab,
2748 const std::vector<std::size_t>& cand) -> std::size_t {
2749 double best = std::numeric_limits<double>::infinity();
2750 std::vector<std::size_t> tied;
2751 for (std::size_t c = 0; c < cand.size(); ++c) {
2752 const std::size_t k = cand[c];
2753 if (tab[k].station >= M || S[tab[k].station].role == Role::Source)
continue;
2755 for (std::size_t q = 0; q < K; ++q) held += acc.qlen[tab[k].station][q];
2760 }
else if (held == best) {
2764 if (tied.empty())
return cand.empty() ? 0 : cand[0];
2765 if (tied.size() == 1)
return tied[0];
2766 std::size_t at =
static_cast<std::size_t
>(uniform01(g_routing) *
2767 static_cast<double>(tied.size()));
2768 if (at >= tied.size()) at = tied.size() - 1;
2782 auto draw_node_route = [&](std::size_t inode, std::size_t r) -> RouteEntry {
2783 const std::vector<RouteDest>& tab = nroute[inode][r];
2785 throw InputError(
"SolverLDES (native engine): node '" + sn.nodes[inode - 1].name +
2786 "' has no routing for class '" + sn.classes[r].name +
"'");
2787 std::size_t pick = 0;
2788 if (tab.size() > 1) {
2789 if (tab_all.size() != tab.size()) {
2790 tab_all.resize(tab.size());
2791 for (std::size_t k = 0; k < tab.size(); ++k) tab_all[k] = k;
2793 switch (node_routing[inode][r]) {
2795 pick =
static_cast<std::size_t
>(uniform01(g_routing) *
2796 static_cast<double>(tab.size()));
2797 if (pick >= tab.size()) pick = tab.size() - 1;
2801 pick = rr_counter[inode][r] % tab.size();
2802 ++rr_counter[inode][r];
2811 const std::map<std::size_t, double>& w =
2812 (sn.nodes[inode - 1].routing_weights.size() > r)
2813 ? sn.nodes[inode - 1].routing_weights[r]
2815 std::vector<double> raw(tab.size(), 0.0);
2816 bool integral =
true;
2817 for (std::size_t k = 0; k < tab.size(); ++k) {
2818 const std::map<std::size_t, double>::const_iterator it = w.find(tab[k].node);
2819 raw[k] = (it == w.end()) ? 0.0 : it->second;
2820 if (raw[k] < 0.0 || raw[k] != std::floor(raw[k])) integral =
false;
2822 std::vector<std::size_t> quota(tab.size(), 0);
2823 std::size_t total = 0;
2824 for (std::size_t k = 0; k < tab.size(); ++k) {
2825 double v = integral ? raw[k] : raw[k] * 1000.0;
2826 std::size_t q = (v > 0.0) ?
static_cast<std::size_t
>(v) : 0;
2827 if (raw[k] > 0.0 && q < 1) q = 1;
2834 pick = rr_counter[inode][r] % tab.size();
2835 ++rr_counter[inode][r];
2838 std::size_t counter = rr_counter[inode][r] % total;
2839 ++rr_counter[inode][r];
2840 std::size_t cum = 0;
2841 for (pick = 0; pick + 1 < tab.size(); ++pick) {
2843 if (counter < cum)
break;
2855 pick = has_sdr ? draw_sdr(tab) : draw_prob(tab);
2859 pick = shortest_queue(tab, tab_all);
2866 std::size_t d = (sn.nodes[inode - 1].routing_param.size() > r &&
2867 sn.nodes[inode - 1].routing_param[r] > 0)
2868 ?
static_cast<std::size_t
>(
2869 sn.nodes[inode - 1].routing_param[r])
2871 if (tab.size() <= d) {
2872 pick = shortest_queue(tab, tab_all);
2875 std::vector<std::size_t> avail(tab.size());
2876 for (std::size_t k = 0; k < tab.size(); ++k) avail[k] = k;
2877 std::vector<std::size_t> cand;
2878 for (std::size_t k = 0; k < d && !avail.empty(); ++k) {
2879 std::size_t at =
static_cast<std::size_t
>(
2880 uniform01(g_routing) *
static_cast<double>(avail.size()));
2881 if (at >= avail.size()) at = avail.size() - 1;
2882 cand.push_back(avail[at]);
2883 avail.erase(avail.begin() +
static_cast<std::ptrdiff_t
>(at));
2885 pick = shortest_queue(tab, cand);
2889 pick = draw_prob(tab);
2894 const RouteDest& d = tab[pick];
2897 e.station = d.station;
2899 e.cls = d.cls[0].first;
2900 if (d.cls.size() > 1) {
2902 for (std::size_t k = 0; k < d.cls.size(); ++k) total += d.cls[k].second;
2903 const double u = uniform01(g_routing) * total;
2905 for (std::size_t k = 0; k < d.cls.size(); ++k) {
2906 cum += d.cls[k].second;
2907 if (u <= cum || k + 1 == d.cls.size()) {
2908 e.cls = d.cls[k].first;
2937 auto resolve_final_cls = [&](std::size_t node, std::size_t cls) -> std::size_t {
2938 std::size_t at_node = node, at_cls = cls;
2939 for (std::size_t hop = 0; hop <= nnodes && at_node != 0; ++hop) {
2940 const NodeType dt = sn.nodes[at_node - 1].nodetype;
2941 if (dt != NodeType::ClassSwitch && dt != NodeType::Router && dt != NodeType::Logger)
2943 const RouteEntry e = draw_node_route(at_node, at_cls);
2955 if (o.busy_period_orders > 0) {
2956 std::vector<std::vector<std::size_t>> sets;
2957 std::vector<std::string> set_names;
2958 for (std::size_t i = 0; i < M; ++i) {
2959 if (S[i].role == Role::Source || S[i].role == Role::Synchronization)
continue;
2960 sets.push_back(std::vector<std::size_t>(1, i));
2961 set_names.push_back(sn.stations[i].name);
2963 for (
const std::vector<std::size_t>& sub : o.busy_period_subnets) {
2965 for (std::size_t s2 : sub) {
2966 if (s2 >= M || S[s2].role == Role::Source)
2967 throw InputError(
"SolverLDES (native engine): station " +
2968 std::to_string(s2) +
2969 " of a busy period subnetwork is not a service station");
2970 nm += (nm.empty() ?
"" :
"+") + sn.stations[s2].name;
2972 sets.push_back(sub);
2973 set_names.push_back(nm);
2975 std::vector<BpTarget> targets;
2976 for (std::size_t si = 0; si < sets.size(); ++si) {
2978 t.stations = sets[si];
2980 t.name = set_names[si];
2981 targets.push_back(t);
2982 for (std::size_t r = 0; r < K; ++r) {
2984 tc.stations = sets[si];
2985 tc.job_class =
static_cast<int>(r);
2986 tc.name = set_names[si] +
":" + sn.classes[r].name;
2987 targets.push_back(tc);
2990 bp.init(targets, o.busy_period_orders, M);
3003 std::function<void(std::size_t)> release_blocked = [&](std::size_t j) {
3004 bool progress =
true;
3007 std::size_t best_i = M, best_sl = 0;
3008 double oldest = std::numeric_limits<double>::infinity();
3009 for (std::size_t a = 0; a < M; ++a) {
3013 if (S[a].role != Role::Queue || S[a].ps || S[a].server_blocked.empty())
continue;
3014 for (std::size_t sl = 0; sl < S[a].nservers; ++sl)
3015 if (S[a].server_blocked[sl] && S[a].blocked_dest[sl] == j &&
3016 S[a].blocked_job[sl].t_arr < oldest) {
3017 oldest = S[a].blocked_job[sl].t_arr;
3022 if (best_i >= M)
break;
3023 const std::size_t dcls = S[best_i].blocked_dest_cls[best_sl];
3024 if (!dest_has_room(j, dcls))
break;
3026 StationState& src = S[best_i];
3027 Job moved = src.blocked_job[best_sl];
3028 src.server_blocked[best_sl] =
false;
3029 src.server_busy[best_sl] =
false;
3030 src.server_tag[best_sl] = 0;
3031 acc.update_qlen(j, dcls, now);
3032 acc.qlen[j][dcls] -= 1.0;
3033 S[j].blocked_at[dcls] -= 1.0;
3036 acc.update_busy(best_i, moved.cls, now);
3037 if (acc.busy[best_i][moved.cls] > 0.0) acc.busy[best_i][moved.cls] -= 1.0;
3041 fresh.t_sys = moved.t_sys;
3042 fresh.parent = moved.parent;
3043 admit(j, fresh, best_i);
3047 if (buffer_has_for_slot(best_i, best_sl)) {
3048 Job nextjob = buffer_pop_for_slot(best_i, best_sl);
3049 start_service(best_i, best_sl, nextjob);
3065 std::function<void(std::size_t)> release_region = [&](std::size_t rg) {
3066 std::vector<RegionWaiter>& q = region_wait[rg];
3067 bool progress =
true;
3068 while (progress && !q.empty()) {
3070 for (std::size_t w = 0; w < q.size(); ++w) {
3072 if (R.would_exceed(q[w].job.cls))
continue;
3073 RegionWaiter taken = q[w];
3075 R.blocked[taken.job.cls] -= 1.0;
3076 q.erase(q.begin() +
static_cast<std::ptrdiff_t
>(w));
3078 fresh.cls = taken.job.cls;
3079 fresh.t_sys = taken.job.t_sys;
3080 admit(taken.station, fresh, M);
3090 auto signal_held = [&](std::size_t i) -> std::size_t {
3091 const StationState& s = S[i];
3092 if (s.role == Role::Delay)
return delay_live[i].size();
3093 if (s.pas)
return s.pas_list.size();
3094 std::size_t n = s.buffer.size();
3095 if (s.ps)
return n + s.ps_jobs.size();
3096 for (std::size_t sl = 0; sl < s.nservers; ++sl)
3097 if (s.server_busy[sl]) ++n;
3118 StationState& s = S[i];
3121 if (s.role == Role::Delay) {
3122 std::vector<Job>& live = delay_live[i];
3123 if (live.empty())
return K;
3126 for (std::size_t j = 1; j < live.size(); ++j)
3127 if (live[j].t_arr < live[at].t_arr) at = j;
3129 for (std::size_t j = 1; j < live.size(); ++j)
3130 if (live[j].t_arr > live[at].t_arr) at = j;
3132 at =
static_cast<std::size_t
>(uniform01(g_routing) *
3133 static_cast<double>(live.size()));
3134 if (at >= live.size()) at = live.size() - 1;
3136 const std::size_t rc = live[at].cls;
3137 live.erase(live.begin() +
static_cast<std::ptrdiff_t
>(at));
3145 if (s.pas_list.empty())
return K;
3148 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
3149 if (s.pas_list[j].t_arr < s.pas_list[at].t_arr) at = j;
3151 for (std::size_t j = 1; j < s.pas_list.size(); ++j)
3152 if (s.pas_list[j].t_arr > s.pas_list[at].t_arr) at = j;
3154 at =
static_cast<std::size_t
>(uniform01(g_routing) *
3155 static_cast<double>(s.pas_list.size()));
3156 if (at >= s.pas_list.size()) at = s.pas_list.size() - 1;
3158 const std::size_t rc = s.pas_list[at].cls;
3159 s.pas_list.erase(s.pas_list.begin() +
static_cast<std::ptrdiff_t
>(at));
3160 acc.update_busy(i, rc, now);
3161 if (acc.busy[i][rc] > 0.0) acc.busy[i][rc] -= 1.0;
3166 const std::size_t waiting = s.buffer.size();
3171 const std::size_t inservice = s.ps_jobs.size();
3172 if (waiting + inservice == 0)
return K;
3175 const std::size_t pick =
static_cast<std::size_t
>(
3176 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
3177 from_wait = pick < waiting;
3179 from_wait = waiting > 0;
3184 for (std::size_t j = 1; j < s.buffer.size(); ++j)
3185 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
3187 for (std::size_t j = 1; j < s.buffer.size(); ++j)
3188 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
3190 at = std::min(waiting - 1,
3191 static_cast<std::size_t
>(uniform01(g_routing) *
3192 static_cast<double>(waiting)));
3194 const std::size_t rc = s.buffer[at].cls;
3195 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
3196 if (s.sched != SchedStrategy::FSP)
3197 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
3203 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
3204 if (s.ps_jobs[j].t_arr < s.ps_jobs[at].t_arr) at = j;
3206 for (std::size_t j = 1; j < s.ps_jobs.size(); ++j)
3207 if (s.ps_jobs[j].t_arr > s.ps_jobs[at].t_arr) at = j;
3209 at = std::min(inservice - 1,
3210 static_cast<std::size_t
>(uniform01(g_routing) *
3211 static_cast<double>(inservice)));
3213 const std::size_t rc = s.ps_jobs[at].cls;
3214 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(at));
3216 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
3217 Job nextjob = buffer_pop(i);
3219 pj.cls = nextjob.cls;
3220 pj.priority = nextjob.priority;
3221 pj.t_arr = nextjob.t_arr;
3222 pj.t_sys = nextjob.t_sys;
3223 pj.total = nextjob.service;
3224 pj.remaining = nextjob.service;
3225 pj.parent = nextjob.parent;
3226 s.ps_jobs.push_back(pj);
3235 std::size_t inservice = 0;
3236 for (std::size_t sl = 0; sl < s.nservers; ++sl)
3237 if (s.server_busy[sl]) ++inservice;
3238 if (waiting + inservice == 0)
return K;
3241 const std::size_t pick =
static_cast<std::size_t
>(
3242 uniform01(g_routing) *
static_cast<double>(waiting + inservice));
3243 from_wait = pick < waiting;
3245 from_wait = waiting > 0;
3251 for (std::size_t j = 1; j < s.buffer.size(); ++j)
3252 if (s.buffer[j].t_arr < s.buffer[at].t_arr) at = j;
3254 for (std::size_t j = 1; j < s.buffer.size(); ++j)
3255 if (s.buffer[j].t_arr > s.buffer[at].t_arr) at = j;
3257 at = std::min(waiting - 1,
3258 static_cast<std::size_t
>(uniform01(g_routing) *
3259 static_cast<double>(waiting)));
3261 const std::size_t rc = s.buffer[at].cls;
3262 s.buffer.erase(s.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
3263 if (s.sched != SchedStrategy::FSP)
3264 std::make_heap(s.buffer.begin(), s.buffer.end(), s.cmp);
3268 std::size_t slot = s.nservers;
3269 for (std::size_t sl = 0; sl < s.nservers; ++sl) {
3270 if (!s.server_busy[sl])
continue;
3271 if (slot == s.nservers) {
3276 if (s.server[sl].t_arr < s.server[slot].t_arr) slot = sl;
3278 if (s.server[sl].t_arr > s.server[slot].t_arr) slot = sl;
3282 std::size_t pick = std::min(inservice - 1,
3283 static_cast<std::size_t
>(uniform01(g_routing) *
3284 static_cast<double>(inservice)));
3285 for (std::size_t sl = 0; sl < s.nservers; ++sl)
3286 if (s.server_busy[sl]) {
3294 if (slot == s.nservers)
return K;
3296 const std::size_t rc = s.server[slot].cls;
3299 s.server_busy[slot] =
false;
3300 s.server_tag[slot] = 0;
3301 acc.update_busy(i, rc, now);
3302 acc.busy[i][rc] -= 1.0;
3307 }
else if (!s.server_blocked[slot] && !s.server_held[slot] &&
3308 buffer_has_for_slot(i, slot)) {
3309 Job nextjob = buffer_pop_for_slot(i, slot);
3310 start_service(i, slot, nextjob);
3332 auto apply_signal = [&](std::size_t i,
const Job& sig) {
3333 StationState& s = S[i];
3334 const std::size_t held = signal_held(i);
3336 std::size_t to_remove = 1;
3337 if (sig.cls < sn.signaltype.size() &&
3340 }
else if (sig.cls < sn.signalremdist.size() && !sn.signalremdist[sig.cls].empty()) {
3341 const std::vector<T>& pmf = sn.signalremdist[sig.cls];
3342 const double u = uniform01(g_routing);
3345 for (; b + 1 < pmf.size(); ++b) {
3346 cum +=
static_cast<double>(pmf[b]);
3349 to_remove = std::min(b, held);
3351 to_remove = std::min<std::size_t>(1, held);
3354 if (sig.cls < sn.signalrempolicy.size()) policy = sn.signalrempolicy[sig.cls];
3355 for (std::size_t rep = 0; rep < to_remove; ++rep) {
3356 if (signal_held(i) == 0)
break;
3357 const std::size_t rc = signal_remove_one(i, policy);
3360 acc.update_qlen(i, rc, now);
3361 acc.qlen[i][rc] -= 1.0;
3362 bp.track(i, rc, -1, now);
3366 if (region_of[i] >= 0) {
3367 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
3368 regions[rg].update(now);
3369 regions[rg].leave(rc);
3378 sys_resp_sum[sig.cls] += now - sig.t_sys;
3379 sys_resp_cnt[sig.cls] += 1.0;
3380 sys_completed[sig.cls] += 1.0;
3397 std::uint64_t total_completions = 0;
3409 std::function<void(CacheState&, std::size_t, std::size_t)> release_delayed_hits;
3425 std::function<void(std::size_t, Job, std::size_t)> deliver =
3426 [&](std::size_t node, Job job, std::size_t from) {
3427 const NodeType nt = sn.nodes[node - 1].nodetype;
3429 if (nt == NodeType::Sink) {
3430 sys_resp_sum[job.cls] += now - job.t_sys;
3431 sys_resp_cnt[job.cls] += 1.0;
3432 sys_completed[job.cls] += 1.0;
3436 if (nt == NodeType::Cache) {
3437 auto ci = caches.find(node);
3438 if (ci == caches.end())
3439 throw InputError(
"SolverLDES (native engine): Cache node '" +
3440 sn.nodes[node - 1].name +
"' carries no parameters");
3441 CacheState& cs = ci->second;
3449 if (cs.has_retrieval) {
3450 const int fetched = cs.fetches_item(job.cls);
3452 const std::size_t fi =
static_cast<std::size_t
>(fetched);
3453 cache_miss(cs, fi, uniform01(g_routing), uniform01(g_routing));
3454 cs.in_flight[fi] = 0;
3455 cs.total_fetch_time += now - cs.fetch_start[fi];
3456 cs.completed_fetches += 1.0;
3457 release_delayed_hits(cs, fi, node);
3458 if (cs.miss_class[job.cls] >= 0)
3459 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
3460 const RouteEntry re = draw_node_route(node, out.cls);
3462 if (re.node != 0 && sn.nodes[re.node - 1].nodetype == NodeType::Sink)
3463 ++total_completions;
3464 deliver(re.node, out, M);
3469 const std::vector<double>& pop = cs.popularity[job.cls];
3471 throw InputError(
"SolverLDES (native engine): class '" +
3472 sn.classes[job.cls].name +
"' reads cache '" +
3473 sn.nodes[node - 1].name +
"' with no item popularity");
3474 const double u = uniform01(g_routing);
3475 std::size_t item = 0;
3476 while (item + 1 < pop.size() && u >= pop[item]) ++item;
3478 const int at = cs.find(item);
3480 cs.hits[job.cls] += 1.0;
3481 cache_hit(cs, item,
static_cast<std::size_t
>(at), uniform01(g_routing));
3482 if (cs.hit_class[job.cls] >= 0)
3483 out.cls =
static_cast<std::size_t
>(cs.hit_class[job.cls]);
3484 }
else if (cs.has_retrieval && cs.retrieval_of(item, job.cls) > 0) {
3485 if (cs.in_flight[item]) {
3489 CacheState::HeldRequest hr;
3492 hr.t_sys = job.t_sys;
3493 cs.held[item].push_back(hr);
3499 cs.misses[job.cls] += 1.0;
3500 cs.in_flight[item] = 1;
3501 cs.fetch_start[item] = now;
3502 out.cls = cs.retrieval_of(item, job.cls) - 1;
3504 cs.misses[job.cls] += 1.0;
3505 cache_miss(cs, item, uniform01(g_routing), uniform01(g_routing));
3506 if (cs.miss_class[job.cls] >= 0)
3507 out.cls =
static_cast<std::size_t
>(cs.miss_class[job.cls]);
3511 const RouteEntry e = draw_node_route(node, out.cls);
3515 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
3516 ++total_completions;
3517 deliver(e.node, out, M);
3521 if (nt == NodeType::ClassSwitch || nt == NodeType::Router || nt == NodeType::Logger) {
3537 std::size_t at = node;
3538 for (std::size_t hop = 0; hop <= nnodes; ++hop) {
3539 const RouteEntry e = draw_node_route(at, out.cls);
3541 if (e.node == 0)
return;
3542 const NodeType dt = sn.nodes[e.node - 1].nodetype;
3543 if (dt == NodeType::ClassSwitch || dt == NodeType::Router ||
3544 dt == NodeType::Logger) {
3548 deliver(e.node, out, from);
3551 throw InputError(
"SolverLDES (native engine): the routing out of node '" +
3552 sn.nodes[node - 1].name +
3553 "' cycles through pass-through nodes without reaching a station");
3556 if (nt == NodeType::Fork) {
3560 const std::vector<RouteDest>& tab = nroute[node][job.cls];
3561 if (tab.empty())
return;
3562 std::vector<RouteEntry> links;
3563 for (std::size_t di = 0; di < tab.size(); ++di)
3564 for (std::size_t ci = 0; ci < tab[di].cls.size(); ++ci) {
3566 e.node = tab[di].node;
3567 e.station = tab[di].station;
3568 e.sink = tab[di].sink;
3569 e.cls = tab[di].cls[ci].first;
3572 const qn::NodeDef& fk = sn.nodes[node - 1];
3573 const qn::ForkParam<double>* fp = sn.fork_param_of(node);
3574 const int per_link = std::max(1,
static_cast<int>(fk.tasks_per_link + 0.5));
3582 std::vector<int> per_branch(links.size(), per_link);
3584 const bool variable = fork_is_variable(node);
3585 for (std::size_t li = 0; li < links.size(); ++li) {
3587 const std::size_t k0 = links[li].node - 1, r0 = job.cls;
3588 const double p = fp->fan_out_prob(k0, r0);
3589 if (p < 1.0 && g_fork.aux.next_double() >= p) {
3591 }
else if (!fp->fan_out_dist[k0][r0].disabled) {
3592 per_branch[li] = sample_fork_degree(fp->fan_out_dist[k0][r0]);
3594 per_branch[li] =
static_cast<int>(fp->fan_out_link(k0, r0) + 0.5);
3597 total += per_branch[li];
3600 throw InputError(
"SolverLDES (native engine): Fork '" + fk.name +
3601 "' emitted no sibling; at least one branch must be certain "
3602 "to emit at least one task");
3606 fs.t_sys = job.t_sys;
3608 fs.required = fs.total;
3609 const int jn = join_of_fork[node];
3611 auto jd = sn.joindecl.find(
static_cast<std::size_t
>(jn));
3612 if (jd != sn.joindecl.end() &&
3618 fs.required = std::min(fs.total,
3619 std::max(1,
static_cast<int>(jd->second.quorum + 0.5)));
3622 const std::uint64_t pid = ++next_parent;
3623 fork_sync[pid] = fs;
3624 for (std::size_t li = 0; li < links.size(); ++li)
3625 for (
int t = 0; t < per_branch[li]; ++t) {
3627 sib.cls = links[li].cls;
3628 sib.t_sys = job.t_sys;
3630 deliver(links[li].node, sib, M);
3635 if (nt == NodeType::Join) {
3636 auto it = fork_sync.find(job.parent);
3637 if (it == fork_sync.end()) {
3649 const std::size_t jd = node_to_station[node];
3651 acc.arrived[jd][job.cls] += 1.0;
3652 acc.join_dropped[jd][job.cls] += 1.0;
3656 ForkSync& fs = it->second;
3665 const std::size_t js = node_to_station[node];
3667 acc.update_qlen(js, job.cls, now);
3668 acc.qlen[js][job.cls] += 1.0;
3669 acc.arrived[js][job.cls] += 1.0;
3671 fs.siblings.push_back(std::make_pair(job.cls, now));
3672 if (
static_cast<int>(fs.siblings.size()) < fs.required)
return;
3676 for (std::size_t si = 0; si < fs.siblings.size(); ++si) {
3677 const std::size_t sc = fs.siblings[si].first;
3678 acc.update_qlen(js, sc, now);
3679 acc.qlen[js][sc] -= 1.0;
3682 acc.resp_sum[js][sc] += now - fs.siblings[si].second;
3683 acc.resp_cnt[js][sc] += 1.0;
3685 acc.completed[js][fs.cls] += 1.0;
3688 merged.cls = fs.cls;
3689 merged.t_sys = fs.t_sys;
3690 fork_sync.erase(it);
3691 const RouteEntry e = draw_node_route(node, merged.cls);
3693 deliver(e.node, merged, M);
3698 const std::size_t j = node_to_station[node];
3702 if (has_removal_signal && j < M && is_removal_signal[job.cls]) {
3703 apply_signal(j, job);
3716 if (has_sync_call && j < M && is_reply_signal[job.cls]) {
3717 acc.completed[j][job.cls] += 1.0;
3718 typename std::map<std::uint64_t, PendingCall>::iterator pc =
3719 pending_reply.find(job.call);
3720 if (pc != pending_reply.end()) {
3721 const std::size_t bi = pc->second.station, bs = pc->second.slot;
3722 const std::size_t bc = pc->second.cls;
3723 pending_reply.erase(pc);
3724 StationState& bst = S[bi];
3725 bst.server_held[bs] =
false;
3726 acc.update_busy(bi, bc, now);
3727 if (acc.busy[bi][bc] > 0.0) acc.busy[bi][bc] -= 1.0;
3728 if (!bst.server_blocked[bs] && buffer_has_for_slot(bi, bs)) {
3729 Job nextjob = buffer_pop_for_slot(bi, bs);
3730 start_service(bi, bs, nextjob);
3733 release_blocked(bi);
3735 const RouteEntry re2 = draw_node_route(node, job.cls);
3737 onward.cls = re2.cls;
3738 onward.t_sys = job.t_sys;
3739 onward.parent = job.parent;
3740 deliver(re2.node, onward, M);
3748 j + 1 == sn.classes[job.cls].refstat) {
3749 sys_resp_sum[job.cls] += now - job.t_sys;
3750 sys_resp_cnt[job.cls] += 1.0;
3751 sys_completed[job.cls] += 1.0;
3755 throw InputError(
"SolverLDES (native engine): a closed job was dropped at "
3756 "station '" + sn.stations[j].name +
3757 "'; a closed class cannot lose population");
3760 release_delayed_hits = [&](CacheState& cs, std::size_t item, std::size_t node) {
3761 if (item >= cs.held.size() || cs.held[item].empty())
return;
3762 std::vector<CacheState::HeldRequest> freed;
3763 freed.swap(cs.held[item]);
3764 for (std::size_t i = 0; i < freed.size(); ++i) {
3765 const CacheState::HeldRequest& hr = freed[i];
3766 cs.delayed[hr.cls] += 1.0;
3767 cs.delayed_wait += now - hr.hold_time;
3769 out.cls = (cs.hit_class[hr.cls] >= 0) ?
static_cast<std::size_t
>(cs.hit_class[hr.cls])
3771 out.t_sys = hr.t_sys;
3772 const RouteEntry e = draw_node_route(node, out.cls);
3774 if (e.node != 0 && sn.nodes[e.node - 1].nodetype == NodeType::Sink)
3775 ++total_completions;
3776 deliver(e.node, out, M);
3788 auto flat_marking = [&]() {
3789 std::vector<double> tok(place_nodes.size() * K, 0.0);
3790 for (std::size_t p = 0; p < place_nodes.size(); ++p)
3791 for (std::size_t r = 0; r < K; ++r) tok[p * K + r] = marking[p][r];
3804 std::uint64_t fire_tag = 0;
3805 std::vector<std::uint64_t> live_fire(transitions.size(), 0);
3806 std::function<void()> spn_settle = [&]() {
3807 if (transitions.empty())
return;
3808 for (
int guard = 0; guard < 1000000; ++guard) {
3809 bool fired_any =
false;
3810 std::vector<double> tok = flat_marking();
3811 for (std::size_t t = 0; t < transitions.size(); ++t) {
3813 if (k < 0)
continue;
3814 const SpnMode& m = transitions[t].modes[
static_cast<std::size_t
>(k)];
3817 for (std::size_t p = 0; p < place_nodes.size(); ++p)
3818 for (std::size_t r = 0; r < K; ++r) {
3819 marking[p][r] -= m.enabling[p * K + r];
3820 marking[p][r] += m.firing[p * K + r];
3822 transitions[t].fired[
static_cast<std::size_t
>(k)] += 1.0;
3823 ++total_completions;
3827 if (!fired_any)
break;
3832 const std::vector<double> tok = flat_marking();
3833 for (std::size_t t = 0; t < transitions.size(); ++t) {
3834 double best = std::numeric_limits<double>::infinity();
3836 for (std::size_t k = 0; k < transitions[t].modes.size(); ++k) {
3837 const SpnMode& m = transitions[t].modes[k];
3838 if (m.immediate || !
spn_enabled(m, tok))
continue;
3839 const double rate =
spn_rate(m, tok);
3840 if (!(rate > 0.0))
continue;
3841 const double d = -std::log(uniform01(g_spn)) / rate;
3844 best_mode =
static_cast<int>(k);
3847 live_fire[t] = ++fire_tag;
3848 if (best_mode < 0)
continue;
3853 e.cls =
static_cast<std::size_t
>(best_mode);
3854 e.tag = live_fire[t];
3877 const bool warm_start = !o.init_sol.empty() && place_nodes.empty();
3879 for (std::size_t i = 0; i < M; ++i) {
3880 if (S[i].role != Role::Queue && S[i].role != Role::Delay)
continue;
3881 for (std::size_t k = K; k-- > 0;) {
3882 const std::size_t idx = i * K + k;
3883 if (idx >= o.init_sol.size())
continue;
3884 const double v = o.init_sol[idx];
3885 if (!(v > 0.0))
continue;
3886 const std::size_t count =
static_cast<std::size_t
>(v);
3887 for (std::size_t j = 0; j < count; ++j) {
3891 if (!admit(i, job, M))
3892 throw InputError(
"SolverLDES (native engine): the warm-start placement "
3893 "of class '" + sn.classes[k].name +
"' does not fit "
3894 "station '" + sn.stations[i].name +
"'");
3900 for (std::size_t r = 0; r < K; ++r) {
3903 for (std::size_t i = 0; i < M; ++i) held += acc.qlen[i][r];
3904 const double want = sn.classes[r].population;
3905 if (std::fabs(held - want) > 0.5)
3906 throw InputError(
"SolverLDES (native engine): the warm-start placement holds " +
3907 std::to_string(
static_cast<long>(held + 0.5)) +
" jobs of class '" +
3908 sn.classes[r].name +
"' against a population of " +
3909 std::to_string(
static_cast<long>(want + 0.5)));
3915 for (std::size_t i = 0; i < M; ++i) {
3916 if (!S[i].has_breakdown)
continue;
3918 e.t = S[i].failure_time.next_at(g_aux[i], 0.0);
3924 std::vector<Sampler> arrival(K);
3925 std::vector<double> lambda(K, 0.0);
3926 for (std::size_t r = 0; r < K; ++r) {
3929 throw InputError(
"SolverLDES (native engine): an open class with no Source");
3930 if (S[source_st].off[r])
continue;
3931 arrival[r] = S[source_st].svc[r];
3932 lambda[r] = 1.0 / arrival[r].mean();
3934 e.t = slot_snap(arrival[r].next_at(g_arr[r], 0.0),
"interarrival time");
3936 e.station = source_st;
3940 if (warm_start)
continue;
3941 const double n = sn.classes[r].population;
3942 if (!(n >= 0.0) || !std::isfinite(n))
3943 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
3944 "' has a population that is not a finite count");
3945 const std::size_t refst = sn.classes[r].refstat;
3946 if (refst == 0 || refst > M)
3947 throw InputError(
"SolverLDES (native engine): class '" + sn.classes[r].name +
3948 "' has no reference station");
3949 const std::size_t count =
static_cast<std::size_t
>(n + 0.5);
3950 for (std::size_t j = 0; j < count; ++j) {
3954 if (!admit(refst - 1, job, M))
3955 throw InputError(
"SolverLDES (native engine): the initial population of "
3956 "class '" + sn.classes[r].name +
3957 "' does not fit its reference station's capacity");
3964 cnvg.init(M, K, o, max_events);
3965 std::vector<std::size_t> servers_of(M, 1);
3966 std::vector<std::vector<bool>> off_of(M, std::vector<bool>(K,
true));
3967 for (std::size_t i = 0; i < M; ++i) {
3968 servers_of[i] = S[i].nservers;
3969 for (std::size_t r = 0; r < K; ++r)
3970 off_of[i][r] = S[i].off[r] || S[i].role == Role::Source ||
3971 S[i].role == Role::Synchronization;
3973 std::uint64_t last_cnvg_events = 0;
3974 bool converged =
false;
3980 Observations obs(M, K, o.tranfilter ==
"mser5", o.mserbatch > 0 ? o.mserbatch : 5);
3981 for (std::size_t i = 0; i < M; ++i)
3982 if (S[i].role == Role::Source || S[i].role == Role::Synchronization) obs.in_mser[i] = 0;
3983 std::uint64_t mser_interval = max_events / 1000ULL;
3984 if (mser_interval < 1) mser_interval = 1;
3985 std::uint64_t last_mser_events = 0;
3998 const bool transient_run = o.has_timespan && std::isfinite(o.t1) && o.t1 > o.t0;
3999 const double horizon = transient_run ? o.t1 : std::numeric_limits<double>::infinity();
4002 const double tran_interval = transient_run ? (o.t1 - o.t0) / 1000.0 : 0.0;
4003 double next_tran_sample = transient_run ? o.t0 + tran_interval : 0.0;
4004 double last_tran_time = transient_run ? o.t0 : 0.0;
4005 std::vector<double> tran_times;
4006 std::vector<std::vector<std::vector<double>>> tran_q(M, std::vector<std::vector<double>>(K));
4007 std::vector<std::vector<std::vector<double>>> tran_u(M, std::vector<std::vector<double>>(K));
4008 std::vector<std::vector<std::vector<double>>> tran_t(M, std::vector<std::vector<double>>(K));
4009 std::vector<std::vector<double>> last_tran_q(M, std::vector<double>(K, 0.0));
4010 std::vector<std::vector<double>> last_tran_b(M, std::vector<double>(K, 0.0));
4011 std::vector<std::vector<double>> last_tran_c(M, std::vector<double>(K, 0.0));
4022 std::map<std::vector<int>,
double> histogram;
4023 std::vector<std::pair<double, std::vector<int>>> trajectory;
4024 double hist_last = 0.0;
4027 const bool want_traj = transient_run || o.export_trajectory;
4028 const bool want_hist = transient_run || o.export_histogram || want_traj;
4030 auto joint_state = [&]() {
4031 std::vector<int> row(M * K, 0);
4032 for (std::size_t i = 0; i < M; ++i)
4033 for (std::size_t r = 0; r < K; ++r)
4034 row[i * K + r] =
static_cast<int>(acc.qlen[i][r] + 0.5);
4037 auto hist_accumulate = [&]() {
4038 if (!want_hist)
return;
4039 const double dt = now - hist_last;
4041 const std::vector<int> row = joint_state();
4042 histogram[row] += dt;
4043 if (want_traj) trajectory.push_back(std::make_pair(hist_last, row));
4048 auto tran_sample = [&]() {
4049 const double dt = now - last_tran_time;
4050 tran_times.push_back(now);
4051 for (std::size_t i = 0; i < M; ++i)
4052 for (std::size_t r = 0; r < K; ++r) {
4054 tran_q[i][r].push_back((acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt);
4055 const double c = S[i].util_peak;
4056 tran_u[i][r].push_back(
4057 (S[i].role == Role::Delay)
4058 ? (acc.tot_qlen[i][r] - last_tran_q[i][r]) / dt
4059 : (acc.tot_busy[i][r] - last_tran_b[i][r]) / (dt * c));
4060 tran_t[i][r].push_back((acc.completed[i][r] - last_tran_c[i][r]) / dt);
4062 tran_q[i][r].push_back(acc.qlen[i][r]);
4063 tran_u[i][r].push_back(0.0);
4064 tran_t[i][r].push_back(0.0);
4066 last_tran_q[i][r] = acc.tot_qlen[i][r];
4067 last_tran_b[i][r] = acc.tot_busy[i][r];
4068 last_tran_c[i][r] = acc.completed[i][r];
4070 last_tran_time = now;
4074 while (!evq.empty() && (transient_run || total_completions < max_events)) {
4075 const Event ev = evq.top();
4076 if (transient_run && ev.t > horizon)
break;
4083 if (want_hist) hist_accumulate();
4084 if (transient_run) {
4085 while (now >= next_tran_sample && next_tran_sample <= horizon) {
4086 const double save = now;
4087 now = next_tran_sample;
4088 for (std::size_t a = 0; a < M; ++a) {
4089 if (S[a].ps) ps_advance(a);
4090 for (std::size_t r = 0; r < K; ++r) {
4091 acc.update_qlen(a, r, now);
4092 acc.update_busy(a, r, now);
4096 next_tran_sample += tran_interval;
4101 if (ev.kind == EV_BREAKDOWN) {
4106 StationState& s = S[ev.station];
4108 ps_advance(ev.station);
4110 sd_advance(ev.station);
4113 ps_reschedule(ev.station);
4115 sd_reschedule(ev.station);
4125 nxt.t = now + (s.up ? s.failure_time.next_at(g_aux[ev.station], now)
4126 : s.repair_time.next_at(g_aux[ev.station], now));
4128 nxt.station = ev.station;
4133 if (ev.kind == EV_FIRING) {
4136 if (ev.station >= transitions.size() || live_fire[ev.station] != ev.tag)
continue;
4137 SpnTransition& tr = transitions[ev.station];
4138 const std::vector<double> tok = flat_marking();
4139 const SpnMode& m = tr.modes[ev.cls];
4141 for (std::size_t p = 0; p < place_nodes.size(); ++p)
4142 for (std::size_t r = 0; r < K; ++r) {
4143 marking[p][r] -= m.enabling[p * K + r];
4144 marking[p][r] += m.firing[p * K + r];
4146 tr.fired[ev.cls] += 1.0;
4147 ++total_completions;
4152 if (ev.kind == EV_SWITCHOVER) {
4153 StationState& sp = S[ev.station];
4154 sp.poll_switching =
false;
4155 sp.poll_at = ev.cls;
4157 ? std::numeric_limits<std::size_t>::max()
4160 for (
const Job& j : sp.buffer)
4161 if (j.cls == sp.poll_at) {
4166 poll_serve(ev.station);
4168 poll_advance(ev.station);
4172 if (ev.kind == EV_RETRIAL) {
4173 StationState& sr = S[ev.station];
4174 const double dt = now - sr.orbit_last;
4176 for (std::size_t r = 0; r < K; ++r) sr.tot_orbit[r] += sr.orbit_size[r] * dt;
4177 sr.orbit_last = now;
4178 sr.orbit_size[ev.cls] -= 1.0;
4179 sr.retried[ev.cls] += 1.0;
4182 admit(ev.station, ev.job, M);
4186 if (ev.kind == EV_SETUP) {
4187 StationState& ss = S[ev.station];
4192 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[ev.station][r];
4193 if (held > 0.0 || now + 1e-12 < ss.delayoff_at)
continue;
4194 ss.setup_on =
false;
4195 ss.delayoff_at = std::numeric_limits<double>::infinity();
4198 if (ss.setup_on)
continue;
4199 ss.setup_running =
false;
4201 ss.delayoff_at = std::numeric_limits<double>::infinity();
4202 for (std::size_t sl = 0; sl < ss.nservers && !ss.buffer.empty(); ++sl)
4203 if (!ss.server_busy[sl] && !ss.server_blocked[sl] && !ss.server_held[sl] &&
4204 buffer_has_for_slot(ev.station, sl)) {
4205 Job nextjob = buffer_pop_for_slot(ev.station, sl);
4206 start_service(ev.station, sl, nextjob);
4208 sd_reschedule(ev.station);
4212 if (ev.kind == EV_RENEGE) {
4216 StationState& sr = S[ev.station];
4217 std::size_t at = sr.buffer.size();
4218 for (std::size_t j = 0; j < sr.buffer.size(); ++j)
4219 if (sr.buffer[j].id == ev.tag) {
4223 if (at == sr.buffer.size())
continue;
4224 const std::size_t rc = sr.buffer[at].cls;
4225 sd_advance(ev.station);
4226 acc.update_qlen(ev.station, rc, now);
4227 acc.qlen[ev.station][rc] -= 1.0;
4228 bp.track(ev.station, rc, -1, now);
4229 sr.buffer.erase(sr.buffer.begin() +
static_cast<std::ptrdiff_t
>(at));
4230 if (sr.sched != SchedStrategy::FSP)
4231 std::make_heap(sr.buffer.begin(), sr.buffer.end(), sr.cmp);
4232 reneged[ev.station][rc] += 1.0;
4233 sd_reschedule(ev.station);
4237 if (ev.kind == EV_ARRIVAL) {
4239 const double gap = slot_snap(arrival[ev.cls].next_at(g_arr[ev.cls], now),
4240 "interarrival time");
4243 if (!(gap > 0.0) && arrival[ev.cls].time_varying())
continue;
4246 nxt.station = ev.station;
4250 const RouteEntry e = draw_node_route(sn.station_to_node[ev.station], ev.cls);
4254 deliver(e.node, job, M);
4259 const std::size_t i = ev.station;
4260 StationState& s = S[i];
4263 if (s.role == Role::Delay) {
4265 if (has_removal_signal) {
4269 std::size_t at = delay_live[i].size();
4270 for (std::size_t j = 0; j < delay_live[i].size(); ++j)
4271 if (delay_live[i][j].
id == job.id) {
4275 if (at == delay_live[i].size())
continue;
4276 delay_live[i].erase(delay_live[i].begin() +
static_cast<std::ptrdiff_t
>(at));
4280 std::size_t idx = s.ps_jobs.size();
4281 for (std::size_t j = 0; j < s.ps_jobs.size(); ++j)
4282 if (s.ps_jobs[j].tag == ev.tag) {
4289 if (idx == s.ps_jobs.size())
continue;
4290 const PsJob& pj = s.ps_jobs[idx];
4292 job.t_arr = pj.t_arr;
4293 job.t_sys = pj.t_sys;
4294 job.parent = pj.parent;
4295 s.ps_jobs.erase(s.ps_jobs.begin() +
static_cast<std::ptrdiff_t
>(idx));
4299 if (s.pas_tag != ev.tag || s.pas_list.empty())
continue;
4300 job = pas_complete(i);
4301 acc.update_busy(i, job.cls, now);
4302 if (acc.busy[i][job.cls] > 0.0) acc.busy[i][job.cls] -= 1.0;
4307 if (!s.server_busy[ev.slot] || s.server_tag[ev.slot] != ev.tag)
continue;
4309 job = s.server[ev.slot];
4310 s.server_busy[ev.slot] =
false;
4311 s.server_tag[ev.slot] = 0;
4312 acc.update_busy(i, job.cls, now);
4313 acc.busy[i][job.cls] -= 1.0;
4317 acc.update_qlen(i, job.cls, now);
4318 acc.qlen[i][job.cls] -= 1.0;
4319 bp.track(i, job.cls, -1, now);
4320 acc.completed[i][job.cls] += 1.0;
4321 acc.resp_sum[i][job.cls] += now - job.t_arr;
4322 if (want_respt) resp_samples[i][job.cls].push_back(now - job.t_arr);
4323 acc.resp_cnt[i][job.cls] += 1.0;
4324 ++total_completions;
4327 if (s.lps_limit > 0 && !s.buffer.empty() && s.ps_jobs.size() < s.lps_limit) {
4328 Job nextjob = buffer_pop(i);
4330 pj.cls = nextjob.cls;
4331 pj.priority = nextjob.priority;
4332 pj.t_arr = nextjob.t_arr;
4333 pj.t_sys = nextjob.t_sys;
4334 pj.total = nextjob.service;
4335 pj.remaining = nextjob.service;
4336 pj.parent = nextjob.parent;
4337 s.ps_jobs.push_back(pj);
4342 const RouteEntry re = draw_node_route(sn.station_to_node[i], job.cls);
4343 const bool to_sink = re.sink;
4344 const std::size_t dst = re.station, dcls = re.cls;
4358 if (has_immfeed && !to_sink && dst == i && s.role == Role::Queue && !s.ps && !s.pas &&
4359 ev.slot < s.nservers && dcls < sn.immfeed[i].size() && sn.immfeed[i][dcls]) {
4362 fed.t_sys = job.t_sys;
4363 fed.parent = job.parent;
4365 fed.priority = classprio[dcls];
4366 fed.service = slot_snap(s.svc[dcls].next_at(g_svc[i][dcls], now),
"service time");
4367 fed.remaining = fed.service;
4369 fed.rank = uniform01(g_routing);
4370 fed.deadline = now + classdeadline[dcls];
4373 acc.update_qlen(i, dcls, now);
4374 acc.qlen[i][dcls] += 1.0;
4375 bp.track(i, dcls, +1, now);
4376 start_service(i, ev.slot, fed);
4378 if (total_completions >= max_events)
break;
4397 if (has_sync_call && sync_reply[job.cls] < K &&
4398 !is_reply_signal[resolve_final_cls(re.node, dcls)] &&
4399 s.role == Role::Queue && !s.ps && !s.pas && ev.slot < s.nservers) {
4400 const std::uint64_t call = ++next_call;
4406 pending_reply[call] = pc;
4407 s.server_held[ev.slot] =
true;
4408 s.held_cls[ev.slot] = job.cls;
4409 acc.update_busy(i, job.cls, now);
4410 acc.busy[i][job.cls] += 1.0;
4413 moved.t_sys = job.t_sys;
4414 moved.parent = job.parent;
4416 deliver(re.node, moved, i);
4417 if (total_completions >= max_events)
break;
4426 if (!to_sink && dst < M && s.role == Role::Queue && !s.ps && ev.slot < s.nservers &&
4427 !dest_has_room(dst, dcls)) {
4432 s.server_blocked[ev.slot] =
true;
4433 s.blocked_job[ev.slot] = held;
4434 s.blocked_dest[ev.slot] = dst;
4435 s.blocked_dest_cls[ev.slot] = dcls;
4442 acc.update_qlen(dst, dcls, now);
4443 acc.qlen[dst][dcls] += 1.0;
4444 S[dst].blocked_at[dcls] += 1.0;
4450 acc.update_busy(i, job.cls, now);
4451 acc.busy[i][job.cls] += 1.0;
4452 blocked_count[i][job.cls] += 1.0;
4464 }
else if (s.role == Role::Queue && !s.ps && !s.pas && !s.server_blocked.empty() &&
4465 ev.slot < s.nservers && !s.server_blocked[ev.slot] &&
4466 !s.server_held[ev.slot] && buffer_has_for_slot(i, ev.slot)) {
4467 Job nextjob = buffer_pop_for_slot(i, ev.slot);
4468 start_service(i, ev.slot, nextjob);
4475 std::uint64_t spawn_parent = 0;
4476 if (has_spawn && spawn_of[job.cls] < K && spawn_joins_at[spawn_of[job.cls]]) {
4477 spawn_parent = job.parent;
4483 moved.t_sys = job.t_sys;
4484 moved.parent = job.parent;
4488 moved.call = job.call;
4489 deliver(re.node, moved, i);
4493 bool region_to_release =
false;
4494 std::size_t released_region = 0;
4495 if (region_of[i] >= 0) {
4496 const std::size_t rg =
static_cast<std::size_t
>(region_of[i]);
4502 const bool stays_inside = !to_sink && dst < M && region_of[dst] == region_of[i];
4503 if (!stays_inside) {
4504 regions[rg].update(now);
4505 regions[rg].leave(job.cls);
4506 regions[rg].completed[job.cls] += 1.0;
4507 region_to_release =
true;
4508 released_region = rg;
4516 if (has_spawn && spawn_of[job.cls] < K) {
4517 const std::size_t scls = spawn_of[job.cls];
4520 spawned.t_sys = now;
4525 spawned.parent = spawn_parent;
4526 admit(i, spawned, M);
4528 if (region_to_release) release_region(released_region);
4531 if (s.has_setup && s.setup_on) {
4533 for (std::size_t r = 0; r < K; ++r) held += acc.qlen[i][r];
4534 if (!(held > 0.0)) {
4536 s.delayoff_time.disabled() ? 0.0 : s.delayoff_time.next(g_aux[i]);
4537 s.delayoff_at = now + idle;
4539 e.t = s.delayoff_at;
4549 if (total_completions >= max_events)
break;
4550 if (cnvg.enabled() && (total_completions - last_cnvg_events) >= cnvg.interval()) {
4551 for (std::size_t a = 0; a < M; ++a) {
4552 if (S[a].ps) ps_advance(a);
4553 for (std::size_t r = 0; r < K; ++r) {
4554 acc.update_qlen(a, r, now);
4555 acc.update_busy(a, r, now);
4558 cnvg.finalize_batch(acc, servers_of, now);
4559 last_cnvg_events = total_completions;
4560 if (cnvg.converged(off_of)) {
4565 if ((total_completions - last_mser_events) >= mser_interval) {
4566 for (std::size_t a = 0; a < M; ++a) {
4567 if (S[a].ps) ps_advance(a);
4568 for (std::size_t r = 0; r < K; ++r) {
4569 acc.update_qlen(a, r, now);
4570 acc.update_busy(a, r, now);
4573 obs.collect(acc, now);
4574 last_mser_events = total_completions;
4579 for (std::size_t i = 0; i < M; ++i) {
4580 if (S[i].ps) ps_advance(i);
4581 for (std::size_t r = 0; r < K; ++r) {
4582 acc.update_qlen(i, r, now);
4583 acc.update_busy(i, r, now);
4588 const Truncation tr = obs.truncate();
4589 const double sim_time = now - tr.warmup_end;
4590 const double elapsed = tr.applied ? (now - obs.time[tr.index]) : sim_time;
4596 res.nchains = sn.nchains;
4597 for (std::size_t i = 0; i < M; ++i) res.station_names.push_back(sn.stations[i].name);
4598 for (std::size_t r = 0; r < K; ++r) res.class_names.push_back(sn.classes[r].name);
4599 res.QN = Matrix<double>(M, K, 0.0);
4600 res.UN = Matrix<double>(M, K, 0.0);
4601 res.RN = Matrix<double>(M, K, 0.0);
4602 res.TN = Matrix<double>(M, K, 0.0);
4603 res.CN = Matrix<double>(1, K, 0.0);
4604 res.XN = Matrix<double>(1, K, 0.0);
4605 res.AN = Matrix<double>(M, K, 0.0);
4606 res.WN = Matrix<double>(M, K, 0.0);
4608 for (std::size_t i = 0; i < M; ++i) {
4609 for (std::size_t r = 0; r < K; ++r) {
4610 if (S[i].role == Role::Source) {
4611 res.TN(i, r) = lambda[r];
4614 if (S[i].off[r])
continue;
4615 if (elapsed > 0.0) {
4616 const double q0 = tr.applied ? obs.qt[i][r][tr.index] : 0.0;
4617 const double b0 = tr.applied ? obs.bt[i][r][tr.index] : 0.0;
4618 const double c0 = tr.applied ? obs.cmp[i][r][tr.index] : 0.0;
4619 res.QN(i, r) = (acc.tot_qlen[i][r] - q0) / elapsed;
4620 res.TN(i, r) = (acc.completed[i][r] - c0) / elapsed;
4621 if (S[i].role == Role::Delay) {
4625 res.UN(i, r) = res.TN(i, r) * S[i].class_mean[r];
4626 }
else if (S[i].has_cd) {
4636 res.UN(i, r) = (S[i].util_peak > 0.0)
4637 ? res.TN(i, r) * S[i].class_mean[r] / S[i].util_peak
4640 res.UN(i, r) = (acc.tot_busy[i][r] - b0) / (elapsed * S[i].util_peak);
4643 if (acc.resp_cnt[i][r] > 0.0) res.RN(i, r) = acc.resp_sum[i][r] / acc.resp_cnt[i][r];
4649 if (elapsed > 0.0) res.AN(i, r) = acc.arrived[i][r] / elapsed;
4650 res.WN(i, r) = res.RN(i, r);
4658 if (!sn.fj.empty()) {
4659 res.DropRateJoin = Matrix<double>(M, K, 0.0);
4660 for (std::size_t i = 0; i < M; ++i)
4661 for (std::size_t r = 0; r < K; ++r) {
4662 const double d0 = tr.applied ? obs.drp[i][r][tr.index] : 0.0;
4664 res.DropRateJoin(i, r) = (acc.join_dropped[i][r] - d0) / elapsed;
4667 for (std::size_t r = 0; r < K; ++r) {
4668 if (sim_time > 0.0) res.XN(0, r) = sys_completed[r] / sim_time;
4669 if (sys_resp_cnt[r] > 0.0) res.CN(0, r) = sys_resp_sum[r] / sys_resp_cnt[r];
4673 for (std::size_t ti = 0; ti < bp.targets().size(); ++ti) {
4675 out.
name = bp.targets()[ti].name;
4676 out.stations = bp.targets()[ti].stations;
4677 out.job_class = bp.targets()[ti].job_class;
4678 for (std::size_t oi = 0; oi < static_cast<std::size_t>(bp.orders()); ++oi) {
4679 out.mean.push_back(bp.mean(ti, oi));
4680 out.count.push_back(bp.count(ti, oi));
4682 res.busy_periods.push_back(out);
4686 if (transient_run) {
4687 now = std::min(now, horizon);
4690 res.QNt.assign(M, std::vector<Matrix<double>>(K));
4691 res.UNt.assign(M, std::vector<Matrix<double>>(K));
4692 res.TNt.assign(M, std::vector<Matrix<double>>(K));
4693 for (std::size_t i = 0; i < M; ++i)
4694 for (std::size_t r = 0; r < K; ++r) {
4695 const std::size_t n = tran_times.size();
4698 Matrix<double> q(n, 2, 0.0), u(n, 2, 0.0), t(n, 2, 0.0);
4699 for (std::size_t k = 0; k < n; ++k) {
4700 q(k, 0) = tran_q[i][r][k];
4701 q(k, 1) = tran_times[k];
4702 u(k, 0) = tran_u[i][r][k];
4703 u(k, 1) = tran_times[k];
4704 t(k, 0) = tran_t[i][r][k];
4705 t(k, 1) = tran_times[k];
4711 res.stopping_reason =
"max_time";
4715 res.respTimeSamples.assign(M, std::vector<std::vector<double>>(K));
4716 for (std::size_t i = 0; i < M; ++i)
4717 for (std::size_t r = 0; r < K; ++r) res.respTimeSamples[i][r] = resp_samples[i][r];
4725 if (want_hist && !transient_run) hist_accumulate();
4726 if (!histogram.empty()) {
4727 res.histogram_space = Matrix<double>(histogram.size(), M * K, 0.0);
4728 res.histogram_time = Matrix<double>(histogram.size(), 1, 0.0);
4729 std::size_t row = 0;
4730 for (
const auto& kv : histogram) {
4731 for (std::size_t c = 0; c < kv.first.size(); ++c)
4732 res.histogram_space(row, c) = kv.first[c];
4733 res.histogram_time(row, 0) = kv.second;
4737 if (!trajectory.empty()) {
4738 res.traj_space = Matrix<double>(trajectory.size(), M * K, 0.0);
4739 res.traj_time = Matrix<double>(trajectory.size(), 1, 0.0);
4740 for (std::size_t k = 0; k < trajectory.size(); ++k) {
4741 for (std::size_t c = 0; c < trajectory[k].second.size(); ++c)
4742 res.traj_space(k, c) = trajectory[k].second[c];
4743 res.traj_time(k, 0) = trajectory[k].first;
4748 for (
const auto& kv : caches) {
4749 const CacheState& cs = kv.second;
4751 cm.
hit = Matrix<double>(1, K, 0.0);
4752 cm.miss = Matrix<double>(1, K, 0.0);
4757 if (cs.has_retrieval) cm.delayed = Matrix<double>(1, K, 0.0);
4758 for (std::size_t r = 0; r < K; ++r) {
4759 const double dl = cs.has_retrieval ? cs.delayed[r] : 0.0;
4760 const double tot = cs.hits[r] + cs.misses[r] + dl;
4762 cm.hit(0, r) = cs.hits[r] / tot;
4763 cm.miss(0, r) = cs.misses[r] / tot;
4764 if (cs.has_retrieval) cm.delayed(0, r) = dl / tot;
4783 const double released = std::accumulate(cs.delayed.begin(), cs.delayed.end(), 0.0);
4784 if (cs.has_retrieval && cs.completed_fetches + released > 0.0) {
4785 cm.latency = Matrix<double>(1, K, 0.0);
4786 const double mean_wait = (cs.total_fetch_time + cs.delayed_wait)
4787 / (cs.completed_fetches + released);
4788 for (std::size_t r = 0; r < K; ++r)
4789 cm.latency(0, r) = (cs.hits[r] + cs.misses[r] + cs.delayed[r] > 0.0)
4791 : std::numeric_limits<double>::quiet_NaN();
4793 res.cache_metrics[sn.nodes[cs.node - 1].name] = cm;
4797 if (!regions.empty()) {
4798 const std::size_t NR = regions.size();
4800 res.QNfcr = Matrix<double>(NR, K, 0.0);
4801 res.TNfcr = Matrix<double>(NR, K, 0.0);
4802 res.WeightNfcr = Matrix<double>(NR, K, 0.0);
4803 res.MemOccNfcr = Matrix<double>(NR, K, 0.0);
4804 res.DropRateNfcr = Matrix<double>(NR, K, 0.0);
4805 for (std::size_t g = 0; g < NR; ++g) {
4806 regions[g].update(now);
4807 for (std::size_t r = 0; r < K; ++r) {
4808 if (sim_time > 0.0) {
4812 res.QNfcr(g, r) = regions[g].tot_jobs[r] / sim_time;
4813 res.WeightNfcr(g, r) = regions[g].tot_weight[r] / sim_time;
4814 res.MemOccNfcr(g, r) = regions[g].tot_mem[r] / sim_time;
4815 res.TNfcr(g, r) = regions[g].completed[r] / sim_time;
4816 res.DropRateNfcr(g, r) = regions[g].dropped[r] / sim_time;
4827 res.total_simulated_events =
static_cast<long long>(total_completions);
4832 double any_balk = 0.0, any_renege = 0.0;
4833 for (std::size_t i = 0; i < M; ++i)
4834 for (std::size_t r = 0; r < K; ++r) {
4835 any_balk += balked[i][r];
4836 any_renege += reneged[i][r];
4838 double any_orbit = 0.0;
4839 for (std::size_t i = 0; i < M; ++i)
4840 for (std::size_t r = 0; r < K; ++r) any_orbit += S[i].retried[r];
4841 if (any_orbit > 0.0) {
4842 res.retriedCustomers = Matrix<double>(M, K, 0.0);
4843 res.retrialDropped = Matrix<double>(M, K, 0.0);
4844 res.avgOrbitSize = Matrix<double>(M, K, 0.0);
4845 for (std::size_t i = 0; i < M; ++i) {
4846 const double dt = now - S[i].orbit_last;
4847 for (std::size_t r = 0; r < K; ++r) {
4848 double tot = S[i].tot_orbit[r];
4849 if (dt > 0.0) tot += S[i].orbit_size[r] * dt;
4850 res.retriedCustomers(i, r) = S[i].retried[r];
4851 res.retrialDropped(i, r) = S[i].retrial_lost[r];
4852 if (sim_time > 0.0) res.avgOrbitSize(i, r) = tot / sim_time;
4857 if (any_balk > 0.0 || any_renege > 0.0) {
4858 res.balkedCustomers = Matrix<double>(M, K, 0.0);
4859 res.renegedCustomers = Matrix<double>(M, K, 0.0);
4860 res.renegingRate = Matrix<double>(M, K, 0.0);
4861 for (std::size_t i = 0; i < M; ++i)
4862 for (std::size_t r = 0; r < K; ++r) {
4863 res.balkedCustomers(i, r) = balked[i][r];
4864 res.renegedCustomers(i, r) = reneged[i][r];
4865 if (sim_time > 0.0) res.renegingRate(i, r) = reneged[i][r] / sim_time;
4869 res.converged = converged;
4870 res.convergence_batches = cnvg.batches();
4871 if (!transient_run) res.stopping_reason = converged ?
"convergence" :
"max_events";
4872 res.engine =
"native";
4873 res.method = o.method;