269 void set_seed(
long long seed) { rng_.reseed(seed); }
273 const std::string&
model_name()
const {
return model_name_; }
277 if (!is_one_of(strat, sched_vocabulary()))
278 throw InputError(
"NetworkGenerator: scheduling strategy '" + strat +
279 "' does not exist or is not supported");
280 sched_strat_ = strat;
286 if (strat !=
"Probabilities" && strat !=
"Random" && strat !=
"randomize")
287 throw InputError(
"NetworkGenerator: routing strategy '" + strat +
288 "' does not exist or is not supported");
289 routing_strat_ = strat;
295 if (!ieq(d,
"Exp") && !ieq(d,
"Erlang") && !ieq(d,
"HyperExp") && !ieq(d,
"randomize"))
296 throw InputError(
"NetworkGenerator: distribution '" + d +
297 "' does not exist or is not supported");
304 if (!ieq(load,
"high") && !ieq(load,
"medium") && !ieq(load,
"low") &&
305 !ieq(load,
"randomize"))
306 throw InputError(
"NetworkGenerator: model load can only be high, medium, low or "
308 cclass_job_load_ = load;
347 if (!fcn)
throw InputError(
"NetworkGenerator: the topology function is empty");
349 if (adj.
rows() != 2 || adj.
cols() != 2)
350 throw InputError(
"NetworkGenerator: topologyFcn must take a positive integer and "
351 "return a square adjacency matrix of that order");
369 if (num_delays < 0) num_delays = (num_queues > 1) ? rng_.randi(0, 1) : 1;
370 if (num_cclass < 0) num_cclass = rng_.randi(1, 4);
371 validate_args(num_queues, num_delays, num_oclass, num_cclass);
373 std::string last_reject;
377 build(model, num_queues, num_delays, num_oclass, num_cclass);
379 const std::string why = why_invalid(
sn);
385 }
catch (
const Error& e) {
386 last_reject = e.what();
390 throw InputError(
"NetworkGenerator: could not generate a model with recurrent closed-chain "
392 " attempts. The requested topology may not admit a strongly connected "
393 "closed chain. Last rejection: " + last_reject);
398 return generate(num_queues, num_delays, num_oclass, -1);
402 return generate(num_queues, num_delays, 0, -1);
406 return generate(num_queues, -1, 0, -1);
410 const int nq = rng_.randi(1, 8);
419 static const std::vector<std::string>& sched_vocabulary() {
420 static const std::vector<std::string> v = {
"fcfs",
"ps",
"inf",
"lcfs",
"lcfspr",
"siro",
421 "sjf",
"ljf",
"sept",
"lept",
"randomize"};
425 static bool ieq(
const std::string& a,
const std::string& b) {
426 if (a.size() != b.size())
return false;
427 for (std::size_t i = 0; i < a.size(); ++i)
428 if (std::tolower(
static_cast<unsigned char>(a[i])) !=
429 std::tolower(
static_cast<unsigned char>(b[i])))
434 static bool is_one_of(
const std::string& s,
const std::vector<std::string>& v) {
435 return std::find(v.begin(), v.end(), s) != v.end();
438 static void validate_args(
int nq,
int nd,
int no,
int nc) {
439 if (nq < 0 || nd < 0 || no < 0 || nc < 0)
440 throw InputError(
"NetworkGenerator: the station and class counts must be non-negative");
441 if (nq + nd <= 0 || no + nc <= 0)
442 throw InputError(
"NetworkGenerator: at least one station and one job class are "
450 double choose_num_servers() {
451 return multi_server_queues_ ? double(rng_.randi(1, MAX_SERVERS)) : 1.0;
454 double choose_num_jobs() {
455 if (ieq(cclass_job_load_,
"high"))
return band(HIGH_LO, HIGH_HI);
456 if (ieq(cclass_job_load_,
"medium"))
return band(MED_LO, MED_HI);
457 if (ieq(cclass_job_load_,
"low"))
return band(LOW_LO, LOW_HI);
458 return double(rng_.randi(1, HIGH_HI));
461 double band(
int lo,
int hi) {
return double(rng_.randi(lo, hi)); }
464 if (ieq(sched_strat_,
"randomize"))
466 return sched_from_name(sched_strat_);
480 throw UnsupportedError(ErrorKind{
"SchedStrategy"},
"NetworkGenerator: scheduling strategy '" + s +
"' is not supported");
484 std::string choose_routing_strat() {
485 if (ieq(routing_strat_,
"randomize"))
486 return rng_.randi(1, 2) == 1 ? std::string(
"Random") : std::string(
"Probabilities");
487 return routing_strat_;
496 lang::Distrib<T> choose_distribution() {
498 if (ieq(distribution_,
"Exp"))
id = 1;
499 else if (ieq(distribution_,
"Erlang"))
id = 2;
500 else if (ieq(distribution_,
"HyperExp"))
id = 3;
501 else id = rng_.randi(1, 3);
504 varying_service_rates_ ? std::pow(2.0,
double(rng_.randi(-6, 6))) : 1.0;
508 const std::size_t k = std::size_t(std::pow(2.0,
double(rng_.randi(0, 6))));
511 const double scv = std::pow(2.0,
double(rng_.randi(0, 6)));
512 return fit_hyperexp(mean, scv);
522 static lang::Distrib<T> fit_hyperexp(
double mean,
double scv) {
525 num_traits<T>::from_double(dd.params[1]),
526 num_traits<T>::from_double(dd.params[2]));
533 void build(qn::Network<T>& model,
int num_queues,
int num_delays,
int num_oclass,
539 create_stations(model, num_queues, num_delays, num_oclass > 0);
540 create_classes(model, num_oclass, num_cclass);
541 set_service_processes(model);
542 define_topology(model, num_oclass > 0);
545 void create_stations(qn::Network<T>& model,
int num_queues,
int num_delays,
bool has_oclass) {
546 for (
int i = 0; i < num_queues; ++i) {
548 const std::size_t nd = model.add_queue(
"queue" + std::to_string(i + 1), sched);
551 const double servers = choose_num_servers();
552 model.set_number_of_servers(nd, servers);
553 stations_.push_back(nd);
555 for (
int i = 0; i < num_delays; ++i)
556 stations_.push_back(model.add_delay(
"delay" + std::to_string(i + 1)));
558 source_ = model.add_source(
"source");
559 sink_ = model.add_sink(
"sink");
563 void create_classes(qn::Network<T>& model,
int num_oclass,
int num_cclass) {
565 for (
int i = 0; i < num_oclass; ++i) {
566 const std::size_t r = model.add_open_class(
"OClass" + std::to_string(i + 1));
567 model.set_arrival(source_, r, choose_distribution());
568 classes_.push_back(r);
570 if (num_cclass > 0) {
575 const std::size_t ref = stations_[std::size_t(rng_.randi(1,
int(stations_.size())) - 1)];
576 for (
int i = 0; i < num_cclass; ++i)
577 classes_.push_back(model.add_closed_class(
"CClass" + std::to_string(i + 1),
578 choose_num_jobs(), ref));
582 void set_service_processes(qn::Network<T>& model) {
583 for (std::size_t c = 0; c < classes_.size(); ++c)
584 for (std::size_t s = 0; s < stations_.size(); ++s)
585 model.set_service(stations_[s], classes_[c], choose_distribution());
593 void define_topology(qn::Network<T>& model,
bool has_oclass) {
594 Matrix<double> topo = topology_fcn_(stations_.size(), rng_);
595 if (topo.rows() != stations_.size() || topo.cols() != stations_.size())
596 throw InputError(
"NetworkGenerator: the topology function returned an adjacency "
597 "matrix of the wrong order");
599 const Matrix<T> mask = gen_cs_mask(model);
603 std::vector<std::size_t> row_node = stations_;
605 const std::size_t S = stations_.size();
606 Matrix<double> grown(S + 2, S + 2, 0.0);
607 for (std::size_t i = 0; i < S; ++i)
608 for (std::size_t j = 0; j < S; ++j) grown(i, j) = topo(i, j);
609 grown(S, std::size_t(rng_.randi(1,
int(S)) - 1)) = 1.0;
610 grown(std::size_t(rng_.randi(1,
int(S)) - 1), S + 1) = 1.0;
612 row_node.push_back(source_);
613 row_node.push_back(sink_);
618 const std::size_t nrows = has_oclass ? stations_.size() + 1 : stations_.size();
620 qn::RoutingMatrix<T>
P;
621 for (std::size_t i = 0; i < nrows; ++i) {
622 std::vector<std::size_t> dest;
623 for (std::size_t j = 0; j < topo.cols(); ++j)
624 if (topo(i, j) > 0.0) dest.push_back(row_node[j]);
625 std::sort(dest.begin(), dest.end());
626 const std::vector<std::size_t> outgoing =
627 add_outgoing_links(model, P, row_node[i], dest, mask);
628 set_routing_strategies(model, P, row_node[i], outgoing);
641 Matrix<T> gen_cs_mask(qn::Network<T>& model) {
642 const qn::NetworkStruct<T>& sn = model.raw_struct();
643 std::size_t num_open = 0, num_closed = 0;
644 for (std::size_t r = 0; r < sn.classes.size(); ++r) {
648 const std::size_t K = num_open + num_closed;
649 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
650 Matrix<T> mask(K, K, zero);
652 if (!multi_chain_cs_) {
653 for (std::size_t i = 0; i < num_open; ++i)
654 for (std::size_t j = 0; j < num_open; ++j) mask(i, j) = one;
655 for (std::size_t i = num_open; i < K; ++i)
656 for (std::size_t j = num_open; j < K; ++j) mask(i, j) = one;
660 std::vector<int> sizes;
662 const std::vector<int> s = assign_chains(
static_cast<int>(num_open));
663 sizes.insert(sizes.end(), s.begin(), s.end());
665 if (num_closed > 0) {
666 const std::vector<int> s = assign_chains(
static_cast<int>(num_closed));
667 sizes.insert(sizes.end(), s.begin(), s.end());
669 std::size_t start = 0;
670 for (std::size_t c = 0; c < sizes.size(); ++c) {
671 const std::size_t end = start + std::size_t(sizes[c]);
672 for (std::size_t i = start; i < end; ++i)
673 for (std::size_t j = start; j < end; ++j) mask(i, j) = one;
680 std::vector<int> assign_chains(
int num_classes) {
681 const int num_chains = rng_.randi(1, num_classes);
682 const std::vector<int> sizes =
randintfixedsum(num_classes, num_chains, rng_);
683 const std::vector<std::size_t> perm = rng_.randperm(std::size_t(num_chains));
684 std::vector<int> out(sizes.size());
685 for (std::size_t i = 0; i < perm.size(); ++i) out[i] = sizes[perm[i]];
690 Matrix<T> rand_class_switch_matrix(
const Matrix<T>& mask) {
691 const std::size_t K = mask.rows();
692 const T zero = num_traits<T>::from_int(0);
693 Matrix<T> C(K, K, zero);
694 for (std::size_t i = 0; i < K; ++i) {
695 std::vector<std::size_t> valid;
696 for (std::size_t j = 0; j < K; ++j)
697 if (num_traits<T>::to_double(mask(i, j)) > 0.0) valid.push_back(j);
699 C(i, i) = num_traits<T>::from_int(1);
703 for (std::size_t k = 0; k < valid.size(); ++k)
704 C(i, valid[k]) = num_traits<T>::from_double(probs[k]);
716 std::vector<std::size_t> add_outgoing_links(qn::Network<T>& model, qn::RoutingMatrix<T>& P,
718 const std::vector<std::size_t>& dest,
719 const Matrix<T>& mask) {
720 const qn::NetworkStruct<T>& sn = model.raw_struct();
721 const std::size_t K = sn.classes.size();
722 const T one = num_traits<T>::from_int(1);
723 std::vector<std::size_t> outgoing;
724 for (std::size_t d = 0; d < dest.size(); ++d) {
725 const std::size_t to = dest[d];
728 if (random_cs_nodes_ && rng_.randi(0, 1) == 1 && !from_source && !to_sink) {
729 const Matrix<T> C = rand_class_switch_matrix(mask);
730 const std::size_t cs = model.add_class_switch(
731 "cs_" + sn.nodes[from - 1].name +
"_" + sn.nodes[to - 1].name, C);
732 outgoing.push_back(cs);
733 for (std::size_t r = 1; r <= K; ++r)
P.set(r, r, cs, to, one);
735 outgoing.push_back(to);
757 void set_routing_strategies(qn::Network<T>& model, qn::RoutingMatrix<T>& P, std::size_t from,
758 const std::vector<std::size_t>& outgoing) {
759 const qn::NetworkStruct<T>& sn = model.raw_struct();
760 const std::size_t K = sn.classes.size();
761 const T one = num_traits<T>::from_int(1);
764 for (std::size_t r = 1; r <= K; ++r) {
766 for (std::size_t k = 0; k < outgoing.size(); ++k)
P.set(r, r, from, outgoing[k], one);
771 for (std::size_t r = 1; r <= K; ++r) {
773 std::vector<std::size_t> allowed;
774 for (std::size_t k = 0; k < outgoing.size(); ++k) {
776 allowed.push_back(outgoing[k]);
778 const std::string strat = choose_routing_strat();
779 if (strat ==
"Random") {
781 if (allowed.empty())
continue;
782 const double share = 1.0 / double(allowed.size());
783 for (std::size_t k = 0; k < allowed.size(); ++k)
784 P.set(r, r, from, allowed[k], num_traits<T>::from_double(share));
787 if (allowed.empty())
continue;
788 const std::vector<double> probs =
randfixedsumone(allowed.size(), rng_);
789 for (std::size_t k = 0; k < allowed.size(); ++k)
790 P.set(r, r, from, allowed[k], num_traits<T>::from_double(probs[k]));
808 static std::string why_invalid(
const qn::NetworkStruct<T>& sn) {
809 for (std::size_t c = 0; c < sn.nchains; ++c) {
810 if (c >= sn.inchain.size() || sn.inchain[c].empty())
continue;
811 const std::vector<std::size_t>& ic = sn.inchain[c];
813 for (std::size_t x = 0; x < ic.size(); ++x)
814 if (!std::isfinite(num_traits<T>::to_double(sn.classes[ic[x] - 1].population)))
816 if (!closed)
continue;
817 const std::size_t rs = sn.classes[ic[0] - 1].refstat;
818 if (rs == 0 || rs > sn.station_to_node.size())
continue;
819 const std::size_t node = sn.station_to_node[rs - 1];
820 if (c >= sn.nodevisits.size() || node == 0 || node > sn.nodevisits[c].rows())
continue;
822 for (std::size_t x = 0; x < ic.size(); ++x)
823 total += num_traits<T>::to_double(sn.nodevisits[c](node - 1, ic[x] - 1));
825 return "closed chain " + std::to_string(c + 1) +
826 " never visits its reference station '" + sn.nodes[node - 1].name +
"'";
828 return std::string();
835 static constexpr int MAX_SERVERS = 40;
836 static constexpr int HIGH_LO = 31, HIGH_HI = 40;
837 static constexpr int MED_LO = 11, MED_HI = 20;
838 static constexpr int LOW_LO = 1, LOW_HI = 5;
841 std::string model_name_ =
"nw";
842 std::string sched_strat_ =
"randomize";
843 std::string routing_strat_ =
"randomize";
844 std::string distribution_ =
"randomize";
845 std::string cclass_job_load_ =
"randomize";
846 bool varying_service_rates_ =
false;
847 bool multi_server_queues_ =
false;
848 bool random_cs_nodes_ =
false;
849 bool multi_chain_cs_ =
false;
850 std::function<Matrix<double>(std::size_t, MatlabRng&)> topology_fcn_;
852 std::vector<std::size_t> stations_;
853 std::vector<std::size_t> classes_;
854 std::size_t source_ = 0, sink_ = 0;