282 void set_seed(
long long seed) { rng_.set_seed(seed); }
286 const std::string&
model_name()
const {
return model_name_; }
290 if (!is_one_of(strat, sched_vocabulary()))
291 throw InputError(
"NetworkGenerator: scheduling strategy '" + strat +
292 "' does not exist or is not supported");
293 sched_strat_ = strat;
299 if (strat !=
"Probabilities" && strat !=
"Random" && strat !=
"randomize")
300 throw InputError(
"NetworkGenerator: routing strategy '" + strat +
301 "' does not exist or is not supported");
302 routing_strat_ = strat;
308 if (!ieq(d,
"Exp") && !ieq(d,
"Erlang") && !ieq(d,
"HyperExp") && !ieq(d,
"randomize"))
309 throw InputError(
"NetworkGenerator: distribution '" + d +
310 "' does not exist or is not supported");
317 if (!ieq(load,
"high") && !ieq(load,
"medium") && !ieq(load,
"low") &&
318 !ieq(load,
"randomize"))
319 throw InputError(
"NetworkGenerator: model load can only be high, medium, low or "
321 cclass_job_load_ = load;
359 if (!fcn)
throw InputError(
"NetworkGenerator: the topology function is empty");
362 if (adj.
rows() != 2 || adj.
cols() != 2)
363 throw InputError(
"NetworkGenerator: topologyFcn must take a positive integer and "
364 "return a square adjacency matrix of that order");
381 if (num_delays < 0) num_delays = (num_queues > 1) ? rng_.next_int(2) : 1;
382 validate_args(num_queues, num_delays, num_oclass, num_cclass);
384 std::string last_reject;
388 build(model, num_queues, num_delays, num_oclass, num_cclass);
390 const std::string why = why_invalid(
sn);
396 }
catch (
const Error& e) {
397 last_reject = e.what();
401 throw InputError(
"NetworkGenerator: could not generate a model with recurrent closed-chain "
403 " attempts. The requested topology may not admit a strongly connected "
404 "closed chain. Last rejection: " + last_reject);
409 return generate(num_queues, num_delays, num_oclass, rng_.next_int(4) + 1);
413 return generate(num_queues, num_delays, 0, rng_.next_int(4) + 1);
417 return generate(num_queues, -1, 0, rng_.next_int(4) + 1);
421 const int nq = rng_.next_int(8) + 1;
422 return generate(nq, -1, 0, rng_.next_int(4) + 1);
430 static const std::vector<std::string>& sched_vocabulary() {
431 static const std::vector<std::string> v = {
"fcfs",
"ps",
"inf",
"lcfs",
"lcfspr",
"siro",
432 "sjf",
"ljf",
"sept",
"lept",
"randomize"};
436 static bool ieq(
const std::string& a,
const std::string& b) {
437 if (a.size() != b.size())
return false;
438 for (std::size_t i = 0; i < a.size(); ++i)
439 if (std::tolower(
static_cast<unsigned char>(a[i])) !=
440 std::tolower(
static_cast<unsigned char>(b[i])))
445 static bool is_one_of(
const std::string& s,
const std::vector<std::string>& v) {
446 return std::find(v.begin(), v.end(), s) != v.end();
449 static void validate_args(
int nq,
int nd,
int no,
int nc) {
450 if (nq < 0 || nd < 0 || no < 0 || nc < 0)
451 throw InputError(
"NetworkGenerator: the station and class counts must be non-negative");
452 if (nq + nd <= 0 || no + nc <= 0)
453 throw InputError(
"NetworkGenerator: at least one station and one job class are "
461 double choose_num_servers() {
462 return multi_server_queues_ ? double(rng_.next_int(MAX_SERVERS) + 1) : 1.0;
465 double choose_num_jobs() {
466 if (ieq(cclass_job_load_,
"high"))
return band(HIGH_LO, HIGH_HI);
467 if (ieq(cclass_job_load_,
"medium"))
return band(MED_LO, MED_HI);
468 if (ieq(cclass_job_load_,
"low"))
return band(LOW_LO, LOW_HI);
469 return double(rng_.next_int(HIGH_HI) + 1);
472 double band(
int lo,
int hi) {
return double(rng_.next_int(hi - lo + 1) + lo); }
475 if (ieq(sched_strat_,
"randomize"))
478 return sched_from_name(sched_strat_);
492 throw InputError(
"NetworkGenerator: scheduling strategy '" + s +
"' is not supported");
496 std::string choose_routing_strat() {
497 if (ieq(routing_strat_,
"randomize"))
498 return (rng_.next_int(2) + 1) == 1 ? std::string(
"Random") : std::string(
"Probabilities");
499 return routing_strat_;
508 lang::Distrib<T> choose_distribution() {
510 if (ieq(distribution_,
"Exp"))
id = 1;
511 else if (ieq(distribution_,
"Erlang"))
id = 2;
512 else if (ieq(distribution_,
"HyperExp"))
id = 3;
513 else id = rng_.next_int(3) + 1;
516 varying_service_rates_ ? std::pow(2.0,
double(rng_.next_int(13) - 6)) : 1.0;
520 const std::size_t k = std::size_t(std::pow(2.0,
double(rng_.next_int(7))));
523 const double scv = std::pow(2.0,
double(rng_.next_int(7)));
524 return fit_hyperexp(mean, scv);
534 static lang::Distrib<T> fit_hyperexp(
double mean,
double scv) {
537 num_traits<T>::from_double(dd.params[1]),
538 num_traits<T>::from_double(dd.params[2]));
545 void build(qn::Network<T>& model,
int num_queues,
int num_delays,
int num_oclass,
551 create_stations(model, num_queues, num_delays, num_oclass > 0);
552 create_classes(model, num_oclass, num_cclass);
553 set_service_processes(model);
554 define_topology(model, num_oclass > 0);
557 void create_stations(qn::Network<T>& model,
int num_queues,
int num_delays,
bool has_oclass) {
558 for (
int i = 0; i < num_queues; ++i) {
560 const std::size_t nd = model.add_queue(
"queue" + std::to_string(i + 1), sched);
563 const double servers = choose_num_servers();
564 model.set_number_of_servers(nd, servers);
565 stations_.push_back(nd);
567 for (
int i = 0; i < num_delays; ++i)
568 stations_.push_back(model.add_delay(
"delay" + std::to_string(i + 1)));
570 source_ = model.add_source(
"source");
571 sink_ = model.add_sink(
"sink");
575 void create_classes(qn::Network<T>& model,
int num_oclass,
int num_cclass) {
577 for (
int i = 0; i < num_oclass; ++i) {
578 const std::size_t r = model.add_open_class(
"OClass" + std::to_string(i + 1));
579 model.set_arrival(source_, r, choose_distribution());
580 classes_.push_back(r);
582 if (num_cclass > 0) {
587 const std::size_t ref =
588 stations_[std::size_t(rng_.next_int(
static_cast<int32_t
>(stations_.size())))];
589 for (
int i = 0; i < num_cclass; ++i)
590 classes_.push_back(model.add_closed_class(
"CClass" + std::to_string(i + 1),
591 choose_num_jobs(), ref));
595 void set_service_processes(qn::Network<T>& model) {
596 for (std::size_t c = 0; c < classes_.size(); ++c)
597 for (std::size_t s = 0; s < stations_.size(); ++s)
598 model.set_service(stations_[s], classes_[c], choose_distribution());
606 void define_topology(qn::Network<T>& model,
bool has_oclass) {
607 Matrix<double> topo = topology_fcn_(stations_.size(), rng_);
608 if (topo.rows() != stations_.size() || topo.cols() != stations_.size())
609 throw InputError(
"NetworkGenerator: the topology function returned an adjacency "
610 "matrix of the wrong order");
612 const Matrix<T> mask = gen_cs_mask(model);
616 std::vector<std::size_t> row_node = stations_;
618 const std::size_t S = stations_.size();
619 Matrix<double> grown(S + 2, S + 2, 0.0);
620 for (std::size_t i = 0; i < S; ++i)
621 for (std::size_t j = 0; j < S; ++j) grown(i, j) = topo(i, j);
622 grown(S, std::size_t(rng_.next_int(
static_cast<int32_t
>(S)))) = 1.0;
623 grown(std::size_t(rng_.next_int(
static_cast<int32_t
>(S))), S + 1) = 1.0;
625 row_node.push_back(source_);
626 row_node.push_back(sink_);
631 const std::size_t nrows = has_oclass ? stations_.size() + 1 : stations_.size();
633 qn::RoutingMatrix<T> P;
634 for (std::size_t i = 0; i < nrows; ++i) {
635 std::vector<std::size_t> dest;
636 for (std::size_t j = 0; j < topo.cols(); ++j)
637 if (topo(i, j) > 0.0) dest.push_back(row_node[j]);
638 std::sort(dest.begin(), dest.end());
639 const std::vector<std::size_t> outgoing =
640 add_outgoing_links(model, P, row_node[i], dest, mask);
641 set_routing_strategies(model, P, row_node[i], outgoing);
654 Matrix<T> gen_cs_mask(qn::Network<T>& model) {
655 const qn::NetworkStruct<T>& sn = model.raw_struct();
656 std::size_t num_open = 0, num_closed = 0;
657 for (std::size_t r = 0; r < sn.classes.size(); ++r) {
661 const std::size_t K = num_open + num_closed;
662 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
663 Matrix<T> mask(K, K, zero);
665 if (!multi_chain_cs_) {
666 for (std::size_t i = 0; i < num_open; ++i)
667 for (std::size_t j = 0; j < num_open; ++j) mask(i, j) = one;
668 for (std::size_t i = num_open; i < K; ++i)
669 for (std::size_t j = num_open; j < K; ++j) mask(i, j) = one;
673 std::vector<int> sizes;
676 static_cast<int>(num_open), rng_.next_int(
static_cast<int32_t
>(num_open)) + 1, rng_);
677 sizes.insert(sizes.end(), s.begin(), s.end());
679 if (num_closed > 0) {
680 const std::vector<int> s =
682 rng_.next_int(
static_cast<int32_t
>(num_closed)) + 1, rng_);
683 sizes.insert(sizes.end(), s.begin(), s.end());
685 std::size_t start = 0;
686 for (std::size_t c = 0; c < sizes.size(); ++c) {
687 const std::size_t end = start + std::size_t(sizes[c]);
688 for (std::size_t i = start; i < end; ++i)
689 for (std::size_t j = start; j < end; ++j) mask(i, j) = one;
696 Matrix<T> rand_class_switch_matrix(
const Matrix<T>& mask) {
697 const std::size_t K = mask.rows();
698 const T zero = num_traits<T>::from_int(0);
699 Matrix<T> C(K, K, zero);
700 for (std::size_t i = 0; i < K; ++i) {
701 std::vector<std::size_t> valid;
702 for (std::size_t j = 0; j < K; ++j)
703 if (num_traits<T>::to_double(mask(i, j)) > 0.0) valid.push_back(j);
705 C(i, i) = num_traits<T>::from_int(1);
709 for (std::size_t k = 0; k < valid.size(); ++k)
710 C(i, valid[k]) = num_traits<T>::from_double(probs[k]);
722 std::vector<std::size_t> add_outgoing_links(qn::Network<T>& model, qn::RoutingMatrix<T>& P,
724 const std::vector<std::size_t>& dest,
725 const Matrix<T>& mask) {
726 const qn::NetworkStruct<T>& sn = model.raw_struct();
727 const std::size_t K = sn.classes.size();
728 const T one = num_traits<T>::from_int(1);
729 std::vector<std::size_t> outgoing;
730 for (std::size_t d = 0; d < dest.size(); ++d) {
731 const std::size_t to = dest[d];
734 if (random_cs_nodes_ && rng_.next_boolean() && !from_source && !to_sink) {
735 const Matrix<T> C = rand_class_switch_matrix(mask);
736 const std::size_t cs = model.add_class_switch(
737 "cs_" + sn.nodes[from - 1].name +
"_" + sn.nodes[to - 1].name, C);
738 outgoing.push_back(cs);
739 for (std::size_t r = 1; r <= K; ++r) P.set(r, r, cs, to, one);
741 outgoing.push_back(to);
763 void set_routing_strategies(qn::Network<T>& model, qn::RoutingMatrix<T>& P, std::size_t from,
764 const std::vector<std::size_t>& outgoing) {
765 const qn::NetworkStruct<T>& sn = model.raw_struct();
766 const std::size_t K = sn.classes.size();
767 const T one = num_traits<T>::from_int(1);
770 for (std::size_t r = 1; r <= K; ++r) {
772 for (std::size_t k = 0; k < outgoing.size(); ++k) P.set(r, r, from, outgoing[k], one);
777 for (std::size_t r = 1; r <= K; ++r) {
779 std::vector<std::size_t> allowed;
780 for (std::size_t k = 0; k < outgoing.size(); ++k) {
782 allowed.push_back(outgoing[k]);
784 const std::string strat = choose_routing_strat();
785 if (strat ==
"Random") {
787 if (allowed.empty())
continue;
788 const double share = 1.0 / double(allowed.size());
789 for (std::size_t k = 0; k < allowed.size(); ++k)
790 P.set(r, r, from, allowed[k], num_traits<T>::from_double(share));
793 if (allowed.empty())
continue;
794 const std::vector<double> probs =
randfixedsumone(allowed.size(), rng_);
795 for (std::size_t k = 0; k < allowed.size(); ++k)
796 P.set(r, r, from, allowed[k], num_traits<T>::from_double(probs[k]));
814 static std::string why_invalid(
const qn::NetworkStruct<T>& sn) {
815 for (std::size_t c = 0; c < sn.nchains; ++c) {
816 if (c >= sn.inchain.size() || sn.inchain[c].empty())
continue;
817 const std::vector<std::size_t>& ic = sn.inchain[c];
819 for (std::size_t x = 0; x < ic.size(); ++x)
820 if (!std::isfinite(num_traits<T>::to_double(sn.classes[ic[x] - 1].population)))
822 if (!closed)
continue;
823 const std::size_t rs = sn.classes[ic[0] - 1].refstat;
824 if (rs == 0 || rs > sn.station_to_node.size())
continue;
825 const std::size_t node = sn.station_to_node[rs - 1];
826 if (c >= sn.nodevisits.size() || node == 0 || node > sn.nodevisits[c].rows())
continue;
828 for (std::size_t x = 0; x < ic.size(); ++x)
829 total += num_traits<T>::to_double(sn.nodevisits[c](node - 1, ic[x] - 1));
831 return "closed chain " + std::to_string(c + 1) +
832 " never visits its reference station '" + sn.nodes[node - 1].name +
"'";
834 return std::string();
841 static constexpr int MAX_SERVERS = 40;
842 static constexpr int HIGH_LO = 31, HIGH_HI = 40;
843 static constexpr int MED_LO = 11, MED_HI = 20;
844 static constexpr int LOW_LO = 1, LOW_HI = 5;
846 rng::JavaRandom rng_;
847 std::string model_name_ =
"nw";
848 std::string sched_strat_ =
"randomize";
849 std::string routing_strat_ =
"randomize";
850 std::string distribution_ =
"randomize";
851 std::string cclass_job_load_ =
"randomize";
852 bool varying_service_rates_ =
false;
853 bool multi_server_queues_ =
false;
854 bool random_cs_nodes_ =
false;
855 bool multi_chain_cs_ =
false;
856 std::function<Matrix<double>(std::size_t, rng::JavaRandom&)> topology_fcn_ =
857 [](std::size_t n, rng::JavaRandom& r) {
return rand_graph(n, r); };
859 std::vector<std::size_t> stations_;
860 std::vector<std::size_t> classes_;
861 std::size_t source_ = 0, sink_ = 0;