5#ifndef LINE_LANG_QN_NETWORK_BUILDER_H
6#define LINE_LANG_QN_NETWORK_BUILDER_H
62 void set(std::size_t r, std::size_t s, std::size_t i, std::size_t j,
const T& p) {
65 void set(std::size_t i, std::size_t j,
const T& p) {
set(1, 1, i, j, p); }
67 T
get(std::size_t r, std::size_t s, std::size_t i, std::size_t j)
const {
74 Key(std::size_t r_, std::size_t s_, std::size_t i_, std::size_t j_)
75 :
r(r_),
s(s_),
i(i_),
j(j_) {}
77 if (
r != o.
r)
return r < o.
r;
78 if (
s != o.
s)
return s < o.
s;
79 if (
i != o.
i)
return i < o.
i;
98 explicit Network(
const std::string& nm) { sn_.name = nm; }
120 st.
nodetype = (sched == SchedStrategy::INF) ? NodeType::Delay : NodeType::Queue;
122 st.
nservers = (sched == SchedStrategy::INF)
123 ? std::numeric_limits<double>::infinity()
125 const std::size_t ist = sn_.add_station(st);
126 init_node(sn_.station_to_node[ist - 1]);
127 return sn_.station_to_node[ist - 1];
135 st.
sched = SchedStrategy::INF;
136 st.
nservers = std::numeric_limits<double>::infinity();
137 const std::size_t ist = sn_.add_station(st);
138 init_node(sn_.station_to_node[ist - 1]);
139 return sn_.station_to_node[ist - 1];
149 if (sn_.sourceIdx != 0)
throw InputError(
"Network: the model already has a Source");
153 st.
sched = SchedStrategy::EXT;
155 const std::size_t ist = sn_.add_station(st);
157 init_node(sn_.station_to_node[ist - 1]);
158 return sn_.station_to_node[ist - 1];
163 if (sn_.sinkNode != 0)
throw InputError(
"Network: the model already has a Sink");
164 const std::size_t nd = sn_.add_node(nm, NodeType::Sink,
false);
172 const std::size_t nd = sn_.add_node(nm, NodeType::Router,
false);
186 std::size_t
add_logger(
const std::string& nm,
const std::string& log_file = std::string()) {
187 const std::size_t nd = sn_.add_node(nm, NodeType::Logger,
false);
191 std::string base = log_file;
192 const std::size_t slash = base.find_last_of(
'/');
193 if (slash != std::string::npos) base = base.substr(slash + 1);
194 sn_.nodes[nd - 1].logger.file_name = base;
208 if (C.
rows() != sn_.classes.size() || C.
cols() != sn_.classes.size())
210 "': the matrix must be (nclasses x nclasses); declare the classes "
212 const std::size_t nd = sn_.add_node(nm, NodeType::ClassSwitch,
false);
213 sn_.csmatrix[nd] = C;
234 const std::size_t nd = sn_.add_node(nm, NodeType::ClassSwitch,
false);
242 if (node == 0 || node > sn_.nodes.size())
243 throw InputError(
"set_class_switch_matrix: node index is out of range");
244 if (sn_.nodes[node - 1].nodetype != NodeType::ClassSwitch)
245 throw InputError(
"set_class_switch_matrix: node '" + sn_.nodes[node - 1].name +
246 "' is not a ClassSwitch");
247 if (C.
rows() != sn_.classes.size() || C.
cols() != sn_.classes.size())
248 throw InputError(
"set_class_switch_matrix: the matrix of '" +
249 sn_.nodes[node - 1].name +
250 "' must be (nclasses x nclasses)");
251 sn_.csmatrix[node] = C;
256 std::size_t
add_fork(
const std::string& nm,
double tasks_per_link = 1.0) {
257 const std::size_t nd = sn_.add_node(nm, NodeType::Fork,
false);
258 sn_.nodes[nd - 1].tasks_per_link = tasks_per_link;
285 double tasks, std::size_t dest_node = 0) {
287 ov.kind = ForkOverride::TASKS;
292 record_fork_override(ov);
298 std::size_t dest_node = 0) {
300 throw InputError(
"set_fork_tasks_per_link_dist: the jobs-per-link "
301 "distribution must be a DiscreteSampler");
303 ov.kind = ForkOverride::DIST;
308 record_fork_override(ov);
313 std::size_t dest_node,
double prob) {
314 if (prob < 0.0 || prob > 1.0)
315 throw InputError(
"set_fork_branch_probability: a branch activation "
316 "probability must lie in [0,1]");
318 ov.kind = ForkOverride::PROB;
323 record_fork_override(ov);
330 std::size_t
add_join(
const std::string& nm, std::size_t fork_node) {
348 st.
sched = SchedStrategy::INF;
349 st.
nservers = std::numeric_limits<double>::infinity();
350 const std::size_t ist = sn_.add_station(st);
351 const std::size_t nd = sn_.station_to_node[ist - 1];
357 void bind_join(std::size_t join_node, std::size_t fork_node) {
358 if (join_node == 0 || join_node > sn_.nodes.size() ||
359 sn_.nodes[join_node - 1].nodetype != NodeType::Join)
360 throw InputError(
"bind_join: the node being bound is not a Join");
361 if (fork_node == 0 || fork_node > sn_.nodes.size() ||
362 sn_.nodes[fork_node - 1].nodetype != NodeType::Fork)
363 throw InputError(
"Join '" + sn_.nodes[join_node - 1].name +
364 "': the node it closes is not a Fork");
365 sn_.fj.emplace_back(fork_node, join_node);
377 st.
sched = SchedStrategy::INF;
378 st.
nservers = std::numeric_limits<double>::infinity();
379 const std::size_t ist = sn_.add_station(st);
380 init_node(sn_.station_to_node[ist - 1]);
381 return sn_.station_to_node[ist - 1];
395 throw InputError(
"add_transition: a transition needs at least one mode");
398 "add_transition: enabling and firing must have one entry per mode");
399 const std::size_t nd = sn_.add_node(nm, NodeType::Transition,
true);
400 sn_.transparam[nd] = par;
412 int max_attempts = 0) {
413 if (node == 0 || node > sn_.nodes.size())
414 throw InputError(
"set_retrial: node index is out of range");
415 const std::size_t ist = sn_.nodes[node - 1].station;
416 if (ist == 0)
throw InputError(
"set_retrial: node is not a station");
421 const std::size_t K = sn_.classes.size();
422 if (cls == 0 || cls > K)
423 throw InputError(
"set_retrial: class index is out of range");
441 Station<T>& st = station_ref(node, cls,
"set_patience");
450 Station<T>& st = station_ref(node, cls,
"set_orbit_impatience");
457 Station<T>& st = station_ref(node, cls,
"set_batch_reject");
469 Station<T>& st = station_ref(node, cls,
"set_balking");
471 st.
balking[cls - 1].strategy = strategy;
472 st.
balking[cls - 1].thresholds = thresholds;
477 const std::size_t ist = station_of(node,
"add_server_type");
478 sn_.stations[ist - 1].server_types.push_back(stype);
481 for (
const auto& pool : sn_.stations[ist - 1].server_types) total += pool.count;
482 sn_.stations[ist - 1].nservers = total;
491 Station<T>& st = station_ref(node, cls,
"set_server_parallelism");
493 throw InputError(
"set_server_parallelism: parallelism must be a positive integer");
497 "set_server_parallelism: parallelism " + std::to_string(n) +
" exceeds the " +
498 std::to_string(
static_cast<long long>(st.
nservers)) +
" servers of station '" +
499 sn_.nodes[node - 1].name +
"', so a job of this class could never enter service");
507 sn_.stations[station_of(node,
"set_hetero_sched_policy") - 1].hetero_policy = policy;
515 Station<T>& st = station_ref(node, cls,
"set_arrival_batch");
522 sn_.stations[station_of(node,
"set_marked_classes") - 1].marked_classes = classes;
528 Station<T>& st = station_ref(node, cls,
"set_departure_discipline");
535 if (node == 0 || node > sn_.nodes.size())
536 throw InputError(
"set_initial_marking: node index is out of range");
537 if (sn_.nodes[node - 1].nodetype != NodeType::Place)
538 throw InputError(
"set_initial_marking: only a Place carries an initial marking");
539 sn_.initmarking[node] = tokens;
548 if (node == 0 || node > sn_.nodes.size())
549 throw InputError(
"set_state_prior: node index is out of range");
550 if (space.
rows() != prior.size())
552 "set_state_prior: the prior has one entry per ROW of the declared state space");
553 sn_.statespace[node] = space;
554 sn_.stateprior[node] = prior;
559 if (node == 0 || node > sn_.nodes.size())
560 throw InputError(
"set_join_strategy: node index is out of range");
561 if (sn_.nodes[node - 1].nodetype != NodeType::Join)
562 throw InputError(
"set_join_strategy: the node is not a Join");
566 sn_.joindecl[node] = jd;
571 const std::map<std::size_t, double>& weights) {
572 if (node == 0 || node > sn_.nodes.size())
573 throw InputError(
"set_routing_weights: node index is out of range");
574 std::vector<std::map<std::size_t, double>>& rw = sn_.nodes[node - 1].routing_weights;
575 if (rw.size() < sn_.classes.size()) rw.resize(sn_.classes.size());
576 if (cls == 0 || cls > rw.size())
577 throw InputError(
"set_routing_weights: class index is out of range");
578 rw[cls - 1] = weights;
583 if (node == 0 || node > sn_.nodes.size())
584 throw InputError(
"set_routing_param: node index is out of range");
585 std::vector<int>& rp = sn_.nodes[node - 1].routing_param;
586 if (rp.size() < sn_.classes.size()) rp.resize(sn_.classes.size(), 0);
587 if (cls == 0 || cls > rp.size())
588 throw InputError(
"set_routing_param: class index is out of range");
600 const std::size_t ist = station_of(node,
"set_setup_delayoff");
601 const std::size_t K = sn_.classes.size();
602 if (cls == 0 || cls > K)
603 throw InputError(
"set_setup_delayoff: class index is out of range");
605 if (sp.
setup.size() < K) {
609 sp.
setup[cls - 1] = setup;
629 const std::size_t ist = station_of(node,
"set_breakdown");
632 "set_breakdown: both a failure and a repair time are required; a server that "
633 "never recovers is an absorbing model, not a breakdown");
636 if (!(fm > 0.0) || !(rm > 0.0))
637 throw InputError(
"set_breakdown: the failure and repair times must have positive means");
638 const std::size_t K = sn_.classes.size();
645 for (std::size_t r = 0; r < K && r < down_service.size(); ++r) {
650 "set_breakdown: station '" + sn_.stations[ist - 1].name +
651 "': the down-server service distribution must be exponential; a phase-type "
652 "degraded service would need its own phase block in the joint chain");
660 const std::size_t nd = sn_.add_node(nm, NodeType::Cache,
true);
661 sn_.nodeparam[nd] = par;
677 const std::vector<std::size_t>& read_classes,
678 const std::vector<std::size_t>& hit_classes) {
679 auto it = sn_.nodeparam.find(cache_node);
680 if (it == sn_.nodeparam.end())
681 throw InputError(
"setItemReadClasses: node is not a Cache");
683 const std::size_t nitems = cp.
nitems;
684 if (read_classes.size() != nitems)
685 throw InputError(
"setItemReadClasses: pass exactly one read class per item");
686 const std::vector<std::size_t> hit =
687 per_item_classes(hit_classes, nitems,
"setItemReadClasses hit");
688 const std::size_t K = sn_.classes.size();
694 for (std::size_t i = 0; i < nitems; ++i) {
697 cp.
pread[read_classes[i] - 1] = onehot;
698 cp.
hitclass[read_classes[i] - 1] = hit[i];
699 cp.
classitem[read_classes[i] - 1] = i + 1;
701 cache_item_classes_[cache_node] = read_classes;
711 std::vector<std::size_t>
set_miss_cache(std::size_t cache_node, std::size_t next_cache,
712 const std::vector<std::size_t>& hit_classes_at_next) {
713 auto it = sn_.nodeparam.find(cache_node);
714 auto itn = sn_.nodeparam.find(next_cache);
715 if (it == sn_.nodeparam.end() || itn == sn_.nodeparam.end())
716 throw InputError(
"setMissCache: both nodes must be Caches");
717 if (it->second.nitems != itn->second.nitems)
718 throw InputError(
"setMissCache: a cache network requires one common item set");
719 auto self_it = cache_item_classes_.find(cache_node);
720 if (self_it == cache_item_classes_.end())
721 throw InputError(
"setMissCache: call setItemReadClasses on the source cache first");
722 const std::vector<std::size_t> self_cls = self_it->second;
723 const std::size_t nitems = it->second.nitems;
724 const std::vector<std::size_t> hit =
725 per_item_classes(hit_classes_at_next, nitems,
"setMissCache hit");
727 std::vector<std::size_t> minted;
728 minted.reserve(nitems);
729 const bool closed = sn_.classes[self_cls[0] - 1].type == JobClassType::CLOSED;
730 const std::size_t refstat = sn_.classes[self_cls[0] - 1].refstat;
731 for (std::size_t i = 0; i < nitems; ++i) {
734 cls =
add_closed_class(sn_.nodes[next_cache - 1].name +
"_item" + std::to_string(i + 1),
735 0.0, sn_.station_to_node[refstat - 1]);
737 cls =
add_open_class(sn_.nodes[next_cache - 1].name +
"_item" + std::to_string(i + 1));
738 minted.push_back(cls);
741 const std::size_t K = sn_.classes.size();
742 for (std::size_t nd : {cache_node, next_cache}) {
752 for (std::size_t i = 0; i < nitems; ++i) {
755 dst.
pread[minted[i] - 1] = onehot;
756 dst.
hitclass[minted[i] - 1] = hit[i];
758 src.
missclass[self_cls[i] - 1] = minted[i];
762 cache_miss_arcs_.push_back(CacheMissArc(minted[i], cache_node, next_cache));
764 cache_item_classes_[next_cache] = minted;
774 auto it = sn_.nodeparam.find(cache_node);
775 if (it == sn_.nodeparam.end())
776 throw InputError(
"setItemMissClass: node is not a Cache");
777 auto self_it = cache_item_classes_.find(cache_node);
778 if (self_it == cache_item_classes_.end())
779 throw InputError(
"setItemMissClass: the cache has no per-item classes");
780 const std::vector<std::size_t>& self_cls = self_it->second;
781 const std::vector<std::size_t> miss =
782 per_item_classes(miss_classes, self_cls.size(),
"setItemMissClass miss");
784 cp.
missclass.resize(sn_.classes.size(), 0);
785 for (std::size_t i = 0; i < self_cls.size(); ++i)
801 std::size_t miss_class,
const std::vector<std::size_t>& queue_nodes) {
802 auto it = sn_.nodeparam.find(cache_node);
803 if (it == sn_.nodeparam.end())
804 throw InputError(
"setRetrievalSystem: node is not a Cache");
806 if (queue_nodes.empty())
807 throw InputError(
"setRetrievalSystem: the retrieval system has no stations");
808 const std::size_t nitems = cp.
nitems;
810 for (
int c : cp.
itemcap) totalcap += c;
815 std::vector<Distrib<T> > svc;
816 svc.reserve(queue_nodes.size());
817 for (std::size_t q : queue_nodes) {
818 const std::size_t st = station_of(q,
"setRetrievalSystem");
819 svc.push_back(sn_.service[st - 1][read_class - 1]);
822 cp.
retrieval_classes.assign(nitems, std::vector<std::size_t>(sn_.classes.size(), 0));
823 const bool closed = sn_.classes[read_class - 1].type == JobClassType::CLOSED;
824 const std::size_t refstat = sn_.classes[read_class - 1].refstat;
825 for (std::size_t i = 0; i < nitems; ++i) {
828 rc =
add_closed_class(sn_.classes[read_class - 1].name +
"_retrievalClass_" +
829 std::to_string(i + 1),
830 0.0, sn_.station_to_node[refstat - 1]);
832 rc =
add_open_class(sn_.classes[read_class - 1].name +
"_retrievalClass_" +
833 std::to_string(i + 1));
834 for (std::size_t s = 0; s < queue_nodes.size(); ++s) {
840 for (std::size_t k = 0; k < nitems; ++k)
847 const std::size_t K = sn_.classes.size();
854 cp.
pread[rc - 1] = onehot;
864 std::size_t
add_closed_class(
const std::string& nm,
double njobs, std::size_t refstat_node,
866 if (!(njobs >= 0.0) || std::isinf(njobs))
867 throw InputError(
"ClosedClass '" + nm +
"': the population must be finite");
870 cl.
type = JobClassType::CLOSED;
872 cl.
refstat = station_of(refstat_node,
"ClosedClass '" + nm +
"'");
874 const std::size_t r = sn_.add_class(cl);
875 grow_class_vectors();
884 if (sn_.sourceIdx == 0)
886 "': the model has no Source to reference; add one first");
889 cl.
type = JobClassType::OPEN;
890 cl.
population = std::numeric_limits<double>::infinity();
893 const std::size_t r = sn_.add_class(cl);
894 grow_class_vectors();
907 std::size_t refstat_node,
int prio = 0) {
909 sn_.classes[r - 1].self_looping =
true;
915 class_ref(cls,
"set_reference_class").is_ref_class =
true;
920 class_ref(cls,
"set_class_deadline").deadline = due;
928 const std::size_t K = sn_.classes.size();
929 if (spawn_cls == 0 || spawn_cls > K)
930 throw InputError(
"set_class_spawn: the spawned class index is out of range");
931 class_ref(cls,
"set_class_spawn").spawn = spawn_cls;
945 const std::size_t K = sn_.classes.size();
946 if (cls == 0 || cls > K)
throw InputError(
"set_class_patience: class index out of range");
947 for (std::size_t ist = 0; ist < sn_.stations.size(); ++ist) {
948 const std::size_t ind = sn_.station_to_node[ist];
954 if (!st.
patience[cls - 1].disabled)
continue;
972 const std::size_t K = sn_.classes.size();
973 if (call_cls == 0 || call_cls > K || reply_cls == 0 || reply_cls > K)
974 throw InputError(
"set_reply_signal_class: class index is out of range");
975 if (sn_.syncreply.size() < K) sn_.syncreply.assign(K, 0);
976 sn_.syncreply[call_cls - 1] = reply_cls;
998 sn_.set_service(station_of(node,
"setService"), cls, dd);
1003 const std::size_t ist = station_of(node,
"setArrival");
1004 if (sn_.stations[ist - 1].nodetype != NodeType::Source)
1005 throw InputError(
"setArrival: node '" + sn_.nodes[node - 1].name +
"' is not a Source");
1011 sn_.set_service(ist, cls, dd);
1025 const std::size_t ist = station_of(node,
"setNumberOfServers");
1026 if (sn_.stations[ist - 1].sched == SchedStrategy::INF)
return;
1027 if (!(n >= 1.0))
throw InputError(
"setNumberOfServers: the server count must be >= 1");
1028 sn_.stations[ist - 1].nservers = n;
1031 if (std::isinf(n) && sn_.stations[ist - 1].nodetype == NodeType::Queue)
1032 sn_.stations[ist - 1].nodetype = NodeType::Delay;
1037 sn_.stations[station_of(node,
"setCapacity") - 1].cap = k;
1042 Station<T>& st = sn_.stations[station_of(node,
"setChainCapacity") - 1];
1043 st.
classcap.resize(sn_.classes.size(), std::numeric_limits<double>::infinity());
1056 Station<T>& st = sn_.stations[station_of(node,
"setImmediateFeedback") - 1];
1057 st.
immfeed.resize(sn_.classes.size(),
false);
1063 sn_.classes[cls - 1].immfeed =
true;
1068 Station<T>& st = sn_.stations[station_of(node,
"setDropRule") - 1];
1069 st.
droprule.resize(sn_.classes.size(), 0);
1070 st.
droprule[cls - 1] =
static_cast<int>(rule);
1089 std::size_t node,
const std::function<T(
const std::vector<std::size_t>&)>& muFun,
1091 const std::size_t ist = station_of(node,
"setServiceRateFunction");
1093 if (st.
sched != SchedStrategy::PAS && st.
sched != SchedStrategy::OI)
1095 "setServiceRateFunction is only applicable to PAS (pass-and-swap) and OI "
1096 "(order-independent) queues");
1097 if (!muFun)
throw InputError(
"setServiceRateFunction: the rate function is empty");
1108 for (std::size_t r = 1; r <= sn_.classes.size(); ++r) {
1109 const T rate_r = muFun(std::vector<std::size_t>{r});
1122 Station<T>& st = sn_.stations[station_of(node,
"setPollingType") - 1];
1123 if (st.
sched != SchedStrategy::POLLING)
1124 throw InputError(
"setPollingType is only applicable to a POLLING station");
1126 throw InputError(
"K-limited polling requires a parameter K >= 1");
1133 Station<T>& st = sn_.stations[station_of(node,
"setSwitchover") - 1];
1134 if (st.
sched != SchedStrategy::POLLING)
1135 throw InputError(
"setSwitchover is only applicable to a POLLING station");
1142 Station<T>& st = sn_.stations[station_of(node,
"setSchedParam") - 1];
1153 if (alpha.empty())
throw InputError(
"setLoadDependence: the scaling vector is empty");
1154 sn_.stations[station_of(node,
"setLoadDependence") - 1].lldscaling = alpha;
1175 const std::vector<T>& peak = std::vector<T>()) {
1176 Station<T>& st = sn_.stations[station_of(node,
"setClassDependence") - 1];
1177 const std::size_t K = sn_.classes.size();
1181 else if (peak.size() == 1)
1183 else if (peak.size() == K)
1187 "setClassDependence: peakRatePerClass must be a scalar or a vector of length "
1201 const std::vector<T>& peak) {
1202 Station<T>& st = sn_.stations[station_of(node,
"setJointDependence") - 1];
1203 const std::size_t K = sn_.classes.size();
1206 "setJointDependence: joint dependence requires an explicit peak rate; pass a "
1207 "scalar (identical peak for every class) or a per-class vector");
1209 if (peak.size() == 1)
1211 else if (peak.size() == K)
1215 "setJointDependence: peakRatePerClass must be a scalar or a vector of length "
1248 throw InputError(
"setGlobalDependence: the scaling must be a callable");
1249 const std::size_t M = sn_.stations.size(), K = sn_.classes.size();
1252 "setGlobalDependence: a global dependence requires an explicit peak rate; pass a "
1253 "scalar, one entry per station, or one entry per (station, class)");
1254 for (std::size_t i = 0; i < peak.size(); ++i)
1256 throw InputError(
"setGlobalDependence: peak must be positive");
1259 for (
int probe = 0; probe < 2; ++probe) {
1261 const std::vector<T> v = fun(n);
1262 if (v.size() != 1 && v.size() != M && v.size() != M * K)
1264 "setGlobalDependence: the handle must return a scalar, one entry per station, "
1265 "or one entry per (station, class)");
1266 for (std::size_t j = 0; j < v.size(); ++j)
1269 "setGlobalDependence: the handle must return finite nonnegative scalings");
1271 if (wire_cutoff < 1)
1272 throw InputError(
"setGlobalDependence: wireCutoff must be a positive integer");
1273 sn_.gdscaling = fun;
1274 sn_.gdscalingcutoff = wire_cutoff;
1275 if (peak.size() == 1)
1276 sn_.gdscalingpeak.assign(M * K, peak[0]);
1277 else if (peak.size() == M) {
1279 for (std::size_t i = 0; i < M; ++i)
1280 for (std::size_t r = 0; r < K; ++r) sn_.gdscalingpeak[i * K + r] = peak[i];
1281 }
else if (peak.size() == M * K)
1282 sn_.gdscalingpeak = peak;
1285 "setGlobalDependence: peak must be a scalar, one entry per station, or one entry "
1286 "per (station, class)");
1291 NodeDef& nd = sn_.nodes[node - 1];
1292 nd.
routing.resize(sn_.classes.size(), RoutingStrategy::PROB);
1320 const std::vector<std::vector<std::size_t>>& branches,
1321 const std::vector<std::size_t>& level,
1323 std::size_t cls = 0) {
1324 if (branches.size() < 2 || !branches[0].empty())
1325 throw InputError(
"set_state_dep_routing: branches[0] must be empty, branch index 1 "
1326 "denotes the complement M-V");
1327 const std::size_t B = branches.size();
1328 if (level.size() != B)
1329 throw InputError(
"set_state_dep_routing: level must have one entry per branch index, "
1330 "including the unused index 0");
1332 nodesdr.
entry = entry - 1;
1334 nodesdr.
branch.assign(B, std::vector<std::size_t>());
1337 for (std::size_t b = 1; b < B; ++b) {
1338 if (branches[b].empty())
1339 throw InputError(
"set_state_dep_routing: branch " + std::to_string(b + 1) +
1341 for (std::size_t k = 0; k < branches[b].size(); ++k)
1342 nodesdr.
branch[b].push_back(branches[b][k] - 1);
1343 nodesdr.
entryOf[b] = branches[b].front() - 1;
1346 nodesdr.
level = level;
1353 std::vector<std::size_t> node_to_station(sn_.nodes.size(), 0);
1354 std::vector<bool> is_station(sn_.nodes.size(),
false);
1355 for (std::size_t k = 0; k < sn_.station_to_node.size(); ++k) {
1356 node_to_station[sn_.station_to_node[k] - 1] = k;
1357 is_station[sn_.station_to_node[k] - 1] =
true;
1360 const std::vector<std::size_t>& n2s;
1361 const std::vector<bool>& isst;
1363 std::size_t operator()(std::size_t nd)
const {
1364 if (nd >= isst.size() || !isst[nd])
1365 throw InputError(
"set_state_dep_routing: node '" +
sn.nodes[nd].name +
1366 "' takes part in state-dependent routing but is not a "
1367 "station: the product form is over queue lengths, and a "
1368 "stateless node holds none");
1371 } to_station{node_to_station, is_station, sn_};
1376 for (std::size_t b = 1; b < B; ++b) {
1377 for (std::size_t k = 0; k < nodesdr.
branch[b].size(); ++k)
1384 sn_.sdr_nodes = nodesdr;
1386 const std::size_t K = sn_.classes.size();
1388 for (std::size_t r = 1; r <= K; ++r)
set_routing(entry, r, RoutingStrategy::SDR);
1407 for (
const auto& kv : Pm.
entries) {
1408 const auto& k = kv.first;
1409 if (k.r == 0 || k.r > sn_.classes.size() || k.s == 0 || k.s > sn_.classes.size())
1410 throw InputError(
"link: the routing matrix names a class that does not exist");
1411 if (k.i == 0 || k.i > sn_.nodes.size() || k.j == 0 || k.j > sn_.nodes.size())
1412 throw InputError(
"link: the routing matrix names a node that does not exist");
1426 const std::size_t K = sn_.classes.size();
1427 const std::size_t I = sn_.nodes.size();
1433 for (std::size_t a = 0; a < cache_miss_arcs_.size(); ++a) {
1434 const CacheMissArc&
mc = cache_miss_arcs_[a];
1435 P.
set(
mc.cls,
mc.cls,
mc.from_node,
mc.to_node, one);
1437 std::map<std::pair<std::size_t, std::size_t>, std::size_t> csid;
1438 for (std::size_t i = 1; i <= I; ++i)
1439 for (std::size_t j = 1; j <= I; ++j) {
1442 for (std::size_t r = 1; r <= K; ++r)
1443 for (std::size_t s = 1; s <= K; ++s) {
1444 const T p = P.
get(r, s, i, j);
1446 C(r - 1, s - 1) = p;
1451 bool offdiag =
false;
1452 for (std::size_t r = 1; r <= K; ++r) {
1454 for (std::size_t s = 1; s <= K; ++s) S += C(r - 1, s - 1);
1456 for (std::size_t s = 1; s <= K; ++s) C(r - 1, s - 1) = T(C(r - 1, s - 1) / S);
1458 C(r - 1, r - 1) = one;
1460 for (std::size_t s = 1; s <= K; ++s)
1463 if (!offdiag)
continue;
1479 if (sn_.nodes[i - 1].nodetype == NodeType::Sink &&
1480 sn_.nodes[j - 1].nodetype == NodeType::Source)
1482 csid[std::make_pair(i, j)] =
1483 add_class_switch(
"CS_" + sn_.nodes[i - 1].name +
"_to_" + sn_.nodes[j - 1].name, C);
1488 for (
const auto& kv : csid) {
1489 const std::size_t i = kv.first.first, j = kv.first.second, c = kv.second;
1490 for (std::size_t r = 1; r <= K; ++r)
1491 for (std::size_t s = 1; s <= K; ++s) {
1492 const T p = P.
get(r, s, i, j);
1494 P.
set(r, r, i, c, T(P.
get(r, r, i, c) + p));
1495 P.
set(r, s, i, j, zero);
1496 P.
set(s, s, c, j, one);
1499 for (
const auto& kv : P.
entries) {
1500 const auto& k = kv.first;
1501 sn_.set_route(k.r, k.s, k.i, k.j, kv.second);
1507 for (
const auto& np : sn_.nodeparam) {
1508 const std::size_t ci = np.first;
1512 const std::size_t rd = rq.first + 1;
1513 std::vector<std::size_t> nodeset = rq.second;
1514 nodeset.push_back(ci);
1515 bool minted =
false;
1516 for (std::size_t i = 0; i < cp.
nitems; ++i) {
1518 if (rc == 0)
continue;
1520 for (std::size_t a : nodeset)
1521 for (std::size_t b : nodeset) {
1522 const T p = Pm.
get(rd, rd, a, b);
1538 for (std::size_t a : nodeset)
1539 for (std::size_t b : nodeset)
1568 const std::size_t Kc = sn_.classes.size(), Ic = sn_.nodes.size();
1569 for (std::size_t i = 1; i <= Ic; ++i) {
1570 NodeDef& nd = sn_.nodes[i - 1];
1571 nd.
routing.resize(Kc, RoutingStrategy::PROB);
1572 bool linked =
false;
1573 for (std::size_t j = 1; j <= Ic && !linked; ++j)
1574 for (std::size_t a = 1; a <= Kc && !linked; ++a)
1575 for (std::size_t b = 1; b <= Kc && !linked; ++b)
1577 if (!linked)
continue;
1578 for (std::size_t r = 1; r <= Kc; ++r) {
1579 if (nd.
routing[r - 1] != RoutingStrategy::PROB)
continue;
1580 if (sn_.classes[r - 1].self_looping)
continue;
1581 if (r <= sn_.issignal.size() && sn_.issignal[r - 1])
continue;
1582 bool routed =
false;
1583 for (std::size_t j = 1; j <= Ic && !routed; ++j)
1584 for (std::size_t s = 1; s <= Kc && !routed; ++s)
1586 if (!routed) nd.
routing[r - 1] = RoutingStrategy::RAND;
1594 apply_fork_overrides();
1603 if (routing_installed()) apply_fork_overrides();
1605 sn_.refresh_struct();
1625 const std::function<T(
const std::vector<std::size_t>&)>& mu,
1626 const std::vector<std::vector<bool>>& swap_graph =
1627 std::vector<std::vector<bool>>()) {
1628 const std::size_t ist = station_of(node,
"set_pas");
1633 pp.
swap_graph.assign(sn_.classes.size(), std::vector<bool>(sn_.classes.size(),
false));
1634 sn_.pasparam[ist] = pp;
1640 for (std::size_t a = 0; a < R; ++a)
1641 for (std::size_t b = 0; b < pp.
swap_graph[a].size(); ++b)
1652 const std::function<T(
const std::vector<std::size_t>&)>& mu,
1656 const std::size_t R = sn_.classes.size();
1657 pp.
swap_graph.assign(R, std::vector<bool>(R,
false));
1658 for (std::size_t a = 0; a < R && a < swap_graph.
rows(); ++a)
1659 for (std::size_t b = 0; b < R && b < swap_graph.
cols(); ++b)
1661 sn_.pasparam[ist] = pp;
1673 std::size_t pk = 1) {
1674 const std::size_t ist = station_of(node,
"set_polling");
1675 if (sn_.stations[ist - 1].sched != SchedStrategy::POLLING)
1676 throw InputError(
"set_polling: the station is not POLLING-scheduled");
1681 if (pp.
switchover.size() < sn_.classes.size())
1683 sn_.pollingparam[ist] = pp;
1698 const std::size_t K = sn_.classes.size();
1699 if (node == 0 || node > sn_.nodes.size())
1700 throw InputError(
"set_sync_reply: node index is out of range");
1701 if (call_cls == 0 || call_cls > K || reply_cls == 0 || reply_cls > K)
1702 throw InputError(
"set_sync_reply: class index is out of range");
1703 const std::size_t ist = sn_.nodes[node - 1].station;
1704 if (ist == 0)
throw InputError(
"set_sync_reply: node is not a station");
1705 if (sn_.stations[ist - 1].sched != SchedStrategy::FCFS)
1707 "set_sync_reply: synchronous calls are supported only at FCFS stations; "
1708 "holding a server across a call has no representation in the state of the "
1709 "other disciplines");
1710 if (sn_.replyblock.size() < sn_.nodes.size())
1711 sn_.replyblock.assign(sn_.nodes.size(), std::vector<bool>(K,
false));
1712 if (sn_.syncreply.size() < K) sn_.syncreply.assign(K, 0);
1713 sn_.replyblock[node - 1][call_cls - 1] =
true;
1714 sn_.syncreply[call_cls - 1] = reply_cls;
1737 const std::vector<double>& class_max_jobs,
1738 double global_max_jobs = -1.0,
1739 const std::vector<DropStrategy>& rule = std::vector<DropStrategy>(),
1740 const std::vector<double>& class_max_memory = std::vector<double>(),
1741 const std::vector<T>& class_size = std::vector<T>(),
1742 double global_max_memory = -1.0,
1743 const std::string& name = std::string()) {
1744 const std::size_t M = sn_.stations.size(), K = sn_.classes.size();
1747 rg.
cap.assign(M, std::vector<double>(K + 1, -1.0));
1748 rg.
maxmem.assign(M, -1.0);
1753 rg.
rule.assign(K, DropStrategy::WAITQ);
1756 for (std::size_t r = 0; r < K && r < rule.size(); ++r) rg.
rule[r] = rule[r];
1757 for (std::size_t r = 0; r < K && r < class_size.size(); ++r) rg.
size[r] = class_size[r];
1759 for (std::size_t j = 0; j < nodes.size(); ++j) {
1760 const std::size_t ist = station_of(nodes[j],
"addRegion");
1762 for (std::size_t r = 0; r < K; ++r) {
1768 if (r >= class_max_jobs.size()) {
1769 rg.
cap[ist - 1][r] = -1.0;
1772 double c = class_max_jobs[r];
1773 if (r < class_max_memory.size() && class_max_memory[r] != -1.0) {
1776 const double memjobs = std::floor(class_max_memory[r] / sz);
1777 c = c == -1.0 ? memjobs : std::min(c, memjobs);
1780 rg.
cap[ist - 1][r] = c;
1782 rg.
cap[ist - 1][K] = global_max_jobs;
1783 rg.
maxmem[ist - 1] = global_max_memory;
1785 sn_.regions.push_back(rg);
1786 return sn_.regions.size();
1795 if (region == 0 || region > sn_.regions.size())
1796 throw InputError(
"set_region_weights: region index is out of range");
1798 for (std::size_t r = 0; r < rg.
weight.size() && r < weight.size(); ++r)
1799 rg.
weight[r] = weight[r];
1804 if (region == 0 || region > sn_.regions.size())
1805 throw InputError(
"set_region_constraint: region index is out of range");
1806 if (A.
rows() != b.size())
1807 throw InputError(
"set_region_constraint: A and b disagree on the number of rows");
1808 sn_.regions[region - 1].lincon_A = A;
1809 sn_.regions[region - 1].lincon_b = b;
1821 const std::function<T(
const std::vector<T>&)>& fn,
1822 const std::string& kind = std::string(), std::size_t node = 0,
1823 std::size_t cls = 0) {
1824 for (std::size_t i = 0; i < sn_.reward.size(); ++i)
1825 if (sn_.reward[i].name == nm) {
1826 sn_.reward[i].fn = fn;
1827 sn_.reward[i].kind = kind;
1828 sn_.reward[i].node = node;
1829 sn_.reward[i].cls = cls;
1838 sn_.reward.push_back(rw);
1851 std::size_t target = 0,
1852 const std::vector<T>& remdist = std::vector<T>()) {
1853 const std::size_t K = sn_.classes.size();
1854 if (cls == 0 || cls > K)
throw InputError(
"set_signal: class index is out of range");
1855 if (target > K)
throw InputError(
"set_signal: target class index is out of range");
1856 if (sn_.issignal.size() < K) {
1857 sn_.issignal.assign(K,
false);
1859 sn_.signaltarget.assign(K, 0);
1861 sn_.signalremdist.assign(K, std::vector<T>());
1863 sn_.issignal[cls - 1] =
true;
1864 sn_.signaltype[cls - 1] = type;
1865 sn_.signaltarget[cls - 1] = target;
1866 sn_.signalrempolicy[cls - 1] = policy;
1867 sn_.signalremdist[cls - 1] = remdist;
1870 std::size_t
station_index(std::size_t node)
const {
return station_of(node,
"station_index"); }
1876 std::map<std::size_t, std::vector<std::size_t> > cache_item_classes_;
1879 struct CacheMissArc {
1880 std::size_t cls, from_node, to_node;
1881 CacheMissArc(std::size_t c, std::size_t f, std::size_t t)
1882 : cls(c), from_node(f), to_node(t) {}
1884 std::vector<CacheMissArc> cache_miss_arcs_;
1887 static std::vector<std::size_t> per_item_classes(
const std::vector<std::size_t>& spec,
1888 std::size_t nitems,
const char* what) {
1889 if (spec.size() == nitems)
return spec;
1890 if (spec.size() == 1)
return std::vector<std::size_t>(nitems, spec[0]);
1892 ": pass one class per item or a single class shared by all");
1896 JobClass& class_ref(std::size_t cls,
const char* what) {
1897 if (cls == 0 || cls > sn_.classes.size())
1898 throw InputError(std::string(what) +
": class index is out of range");
1899 return sn_.classes[cls - 1];
1902 void init_node(std::size_t nd) {
1903 sn_.nodes[nd - 1].routing.assign(sn_.classes.size(), RoutingStrategy::PROB);
1907 void grow_class_vectors() {
1908 const std::size_t K = sn_.classes.size();
1909 for (NodeDef& nd : sn_.nodes) nd.routing.resize(K, RoutingStrategy::PROB);
1910 for (Station<T>& st : sn_.stations) {
1911 if (!st.classcap.empty())
1912 st.classcap.resize(K, std::numeric_limits<double>::infinity());
1913 if (!st.droprule.empty()) st.droprule.resize(K, 0);
1914 if (!st.schedparam.empty()) st.schedparam.resize(K, num_traits<T>::from_int(1));
1919 Station<T>& station_ref(std::size_t node, std::size_t cls,
const char* what) {
1920 const std::size_t ist = station_of(node, what);
1921 if (cls == 0 || cls > sn_.classes.size())
1922 throw InputError(std::string(what) +
": class index is out of range");
1923 return sn_.stations[ist - 1];
1934 void grow_class_slot(std::vector<V>& v, std::size_t cls,
const V& fill) {
1935 const std::size_t K = sn_.classes.size();
1936 if (v.size() < K) v.resize(K < cls ? cls : K, fill);
1939 std::size_t station_of(std::size_t node,
const std::string& what)
const {
1940 if (node == 0 || node > sn_.nodes.size())
1941 throw InputError(what +
": node index " + std::to_string(node) +
" does not exist");
1942 const std::size_t ist = sn_.nodes[node - 1].station;
1944 throw InputError(what +
": node '" + sn_.nodes[node - 1].name +
1945 "' is not a station (it serves no jobs)");
1959 struct ForkOverride {
1960 enum Kind { TASKS, DIST, PROB };
1962 std::size_t fork = 0, cls = 0, dest = 0;
1964 lang::Distrib<T> dist;
1966 std::vector<ForkOverride> fork_overrides_;
1975 void record_fork_override(
const ForkOverride& ov) {
1976 if (ov.fork == 0 || ov.fork > sn_.nodes.size() ||
1977 sn_.nodes[ov.fork - 1].nodetype != NodeType::Fork)
1978 throw InputError(
"the node given is not a Fork of this model");
1979 if (ov.cls == 0 || ov.cls > sn_.classes.size())
1980 throw InputError(
"a fork override names a class that does not exist");
1981 fork_overrides_.push_back(ov);
1982 if (routing_installed()) apply_fork_override(ov);
1986 bool routing_installed()
const {
1987 return sn_.rtnodes.rows() >= sn_.nodes.size() * sn_.classes.size();
1991 void apply_fork_overrides() {
1992 for (std::size_t i = 0; i < fork_overrides_.size(); ++i)
1993 apply_fork_override(fork_overrides_[i]);
1996 void apply_fork_override(
const ForkOverride& ov) {
1997 qn::ForkParam<T>& f = fork_param(ov.fork);
1998 const std::vector<std::size_t> dests = fork_dests(ov.fork, ov.dest);
1999 for (std::size_t x = 0; x < dests.size(); ++x) {
2000 const std::size_t k = dests[x];
2002 case ForkOverride::TASKS:
2003 f.fan_out_link(k - 1, ov.cls - 1) = num_traits<T>::from_double(ov.value);
2005 case ForkOverride::DIST:
2006 f.fan_out_dist[k - 1][ov.cls - 1] = ov.dist;
2009 f.fan_out_link(k - 1, ov.cls - 1) = ov.dist.mean;
2011 case ForkOverride::PROB:
2012 f.fan_out_prob(k - 1, ov.cls - 1) = num_traits<T>::from_double(ov.value);
2016 refresh_fork_scalar(sn_.nodes[ov.fork - 1], f);
2019 qn::ForkParam<T>& fork_param(std::size_t fork_node) {
2020 if (fork_node == 0 || fork_node > sn_.nodes.size() ||
2021 sn_.nodes[fork_node - 1].nodetype != NodeType::Fork)
2022 throw InputError(
"the node given is not a Fork of this model");
2023 qn::ForkParam<T>& f = sn_.forkparam[fork_node];
2024 const std::size_t I = sn_.nodes.size(), K = sn_.classes.size();
2025 if (f.fan_out_link.rows() == I && f.fan_out_link.cols() == K)
return f;
2026 const T zero = num_traits<T>::from_int(0);
2029 f.fan_out_dist.assign(I, std::vector<lang::Distrib<T> >(K));
2030 const T tpl = num_traits<T>::from_double(sn_.nodes[fork_node - 1].tasks_per_link);
2031 const T one = num_traits<T>::from_int(1);
2032 for (std::size_t k = 1; k <= I; ++k)
2033 for (std::size_t r = 1; r <= K; ++r)
2034 if (fork_links_to(fork_node, k, r)) {
2035 f.fan_out_link(k - 1, r - 1) = tpl;
2036 f.fan_out_prob(k - 1, r - 1) = one;
2050 void refresh_fork_scalar(qn::NodeDef& nd,
const qn::ForkParam<T>& f) {
2052 std::size_t cnt = 0;
2053 for (std::size_t k = 0; k < f.fan_out_link.rows(); ++k)
2054 for (std::size_t r = 0; r < f.fan_out_link.cols(); ++r) {
2055 if (num_traits<T>::to_double(f.fan_out_prob(k, r)) == 0.0)
continue;
2056 acc += num_traits<T>::to_double(f.fan_out_link(k, r)) *
2057 num_traits<T>::to_double(f.fan_out_prob(k, r));
2060 if (cnt > 0) nd.tasks_per_link = acc /
static_cast<double>(cnt);
2064 bool fork_links_to(std::size_t fork_node, std::size_t k, std::size_t r)
const {
2065 const std::size_t K = sn_.classes.size(), I = sn_.nodes.size();
2066 if (sn_.rtnodes.rows() < I * K)
return false;
2067 for (std::size_t s = 1; s <= K; ++s)
2068 if (num_traits<T>::to_double(
2069 sn_.rtnodes((fork_node - 1) * K + r - 1, (k - 1) * K + s - 1)) != 0.0)
2080 std::vector<std::size_t> fork_dests(std::size_t fork_node, std::size_t dest_node)
const {
2081 std::vector<std::size_t> out;
2082 const std::size_t I = sn_.nodes.size(), K = sn_.classes.size();
2083 if (dest_node != 0) {
2085 throw InputError(
"a fork override names a node that does not exist");
2086 out.push_back(dest_node);
2089 for (std::size_t k = 1; k <= I; ++k)
2090 for (std::size_t r = 1; r <= K; ++r)
2091 if (fork_links_to(fork_node, k, r)) { out.push_back(k);
break; }
2093 throw InputError(
"a fork override was set on '" + sn_.nodes[fork_node - 1].name +
2094 "', which links nowhere yet: call link() before the override");
2105 void validate()
const {
2106 if (sn_.classes.empty())
throw InputError(
"Network '" + sn_.name +
"': it has no classes");
2107 if (sn_.stations.empty())
2108 throw InputError(
"Network '" + sn_.name +
"': it has no stations");
2124 const std::size_t K = sn_.classes.size();
2125 const T zero = num_traits<T>::from_int(0);
2126 std::vector<std::vector<bool>> switches(K, std::vector<bool>(K,
false));
2127 for (std::size_t r = 0; r < K; ++r)
2128 for (std::size_t sc = 0; sc < K; ++sc) {
2129 if (r == sc)
continue;
2130 for (std::size_t i = 1; i <= sn_.nodes.size() && !switches[r][sc]; ++i)
2131 for (std::size_t j = 1; j <= sn_.nodes.size(); ++j)
2132 if (sn_.get_route(r + 1, sc + 1, i, j) > zero) {
2133 switches[r][sc] =
true;
2137 for (
const auto& kv : sn_.csmatrix) {
2138 const Matrix<T>& C = kv.second;
2139 for (std::size_t r = 0; r < K && r < C.rows(); ++r)
2140 for (std::size_t sc = 0; sc < K && sc < C.cols(); ++sc)
2141 if (r != sc && C(r, sc) > zero) switches[r][sc] =
true;
2143 for (
const auto& kv : sn_.nodeparam) {
2144 const CacheParam<T>& cp = kv.second;
2145 for (std::size_t r = 0; r < K; ++r) {
2146 if (r < cp.hitclass.size() && cp.hitclass[r] >= 1 && cp.hitclass[r] <= K)
2147 switches[r][cp.hitclass[r] - 1] =
true;
2148 if (r < cp.missclass.size() && cp.missclass[r] >= 1 && cp.missclass[r] <= K)
2149 switches[r][cp.missclass[r] - 1] =
true;
2151 for (
const auto& row : cp.retrieval_classes)
2152 for (std::size_t r = 0; r < K && r < row.size(); ++r)
2153 if (row[r] >= 1 && row[r] <= K) switches[r][row[r] - 1] =
true;
2159 for (std::size_t r = 0; r < K; ++r)
2160 if (sn_.classes[r].spawn >= 1 && sn_.classes[r].spawn <= K)
2161 switches[r][sn_.classes[r].spawn - 1] =
true;
2162 for (std::size_t r = 0; r < sn_.classes.size(); ++r) {
2166 bool switched_into =
false;
2167 for (std::size_t q = 0; q < K; ++q)
2168 if (switches[q][r]) switched_into =
true;
2169 bool served =
false;
2170 for (std::size_t i = 0; i < sn_.stations.size(); ++i)
2171 if (!sn_.service[i][r].disabled) served =
true;
2177 for (std::size_t nd = 0; nd < sn_.nodes.size(); ++nd)
2178 if (sn_.nodes[nd].nodetype == NodeType::Transition) petri =
true;
2179 if (!served && !switched_into && !petri)
2180 throw InputError(
"Network '" + sn_.name +
"': class '" + sn_.classes[r].name +
2181 "' has no service process at any station");
2210 for (std::size_t i = 0; i < sn_.nodes.size(); ++i) {
2211 if (sn_.nodes[i].nodetype != NodeType::Fork)
continue;
2212 bool closed =
false;
2213 for (
const auto& fjp : sn_.fj)
2214 if (fjp.first == i + 1) closed =
true;
2215 if (closed)
continue;
2219 std::vector<bool> seen(sn_.nodes.size(),
false);
2220 std::vector<std::size_t> stack(1, i + 1);
2222 bool departs =
false;
2223 while (!stack.empty() && !departs) {
2224 const std::size_t u = stack.back();
2226 for (std::size_t v = 1; v <= sn_.nodes.size() && !departs; ++v) {
2227 if (seen[v - 1])
continue;
2229 for (std::size_t r = 1; r <= K && !edge; ++r)
2230 for (std::size_t s = 1; s <= K; ++s)
2231 if (sn_.get_route(r, s, u, v) > zero) {
2235 if (!edge)
continue;
2236 if (sn_.nodes[v - 1].nodetype == NodeType::Sink) {
2245 throw InputError(
"Network '" + sn_.name +
"': the Fork '" + sn_.nodes[i].name +
2246 "' is not closed by a Join and no Sink is reachable from it, "
2247 "so its siblings can neither merge nor depart");
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
void set_service_rate_function(std::size_t node, const std::function< T(const std::vector< std::size_t > &)> &muFun, const Matrix< T > &swap_graph=Matrix< T >())
Queue.setServiceRateFunction(muFun): the TOTAL service rate of a PAS or OI station as a function of t...
void set_drop_rule(std::size_t node, std::size_t cls, DropStrategy rule)
station.setDropRule(class, rule).
void set_state_prior(std::size_t node, const Matrix< T > &space, const std::vector< T > &prior)
StatefulNode.setStatePrior(space, prior): a distribution over the rows of a DECLARED state space.
void set_departure_discipline(std::size_t node, std::size_t cls, lang::DepartureDiscipline rule)
Place.setDepartureDiscipline(class, rule).
std::size_t add_logger(const std::string &nm, const std::string &log_file=std::string())
A Logger node: a pass-through that records every job crossing it.
void set_global_dependence(const GdScaling< T > &fun, const std::vector< T > &peak, int wire_cutoff)
As above, with an explicit per-slot OPEN-class truncation used when phi is materialized onto the JSON...
void set_load_dependence(std::size_t node, const std::vector< T > &alpha)
station.setLoadDependence(alpha): the rate multiplier at population 1, 2, ... The vector is indexed f...
void set_class_capacity(std::size_t node, std::size_t cls, double k)
std::size_t add_source(const std::string &nm)
The external arrival station.
std::size_t add_fork(const std::string &nm, double tasks_per_link=1.0)
A Fork node.
void bind_join(std::size_t join_node, std::size_t fork_node)
void set_arrival_batch(std::size_t node, std::size_t cls, const Distrib< T > &dist)
Source.setArrivalBatch(class, dist): the batch-size law released at each arrival epoch.
std::size_t add_open_class(const std::string &nm, int prio=0)
Network(const std::string &nm)
void set_class_patience(std::size_t cls, const Distrib< T > &dist, lang::ImpatienceType kind=lang::ImpatienceType::RENEGING)
JobClass.setPatience(kind, dist): the CLASS-WIDE abandonment law.
std::size_t add_delay(const std::string &nm)
An infinite-server station (a Delay, MATLAB's Delay / DelayStation).
void set_class_spawn(std::size_t cls, std::size_t spawn_cls)
JobClass.spawnClass (sn.classspawn): the class injected at the same station on every completion of cl...
void set_region_constraint(std::size_t region, const Matrix< T > &A, const std::vector< T > &b)
The optional linear constraint A n <= b a region may carry beyond its caps.
RoutingMatrix< T > init_routing_matrix() const
An empty routing matrix, MATLAB's model.initRoutingMatrix.
std::vector< std::size_t > set_miss_cache(std::size_t cache_node, std::size_t next_cache, const std::vector< std::size_t > &hit_classes_at_next)
Cache.setMissCache(readClass, nextCache, hitClassAtNext): send this cache's misses to next_cache pres...
void set_class_immediate_feedback(std::size_t cls)
jobclass.setImmediateFeedback(): the same property, class-wide.
void set_polling(std::size_t node, lang::PollingType ptype, const std::vector< Distrib< T > > &switchover=std::vector< Distrib< T > >(), std::size_t pk=1)
Queue.setPollingType(...): the polling discipline of a POLLING station and the switchover walks betwe...
std::size_t add_router(const std::string &nm)
A stateless routing node.
void set_initial_marking(std::size_t node, const std::vector< T > &tokens)
Place.setState(marking): the initial token count of the place, per class.
void set_number_of_servers(std::size_t node, double n)
queue.setNumberOfServers(n).
void set_joint_dependence(std::size_t node, const CdScaling< T > &fun, const std::vector< T > &peak)
station.setJointDependence(eta, peakRatePerClass): MATLAB's Station.ljdScaling / ljdScalingPeak.
void set_fork_tasks_per_link(std::size_t fork_node, std::size_t jobclass, double tasks, std::size_t dest_node=0)
Variable forking levels on an existing Fork, the twin of MATLAB Fork.setTasksPerLink(jobclass,...
void set_patience(std::size_t node, std::size_t cls, const Distrib< T > &dist, lang::ImpatienceType kind=lang::ImpatienceType::RENEGING)
Queue.setPatience(class, dist, type): the abandonment timer of a job WAITING at the station,...
void set_reply_signal_class(std::size_t call_cls, std::size_t reply_cls)
JobClass.setReplySignalClass(reply) (sn.syncreply), plus the sn.replyblock the state layer needs.
std::size_t add_queue(const std::string &nm, SchedStrategy sched=SchedStrategy::FCFS)
A queueing station.
void set_class_switch_matrix(std::size_t node, const Matrix< T > &C)
Install the switching matrix of a ClassSwitch created without one.
void set_routing(std::size_t node, std::size_t cls, RoutingStrategy rs)
node.setRouting(class, strategy).
void set_join_strategy(std::size_t node, lang::JoinStrategy strategy, double quorum=0.0)
Join.setStrategy(...): STD waits for every sibling, PARTIAL for a quorum.
void set_fork_branch_probability(std::size_t fork_node, std::size_t jobclass, std::size_t dest_node, double prob)
A branch that fires only with probability prob.
std::size_t add_cache(const std::string &nm, const CacheParam< T > &par)
A Cache node with its item population, list capacities and popularity.
void set_reward(const std::string &nm, const std::function< T(const std::vector< T > &)> &fn, const std::string &kind=std::string(), std::size_t node=0, std::size_t cls=0)
model.setReward(name, fn): a named reward evaluated on the AGGREGATE state row, the per-(station,...
std::size_t add_self_looping_class(const std::string &nm, double njobs, std::size_t refstat_node, int prio=0)
SelfLoopingClass(model, name, njobs, refstat, prio): a closed class whose jobs perpetually cycle at t...
void set_marked_classes(std::size_t node, const std::vector< std::size_t > &classes)
Source.markedClasses: the 1-based class carried by each mark of an MMAP.
std::size_t add_closed_class(const std::string &nm, double njobs, std::size_t refstat_node, int prio=0)
void set_sched_param(std::size_t node, std::size_t cls, const T &weight)
The DPS / GPS weight of a class at a station.
std::size_t add_class_switch(const std::string &nm, const Matrix< T > &C)
A ClassSwitch node carrying the (nclasses x nclasses) switching matrix.
void set_fork_tasks_per_link_dist(std::size_t fork_node, std::size_t jobclass, const lang::Distrib< T > &dist, std::size_t dest_node=0)
A random jobs-per-link degree, redrawn per link and per forked job.
std::size_t add_sink(const std::string &nm)
The external departure node.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
std::size_t add_class_switch(const std::string &nm)
A ClassSwitch node whose matrix is installed LATER, by set_class_switch_matrix.
void set_batch_reject(std::size_t node, std::size_t cls, const T &p)
Queue.setBatchRejectProbability(class, p).
void set_class_dependence(std::size_t node, const CdScaling< T > &fun, const std::vector< T > &peak=std::vector< T >())
station.setClassDependence(beta, peakRatePerClass).
void set_state_dep_routing(std::size_t entry, std::size_t departure, const std::vector< std::vector< std::size_t > > &branches, const std::vector< std::size_t > &level, const std::vector< double > &C, const Matrix< double > &d, std::size_t cls=0)
node.setStateDepRouting(class, departure, branches, level, C, d).
std::size_t add_place(const std::string &nm)
A Place: an SPN token container.
void set_routing_weights(std::size_t node, std::size_t cls, const std::map< std::size_t, double > &weights)
The per-destination weights of a WRROBIN dispatcher, per (node, class).
void set_item_miss_class(std::size_t cache_node, const std::vector< std::size_t > &miss_classes)
Cache.setItemMissClass(readClass, missClasses): terminate a cache network, every per-item class of th...
void set_log_path(const std::string &path)
Network.setLogPath: the directory every Logger of this model writes into.
void set_capacity(std::size_t node, double k)
station.setCapacity(k), the K of Kendall's notation.
NetworkStruct< T > & raw_struct()
The struct WITHOUT refreshing it, for a caller that is still building.
void set_switchover(std::size_t node, std::size_t cls, const Distrib< T > &so)
Queue.setSwitchover(jobclass, distrib): the switchover time of a class.
void set_immediate_feedback(std::size_t node, std::size_t cls)
queue.setImmediateFeedback(class): a completing job of that class is fed straight back into service,...
void set_balking(std::size_t node, std::size_t cls, lang::BalkingStrategy strategy, const std::vector< typename Station< T >::BalkingThreshold > &thresholds)
Queue.setBalking(class, strategy, thresholds): an arrival that refuses to JOIN, on the state it finds...
void add_server_type(std::size_t node, const typename Station< T >::ServerType &stype)
Queue.addServerType(...): one heterogeneous server pool of the station.
void set_setup_delayoff(std::size_t node, std::size_t cls, const Distrib< T > &setup, const Distrib< T > &delayoff)
Queue.setDelayOff(class, setupTime, delayoffTime): the station powers down after sitting idle for the...
void set_retrieval_system(std::size_t cache_node, std::size_t read_class, std::size_t miss_class, const std::vector< std::size_t > &queue_nodes)
Cache.setRetrievalSystem(readClass, missClass, queues): a delayed-hit cache whose misses are fetched ...
void set_class_deadline(std::size_t cls, double due)
JobClass.deadline: the soft deadline EDD, EDF and JMT's tardiness use.
void link(const RoutingMatrix< T > &Pm)
model.link(P): install the routing.
void set_routing_param(std::size_t node, std::size_t cls, int d)
The d of a power-of-d (SQ) dispatcher, per (node, class).
void set_retrial(std::size_t node, std::size_t cls, const Distrib< T > &proc, const T &rate, int max_attempts=0)
Queue.setRetrial(...): a station with an ORBIT instead of a waiting line.
void set_service(std::size_t node, std::size_t cls, const Distrib< double > &d)
void set_reference_class(std::size_t cls)
JobClass.setReferenceClass(true): sn.refclass(c) picks this class.
void pas_mirror(std::size_t ist, const std::function< double(const std::vector< std::size_t > &)> &mu, const Matrix< double > &swap_graph)
std::size_t add_join_unbound(const std::string &nm)
void set_hetero_sched_policy(std::size_t node, lang::HeteroSchedPolicy policy)
Queue.setHeteroSchedPolicy(...): how the server pools are picked among.
std::size_t add_join(const std::string &nm, std::size_t fork_node)
A Join node, which IS a station: it serves at an infinite rate, and the synchronisation delay is supp...
void set_breakdown(std::size_t node, const Distrib< T > &failure, const Distrib< T > &repair, const std::vector< Distrib< T > > &down_service=std::vector< Distrib< T > >())
Queue.setBreakdown(failure, repair, downService): the server alternates up and down on the two clocks...
void set_pas(std::size_t node, const std::function< T(const std::vector< std::size_t > &)> &mu, const std::vector< std::vector< bool > > &swap_graph=std::vector< std::vector< bool > >())
Queue.setService(@(c) ...) for a pass-and-swap / order-independent station: the total service rate mu...
void set_item_read_classes(std::size_t cache_node, const std::vector< std::size_t > &read_classes, const std::vector< std::size_t > &hit_classes)
Cache.setItemReadClasses(readClasses, hitClasses): declare that read_classes[i] is the request stream...
std::size_t add_transition(const std::string &nm, const TransitionParam< T > &par)
A Transition: the firing rules of an SPN, as Transition in MATLAB.
void set_orbit_impatience(std::size_t node, std::size_t cls, const Distrib< T > &dist)
Queue.setOrbitImpatience(class, dist): abandonment from the retrial orbit.
void set_sync_reply(std::size_t node, std::size_t call_cls, std::size_t reply_cls)
Declare a SYNCHRONOUS call: a job of call_cls leaving node keeps its server until a job of the REPLY ...
void set_signal(std::size_t cls, lang::SignalType type, lang::RemovalPolicy policy=lang::RemovalPolicy::RANDOM, std::size_t target=0, const std::vector< double > &remdist=std::vector< double >())
void set_global_dependence(const GdScaling< T > &fun, const std::vector< T > &peak)
model.setGlobalDependence(phi, peak): MATLAB's Network.gdScaling.
void set_server_parallelism(std::size_t node, std::size_t cls, std::size_t n)
Queue.setServerParallelism(class, n): the servers a job seizes for the whole of its service.
std::size_t station_index(std::size_t node) const
void set_region_weights(std::size_t region, const std::vector< T > &weight)
FiniteCapacityRegion.setClassWeight: the per-class weight the region's global cap counts a job agains...
void set_arrival(std::size_t node, std::size_t cls, const Distrib< T > &d)
source.setArrival(class, dist): the same table, at the Source.
std::size_t add_region(const std::vector< std::size_t > &nodes, const std::vector< double > &class_max_jobs, double global_max_jobs=-1.0, const std::vector< DropStrategy > &rule=std::vector< DropStrategy >(), const std::vector< double > &class_max_memory=std::vector< double >(), const std::vector< T > &class_size=std::vector< T >(), double global_max_memory=-1.0, const std::string &name=std::string())
FiniteCapacityRegion(model, nodes): a cap on the jobs held ACROSS a set of stations.
void set_polling_type(std::size_t node, lang::PollingType rule, int par=0)
Queue.setPollingType(rule, par): the polling discipline of a POLLING station, identical across all cl...
The routing matrix a model script fills in, MATLAB's P cell array.
T get(std::size_t r, std::size_t s, std::size_t i, std::size_t j) const
void set(std::size_t r, std::size_t s, std::size_t i, std::size_t j, const T &p)
void set(std::size_t i, std::size_t j, const T &p)
std::map< Key, double > entries
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Dense matrix and non-owning view.
void sn_fj_nodevisits_mmt(qn::NetworkStruct< T > &sn)
Rewrite sn.nodevisits with the MMT correction.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
BalkingStrategy
Balking rules, with the values of MATLAB BalkingStrategy.
SignalType
G-network signal classes, with the values of MATLAB SignalType.
@ REPLY
completes a synchronous call, releasing a held server
@ NEGATIVE
removes a batch of jobs (Gelenbe's negative customer)
void prior_refresh_moments(Distrib< T > &d)
Write the mixture moments onto a Prior, the counterpart of dist_refresh_moments for the Markovian fam...
JoinStrategy
Join rules, with the values of MATLAB JoinStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
DepartureDiscipline
When a Place releases a served token, MATLAB DepartureDiscipline.
RemovalPolicy
Which job a negative signal removes, with the values of MATLAB RemovalPolicy.
@ RANDOM
uniform over waiting AND in-service jobs
HeteroSchedPolicy
How a heterogeneous station picks among its server types, MATLAB HeteroSchedPolicy.
PollingType
Polling service disciplines, with the values of MATLAB PollingType.
@ KLIMITED
serve at most K per visit (K in pollingPar)
std::function< std::vector< T >(const std::vector< T > &)> GdScaling
A globally state-dependent scaling, sn.gdscaling.
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
void dist_refresh_moments(Distrib< T > &d)
Fill in the first two moments of a distribution given by its matrices.
NodeType
Node kinds, with the values of MATLAB NodeType.
ImpatienceType
Impatience kinds, with the values of MATLAB ImpatienceType.
SdrCoeff pfqn_sdrcoeff(const SdrStruct &sdr)
Validates an SDR structure and returns its derived coefficients.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Prior: parameter uncertainty as a weighted set of alternative models.
Post-MMT node visits of a fork-join model.
Topology and coefficients of a state-dependent routing subnetwork.
std::vector< std::size_t > departureOf
departureOf[b] is the departure centre d(b) of branch b.
std::vector< std::size_t > entryOf
entryOf[b] is the entry centre e(b) of branch b.
std::vector< std::size_t > level
level[b] is the unique t with B_b in V_t - V_{t+1}; level[0] is unused.
Matrix< double > d
Coefficients d_tb of eq.
std::vector< double > C
Coefficients C_t of eq.
std::size_t departure
Departure centre d of Q(V,V); may equal entry.
std::vector< std::vector< std::size_t > > branch
branch[b] holds the centres of branch b, b >= 1; branch[0] is unused.
std::size_t entry
Entry centre e of Q(V,V).
Server breakdown and repair of a station whose server fails and is repaired.
T failure_rate
sn.breakdownMu: 1 / mean failure time
lang::Distrib< T > repair
time to repair of a down server
lang::Distrib< T > failure
time to failure of an up server
T repair_rate
sn.repairMu: 1 / mean repair time
std::vector< T > down_service_rates
sn.downServiceRates(ist, :): per class, 0 = no service while down.
std::vector< Popularity > preadkind
per class, parallel to pread
std::map< std::size_t, std::vector< std::size_t > > retrieval_queues
read class(0-based)->nodes
std::vector< std::vector< std::size_t > > retrieval_classes
(nitems x nclasses), 1-based
std::vector< int > itemcap
std::vector< std::size_t > missclass
std::vector< std::size_t > hitclass
std::vector< std::size_t > classitem
Item read by each per-item class of a cache network (MATLAB Cache.setItemReadClasses,...
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN
static Distrib exp_rate(const T &r)
static Distrib disabled_dist()
static Distrib immediate()
The Immediate singleton.
One job class of the network.
std::size_t refstat
1-based reference station
double population
infinite for an open class
The DECLARED join rule of a Join node, by 1-based node index.
lang::JoinStrategy strategy
double quorum
0 = every sibling
The G-network signal declaration, per CLASS.
std::function< T(const std::vector< std::size_t > &)> svc_rate_fun
std::vector< std::vector< bool > > swap_graph
The polling controller of a POLLING station, keyed by station index.
std::size_t pk
the K of K-LIMITED
std::vector< lang::Distrib< T > > switchover
FINITE CAPACITY REGIONS, MATLAB's refreshRegions output.
std::vector< std::vector< double > > cap
(nstations x nclasses+1), -1 = unbounded
std::string name
The region's declared name, as the wire carries it; a generated one otherwise.
std::vector< double > maxmem
per member station, -1 = unbounded
std::vector< DropStrategy > rule
per class
std::vector< T > size
per class; size is the memory footprint
std::vector< bool > members
membership, independent of the caps
sn.reward: the user-declared reward functions, MATLAB's model.setReward(name, fn).
std::function< T(const std::vector< T > &)> fn
std::string kind
The DECLARATIVE form the reward was built from, when it was: the template name (QLen,...
std::vector< RoutingStrategy > routing
sn.routing, per class.
The parameters of a retrial station: MATLAB sn.retrialProc and friends.
std::vector< int > max_attempts
0 = unbounded
std::vector< T > retrial_rate
mu_r, the per-class orbit retry rate
std::vector< lang::Distrib< T > > retrial_proc
retrial_proc[r] is the class-r retrial process; empty = not a retrial class.
Key(std::size_t r_, std::size_t s_, std::size_t i_, std::size_t j_)
bool operator<(const Key &o) const
Setup and delay-off of a station that powers down when it falls idle.
std::vector< lang::Distrib< T > > setup
per class, disabled = not declared
std::vector< lang::Distrib< T > > delayoff
per class
Per class; strategy == NONE is a class that declares no balking.
One balking threshold: with min_jobs <= n <= max_jobs at the station, an arriving job of the class re...
A heterogeneous server pool: count servers that serve only compatible classes, each with its own serv...
One station of the network.
std::vector< Distrib< T > > orbit_impatience
Queue.setOrbitImpatience(class, dist): abandonment from the RETRIAL ORBIT, which is a different popul...
std::vector< T > jdscalingpeak
sn.jdscalingpeak for this station: the declared peak joint-dependent scaling per class.
std::vector< BalkingParam > balking
std::function< T(const std::vector< std::size_t > &)> svc_rate_fun
sn.nodeparam{ind}.svcRateFun for a PAS / OI station: the TOTAL service rate as a function of the orde...
std::vector< T > batch_reject
Queue.setBatchRejectProbability: per-class rejection of a whole batch.
std::vector< Distrib< T > > patience
Queue.setPatience(class, dist): the abandonment timer of a WAITING job, with impatience[r] naming whi...
std::vector< std::size_t > server_parallelism
Queue.setServerParallelism(class, n): the servers a job seizes for the whole of its service,...
std::vector< int > droprule
Per-class blocking rule as an INT, with 0 meaning "not set".
double nservers
may be infinite (a Delay, or an inf-scheduled task)
std::vector< lang::PollingType > polling_type
Polling parameters for a POLLING station, MATLAB's pollingType, switchoverTime and pollingPar on the ...
Matrix< T > swap_graph
sn.nodeparam{ind}.swapGraph: which class a departing job promotes the jobs behind it into.
CdScaling< T > jdscaling
sn.jdscaling for this station: MATLAB's Station.ljdScaling, the JOINT dependence map eta_i(n),...
std::vector< T > cdscalingpeak
sn.cdscalingpeak for this station: the DECLARED peak rate scaling per class, empty when the station i...
std::vector< lang::ImpatienceType > impatience
std::vector< T > schedparam
sn.schedparam, per class: the DPS / GPS weight, or the SEPT / LEPT rank.
CdScaling< T > cdscaling
sn.cdscaling for this station: the class-dependence map, empty when unset.
std::vector< double > classcap
Per-class buffer from setChainCapacity; infinite where unset.
std::vector< lang::DepartureDiscipline > departure_discipline
Place.departureDiscipline, per class.
std::vector< Distrib< T > > arrival_batch
Source.setArrivalBatch(class, dist): the batch-size law released at each arrival epoch.
std::vector< Distrib< T > > switchover
std::vector< bool > immfeed
Node-level immediate feedback, per class; empty when the station sets none.
The parameters of a Cache node, MATLAB's sn.nodeparam{ind} for a Cache.
std::vector< Matrix< T > > firing
firing[m](p,r): class-r tokens mode m moves to/from place p when it fires.
std::vector< Matrix< T > > enabling
enabling[m](p,r): class-r tokens of place p (0-based node) mode m needs.