5#ifndef LINE_LANG_LQN_LQN_READER_H
6#define LINE_LANG_LQN_LQN_READER_H
51#include <unordered_map>
78 std::string bound_to_entry;
80 std::size_t task_slot = 0;
81 std::vector<RawCall<T>> sync_calls;
82 std::vector<RawCall<T>> async_calls;
89 std::vector<std::pair<lang::RoutingStrategy, std::vector<std::string>>> call_groups;
96 std::vector<std::string> preacts;
97 std::vector<std::string> postacts;
98 std::vector<T> preparams;
99 std::vector<T> postparams;
100 bool has_quorum =
false;
101 std::size_t quorum = 0;
107 std::size_t task_slot = 0;
108 std::vector<std::string> reply_activities;
109 bool has_arrival =
false;
111 std::vector<std::string> fwd_dest;
112 std::vector<T> fwd_prob;
114 std::size_t cardinality = 0;
115 std::vector<T> popularity;
127 std::vector<std::string> names;
128 std::vector<T> coeffs;
140struct RawServerPool {
144 std::vector<std::string> compatible;
153 Distrib<T> thinktime;
154 std::size_t proc_slot = 0;
156 std::vector<std::pair<std::string, double>> fanout;
157 std::vector<std::pair<std::string, double>> fanin;
158 std::vector<RawPrecedence<T>> precedences;
159 std::vector<RawLinConRow<T>> linconrows;
161 std::vector<T> lincon_b;
163 std::size_t nitems = 0;
164 std::vector<int> itemcap;
167 Distrib<T> setuptime;
168 Distrib<T> delayofftime;
173 std::vector<T> lldscaling;
175 std::vector<T> cdscalingpeak;
177 std::vector<T> jdscalingpeak;
178 std::vector<RawServerPool<T>> pools;
196 double speed_factor = 1.0;
197 double quantum = 0.0;
219Distrib<T> host_demand(
const std::string& mean_s,
const std::string& scv_s) {
226 if constexpr (!num_traits<T>::has_transcendental) {
232 "lqn reader: an activity declares host-demand-cvsq " + scv_s +
233 ", whose APH / HyperExp fit needs a square root that exact arithmetic cannot "
234 "represent; solve this model in double precision");
252Distrib<T> setup_time(
const std::string& mean_s,
const std::string& scv_s) {
258 if constexpr (!num_traits<T>::has_transcendental) {
260 "lqn reader: a task declares a setup or delay-off scv " + scv_s +
261 ", whose APH fit needs a square root that exact arithmetic cannot represent; "
262 "solve this model in double precision");
280 for (std::size_t i = 0; i < s.size(); ++i) {
282 if (c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r')
continue;
283 key.push_back(
static_cast<char>(std::toupper(
static_cast<unsigned char>(c))));
285 if (key.empty() || key ==
"FIFO")
return R::FIFO;
286 if (key ==
"RR" || key ==
"RANDOM")
return R::RR;
287 if (key ==
"SFIFO")
return R::SFIFO;
288 if (key ==
"LRU")
return R::LRU;
289 if (key ==
"HLRU")
return R::HLRU;
290 if (key ==
"CLIMB")
return R::CLIMB;
291 if (key ==
"QLRU")
return R::QLRU;
292 throw UnsupportedError(
"lqn reader: unsupported cache replacement strategy '" + s +
"'");
306std::vector<T> popularity_from_lqnx(
const xml::Element* item, std::size_t cardinality) {
308 const std::vector<const xml::Element*> pops = item->child_tags(
"access-popularity");
313 for (std::size_t k = 0; k < cardinality; ++k)
314 out.push_back(num_traits<T>::from_double(1.0 /
double(cardinality ? cardinality : 1)));
317 const xml::Element* pe = pops[0];
318 const std::string family = pe->attr(
"name");
319 std::vector<std::string> raw;
320 for (
const xml::Element* par : pe->child_tags(
"parameter")) raw.push_back(par->attr(
"value"));
321 if (family ==
"Zipf") {
324 "lqn reader: an access popularity of class Zipf carries exactly two parameters, "
325 "the shape s then the item count n, but this one carries " +
326 std::to_string(raw.size()));
328 const std::size_t n =
static_cast<std::size_t
>(
dbl_from_decimal(raw[1],
double(cardinality)));
330 for (std::size_t k = 1; k <= n; ++k) h += std::pow(
double(k), -s);
331 for (std::size_t k = 1; k <= n; ++k)
332 out.push_back(num_traits<T>::from_double(std::pow(
double(k), -s) / h));
335 if (family.empty() || family ==
"DiscreteSampler") {
336 const std::size_t n =
337 (cardinality > 0 && raw.size() == 2 * cardinality) ? cardinality : raw.size();
342 "lqn reader: an access popularity is written as '" + family +
343 "', and the discrete families the .lqnx dialect carries are DiscreteSampler and Zipf");
351 const std::string& act_name) {
353 for (std::size_t i = 0; i < name.size(); ++i) {
354 const char c = name[i];
355 if (c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r')
continue;
356 key +=
static_cast<char>(std::toupper(
static_cast<unsigned char>(c)));
360 throw InputError(
"lqn reader: activity '" + act_name +
361 "' declares a call group with an unrecognized strategy '" + name +
362 "'; the dialect spells them RROBIN and JSQ");
373void read_call_groups(
const xml::Element* ae, RawActivity<T>& ac) {
374 const std::vector<const xml::Element*> groups = ae->child_tags(
"call-group");
375 for (std::size_t g = 0; g < groups.size(); ++g) {
376 std::vector<std::string> dests;
377 const std::vector<const xml::Element*> de = groups[g]->child_tags(
"dest");
378 for (std::size_t d = 0; d < de.size(); ++d) dests.push_back(de[d]->attr(
"name"));
379 ac.call_groups.push_back(
380 std::make_pair(callgroup_from_lqnx(groups[g]->attr(
"strategy"), ac.name), dests));
401 std::vector<detail::RawTask<T>>
tasks;
403 std::vector<detail::RawActivity<T>>
acts;
413 std::map<std::size_t, std::pair<Matrix<T>, std::vector<T>>>
proc_lincon;
427 std::map<std::size_t, std::vector<detail::RawServerPool<T>>>
proc_pools;
435 const std::vector<detail::RawProc>& procs = m.
procs;
436 const std::vector<detail::RawTask<T>>& tasks = m.
tasks;
437 const std::vector<detail::RawEntry<T>>& entries = m.
entries;
438 const std::vector<detail::RawActivity<T>>& acts = m.
acts;
445 l.
nacts = acts.size();
453 const std::size_t N = l.
nidx;
455 l.
names.assign(N + 1, {});
457 l.
type.assign(N + 1, LqnElement::HOST);
458 l.
parent.assign(N + 1, 0);
459 l.
sched.assign(NT + 1, SchedStrategy::NONE);
460 l.
mult.assign(NT + 1, 0.0);
462 l.
repl.assign(NT + 1, 1.0);
469 l.
isref.assign(NT + 1,
false);
470 l.
iscache.assign(NT + 1,
false);
472 l.
nitems.assign(N + 1, 0);
488 l.
actpretype.assign(N + 1, PrecedenceType::NONE);
498 std::unordered_map<std::string, std::size_t> byhash;
500 for (std::size_t p = 0; p < l.
nhosts; ++p) {
501 const std::size_t idx = p + 1;
502 l.
sched[idx] = procs[p].sched;
503 l.
mult[idx] = procs[p].mult;
504 l.
repl[idx] = procs[p].repl;
505 l.
names[idx] = procs[p].name;
507 l.
type[idx] = LqnElement::HOST;
517 for (std::size_t t = 0; t < l.
ntasks; ++t) {
518 const std::size_t idx = l.
tshift + t + 1;
519 l.
sched[idx] = tasks[t].sched;
521 l.
think[idx] = tasks[t].thinktime;
522 l.
mult[idx] = tasks[t].mult;
523 l.
repl[idx] = tasks[t].repl;
529 l.
names[idx] = tasks[t].name;
532 l.
nitems[idx] = tasks[t].nitems;
533 l.
itemcap[idx] = tasks[t].itemcap;
535 l.
iscache[idx] = tasks[t].nitems > 0;
540 l.
hassetup[idx] = !tasks[t].setuptime.disabled &&
545 : tasks[t].sched == SchedStrategy::REF ?
"R:"
548 l.
parent[idx] = tasks[t].proc_slot + 1;
550 l.
type[idx] = LqnElement::TASK;
554 for (std::size_t t = 0; t < l.
ntasks; ++t) {
555 const std::size_t tidx = l.
tshift + t + 1;
563 std::unordered_map<std::string, std::size_t> task_by_name;
564 for (std::size_t t = 0; t < l.
ntasks; ++t) {
565 const std::size_t tidx = l.
tshift + t + 1;
566 task_by_name[l.
names[tidx]] = tidx;
568 for (std::size_t t = 0; t < l.
ntasks; ++t) {
569 const std::size_t tidx = l.
tshift + t + 1;
570 for (std::size_t k = 0; k < tasks[t].fanout.size(); ++k) {
571 const std::unordered_map<std::string, std::size_t>::const_iterator it =
572 task_by_name.find(tasks[t].fanout[k].first);
573 if (it != task_by_name.end())
574 l.
fanout[std::make_pair(tidx, it->second)] = tasks[t].fanout[k].second;
576 for (std::size_t k = 0; k < tasks[t].fanin.size(); ++k) {
577 const std::unordered_map<std::string, std::size_t>::const_iterator it =
578 task_by_name.find(tasks[t].fanin[k].first);
579 if (it != task_by_name.end())
580 l.
fanin[std::make_pair(tidx, it->second)] = tasks[t].fanin[k].second;
584 for (std::size_t p = 1; p <= l.
nhosts; ++p)
585 for (std::size_t idx = 1; idx <= NT; ++idx)
586 if (l.
type[idx] == LqnElement::TASK && l.
parent[idx] == p) l.
tasksof[p].push_back(idx);
588 for (std::size_t e = 0; e < l.
nentries; ++e) {
589 const std::size_t idx = l.
eshift + e + 1;
590 l.
names[idx] = entries[e].name;
592 l.
nitems[idx] = entries[e].cardinality;
593 l.
itemproc[idx] = entries[e].popularity;
594 l.
hashnames[idx] = (entries[e].cardinality > 0 ?
"I:" :
"E:") + entries[e].name;
597 l.
arrival[idx] = entries[e].arrival;
598 const std::size_t tidx = l.
tshift + entries[e].task_slot + 1;
600 l.
graph.set(tidx, idx, one);
602 l.
type[idx] = LqnElement::ENTRY;
605 for (std::size_t a = 0; a < l.
nacts; ++a) {
606 const std::size_t idx = l.
ashift + a + 1;
607 l.
names[idx] = acts[a].name;
609 l.
hostdem[idx] = acts[a].hostdem;
610 l.
actthink[idx] = acts[a].thinktime;
611 const std::size_t tidx = l.
tshift + acts[a].task_slot + 1;
613 l.
actsof[tidx].push_back(idx);
614 l.
type[idx] = LqnElement::ACTIVITY;
619 auto find_entry = [&](
const std::string& name) -> std::size_t {
620 auto it = byhash.find(
"E:" + name);
621 if (it != byhash.end())
return it->second;
622 it = byhash.find(
"I:" + name);
623 return it == byhash.end() ? 0 : it->second;
625 auto find_act = [&](
const std::string& name) -> std::size_t {
626 auto it = byhash.find(
"A:" + name);
627 return it == byhash.end() ? 0 : it->second;
631 std::vector<std::pair<std::size_t, std::size_t>> loop_back_edges;
632 std::unordered_map<std::string, std::string> bound_entry_to_act;
633 std::size_t cidx = 0;
635 auto add_call = [&](std::size_t src, std::size_t dst_e,
CallType ct,
const T& mean,
636 const std::string& arrow) {
648 l.
calltype.push_back(CallType::NONE);
653 for (std::size_t t = 0; t < l.
ntasks; ++t) {
654 const std::size_t tidx = l.
tshift + t + 1;
655 for (std::size_t a = 0; a < l.
nacts; ++a) {
656 if (acts[a].task_slot != t)
continue;
657 const std::size_t aidx = l.
ashift + a + 1;
659 if (!acts[a].bound_to_entry.empty()) {
660 const std::size_t eidx = find_entry(acts[a].bound_to_entry);
662 l.
graph.set(eidx, aidx, one);
663 auto it = bound_entry_to_act.find(acts[a].bound_to_entry);
664 if (it != bound_entry_to_act.end())
665 throw InputError(
"lqn reader: activities '" + it->second +
"' and '" +
666 acts[a].name +
"' are both bound to entry '" +
667 acts[a].bound_to_entry +
"'");
668 bound_entry_to_act[acts[a].bound_to_entry] = acts[a].name;
672 for (
const auto& c : acts[a].sync_calls) {
673 const std::size_t te = find_entry(c.dest);
675 throw InputError(
"lqn reader: activity '" + acts[a].name +
676 "' calls unknown entry '" + c.dest +
"'");
677 const std::size_t tt = l.
parent[te];
679 throw InputError(
"lqn reader: an entry on a task cannot call another entry on "
680 "the same task ('" + acts[a].name +
"' -> '" + c.dest +
"')");
681 add_call(aidx, te, CallType::SYNC, c.mean,
"=>");
682 l.
callsof[aidx].push_back(cidx);
692 l.
graph.set(aidx, te, one);
694 for (
const auto& c : acts[a].async_calls) {
695 const std::size_t te = find_entry(c.dest);
697 throw InputError(
"lqn reader: activity '" + acts[a].name +
698 "' has an async call to unknown entry '" + c.dest +
"'");
699 const std::size_t tt = l.
parent[te];
701 throw InputError(
"lqn reader: async self-call from '" + acts[a].name +
"'");
702 add_call(aidx, te, CallType::ASYNC, c.mean,
"->");
703 l.
callsof[aidx].push_back(cidx);
713 l.
graph.set(aidx, te, one);
719 for (
const auto& g : acts[a].call_groups) {
723 for (
const std::string& nm : g.second) {
724 const std::size_t te = find_entry(nm);
726 throw InputError(
"lqn reader: activity '" + acts[a].name +
727 "' dispatches a call group to unknown entry '" + nm +
735 for (
const auto& pr : tasks[t].precedences) {
748 if (pr.pretype == PrecedenceType::PRE_AND && pr.has_quorum &&
751 bool resolved =
true;
752 for (
const std::string& nm : pr.preacts) {
753 const std::size_t ai = find_act(nm);
754 if (ai == 0) { resolved =
false;
break; }
757 for (
const std::string& nm : pr.postacts) {
758 const std::size_t ai = find_act(nm);
759 if (ai == 0) { resolved =
false;
break; }
764 std::size_t quorum_count = 0;
765 if (pr.pretype == PrecedenceType::PRE_AND) {
766 if (pr.preacts.empty())
767 throw InputError(
"lqn reader: PRE_AND precedence with no pre activities in "
768 "task '" + tasks[t].name +
"'");
769 quorum_count = (pr.has_quorum && pr.quorum >= 1 && pr.quorum <= pr.preacts.size())
773 for (std::size_t pa = 0; pa < pr.preacts.size(); ++pa) {
774 const std::size_t preaidx = find_act(pr.preacts[pa]);
776 throw InputError(
"lqn reader: precedence names unknown activity '" +
777 pr.preacts[pa] +
"'");
778 switch (pr.posttype) {
779 case PrecedenceType::POST_OR:
780 for (std::size_t po = 0; po < pr.postacts.size(); ++po) {
781 const std::size_t postaidx = find_act(pr.postacts[po]);
782 l.
graph.set(preaidx, postaidx, pr.postparams[po]);
787 case PrecedenceType::POST_AND:
788 for (std::size_t po = 0; po < pr.postacts.size(); ++po) {
789 const std::size_t postaidx = find_act(pr.postacts[po]);
790 l.
graph.set(preaidx, postaidx, one);
795 case PrecedenceType::POST_LOOP: {
797 const T counts = pr.postparams.empty() ? one : pr.postparams[0];
798 const std::size_t enda = pr.postacts.size() - 1;
799 const std::size_t loopentry = find_act(pr.preacts[0]);
800 const std::size_t loopstart = find_act(pr.postacts[0]);
801 const std::size_t loopend = find_act(pr.postacts[enda]);
803 l.
graph.set(loopentry, loopstart, counts);
804 l.
graph.set(loopentry, loopend, T(one - counts));
805 std::size_t cur = loopstart;
806 for (std::size_t po = 1; po + 1 < pr.postacts.size(); ++po) {
807 const std::size_t pi = find_act(pr.postacts[po]);
808 l.
graph.set(cur, pi, one);
812 l.
graph.set(cur, loopend, one);
815 std::size_t cur = loopentry;
816 for (std::size_t po = 0; po + 1 < pr.postacts.size(); ++po) {
817 const std::size_t pi = find_act(pr.postacts[po]);
818 l.
graph.set(cur, pi, one);
822 loop_back_edges.emplace_back(cur, loopstart);
823 l.
graph.set(cur, loopstart, T(one - one / counts));
824 l.
graph.set(cur, loopend, T(one / counts));
830 for (std::size_t po = 0; po < pr.postacts.size(); ++po) {
831 const std::size_t postaidx = find_act(pr.postacts[po]);
833 throw InputError(
"lqn reader: precedence names unknown activity '" +
834 pr.postacts[po] +
"'");
835 l.
graph.set(preaidx, postaidx, one);
838 if (quorum_count > 0) l.
actquorum[postaidx] = quorum_count;
847 for (std::size_t e = 0; e < l.
nentries; ++e) {
848 const std::size_t eidx = l.
eshift + e + 1;
849 const std::size_t src_t = l.
parent[eidx];
850 for (std::size_t f = 0; f < entries[e].fwd_dest.size(); ++f) {
851 const std::size_t te = find_entry(entries[e].fwd_dest[f]);
853 throw InputError(
"lqn reader: entry '" + entries[e].name +
854 "' forwards to unknown entry '" + entries[e].fwd_dest[f] +
"'");
855 if (l.
parent[te] == src_t)
856 throw InputError(
"lqn reader: entry '" + entries[e].name +
857 "' forwards to an entry on the same task");
858 add_call(eidx, te, CallType::FWD, entries[e].fwd_prob[f],
"~>");
860 l.
graph.set(eidx, te, one);
870 for (std::size_t cidx2 = 1; cidx2 <= l.
tshift + l.
ntasks; ++cidx2) {
871 const bool ishost = cidx2 <= l.
nhosts;
872 const std::vector<std::size_t>& colIdx =
874 const char* colwhat = ishost ?
"tasks on this host" :
"entries of this task";
876 const std::vector<T>* rawb =
nullptr;
877 const std::vector<detail::RawLinConRow<T>>* rows =
nullptr;
879 typename std::map<std::size_t, std::vector<detail::RawLinConRow<T>>>::const_iterator
882 typename std::map<std::size_t,
883 std::pair<Matrix<T>, std::vector<T>>>::const_iterator ip =
886 rawA = &ip->second.first;
887 rawb = &ip->second.second;
890 const detail::RawTask<T>& rt = tasks[cidx2 - l.
tshift - 1];
891 rows = &rt.linconrows;
895 const std::size_t ncols = colIdx.size();
896 std::vector<std::vector<T>> Arows;
897 std::vector<T> brows;
898 if (rawA !=
nullptr && rawA->
rows() > 0) {
899 if (rawA->
cols() != ncols)
900 throw InputError(
"lqn reader: admission constraint on '" + l.
names[cidx2] +
901 "' has " + std::to_string(rawA->
cols()) +
" columns but there are " +
902 std::to_string(ncols) +
" " + colwhat);
903 for (std::size_t k = 0; k < rawA->
rows(); ++k) {
904 std::vector<T> row(ncols, zero);
905 for (std::size_t j = 0; j < ncols; ++j) row[j] = (*rawA)(k, j);
906 Arows.push_back(row);
907 brows.push_back(k < rawb->size() ? (*rawb)[k] : zero);
910 if (rows !=
nullptr) {
911 for (std::size_t r = 0; r < rows->size(); ++r) {
912 std::vector<T> row(ncols, zero);
913 for (std::size_t k = 0; k < (*rows)[r].names.size(); ++k) {
914 std::size_t pos = ncols;
915 for (std::size_t j = 0; j < ncols; ++j)
916 if (l.
names[colIdx[j]] == (*rows)[r].names[k]) pos = j;
918 throw InputError(
"lqn reader: admission constraint on '" + l.
names[cidx2] +
919 "' names '" + (*rows)[r].names[k] +
920 "', which is not one of the " + colwhat);
921 row[pos] = T(row[pos] + (*rows)[r].coeffs[k]);
923 Arows.push_back(row);
924 brows.push_back((*rows)[r].cap);
927 if (Arows.empty())
continue;
929 for (std::size_t k = 0; k < Arows.size(); ++k)
930 for (std::size_t j = 0; j < ncols; ++j) A(k, j) = Arows[k][j];
939 for (std::size_t pidx = 1; pidx <= l.
tshift + l.
ntasks; ++pidx) {
940 const bool ishost = pidx <= l.
nhosts;
941 const std::vector<std::size_t>& colIdx =
943 const char* colwhat = ishost ?
"tasks on this host" :
"entries of this task";
944 const std::vector<detail::RawServerPool<T>>* raw =
nullptr;
946 typename std::map<std::size_t,
947 std::vector<detail::RawServerPool<T>>>::const_iterator it =
949 if (it != m.
proc_pools.end()) raw = &it->second;
951 raw = &tasks[pidx - l.
tshift - 1].pools;
953 if (raw ==
nullptr || raw->empty())
continue;
954 const std::size_t ncols = colIdx.size();
957 "' but the element has no operand to serve");
960 for (std::size_t t2 = 0; t2 < raw->size(); ++t2) {
961 const detail::RawServerPool<T>& rp = (*raw)[t2];
962 sp.
names.push_back(rp.name);
963 sp.
counts.push_back(rp.count);
964 sp.
rates.push_back(rp.rate);
965 for (std::size_t k = 0; k < rp.compatible.size(); ++k) {
967 for (std::size_t j = 0; j < ncols; ++j) {
968 if (l.
names[colIdx[j]] == rp.compatible[k]) {
975 throw InputError(
"lqn reader: server pool '" + rp.name +
"' on '" +
976 l.
names[pidx] +
"' names '" + rp.compatible[k] +
977 "', which is not one of the " + colwhat);
982 for (std::size_t j = 0; j < ncols; ++j) {
984 for (std::size_t t2 = 0; t2 < sp.
npools(); ++t2)
985 if (sp.
compat(t2, j) != zero) served =
true;
988 "' is compatible with no server pool, so it can never be served");
994 for (std::size_t e = 1; e <= l.
nentries; ++e) {
995 const std::size_t eidx = l.
eshift + e;
997 for (std::size_t s : l.
graph.succ(eidx))
998 if (s > l.
ashift) bound =
true;
1001 if (!bound)
throw InputError(
"An entry does not have any boundTo activity.");
1010 for (std::size_t e = 0; e < entries.size(); ++e) {
1011 for (
const std::string& rname : entries[e].reply_activities) {
1012 const std::size_t aidx = find_act(rname);
1013 if (aidx == 0)
continue;
1014 for (std::size_t succ : l.
graph.succ(aidx)) {
1015 if (succ <= l.
ashift)
continue;
1017 throw InputError(
"Unsupported replyTo in non-terminal activity.");
1023 for (std::size_t tidx = 1; tidx <= NT; ++tidx) {
1024 if (l.
sched[tidx] != SchedStrategy::INF)
continue;
1025 if (l.
type[tidx] != LqnElement::TASK)
continue;
1027 for (std::size_t c = 1; c <= NT; ++c)
1032 for (std::size_t idx = 1; idx <= NT; ++idx) l.
isref[idx] = l.
sched[idx] == SchedStrategy::REF;
1036 for (std::size_t i = 1; i <= N; ++i) {
1037 if (l.
type[i] != LqnElement::TASK || l.
isref[i])
continue;
1038 std::vector<std::size_t> to_flip;
1039 for (
const auto& e : l.
dag.row[i])
1040 if (l.
type[e.first] == LqnElement::ENTRY && e.second != zero)
1041 to_flip.push_back(e.first);
1042 for (std::size_t j : to_flip) {
1044 l.
dag.set(j, i, one);
1047 for (
const auto& be : loop_back_edges) l.
dag.erase(be.first, be.second);
1050 for (std::size_t e = 1; e <= l.
nentries; ++e) {
1051 const std::size_t eidx = l.
eshift + e;
1052 const std::size_t tidx = l.
parent[eidx];
1053 std::vector<bool> visited(N + 1,
false);
1054 std::vector<std::size_t> stack{eidx};
1055 visited[eidx] =
true;
1056 while (!stack.empty()) {
1057 const std::size_t v = stack.back();
1059 for (std::size_t w : l.
graph.succ(v))
1065 std::vector<std::size_t> found;
1066 for (std::size_t i = 1; i <= N; ++i)
1067 if (visited[i] && l.
type[i] == LqnElement::ACTIVITY && l.
parent[i] == tidx)
1076 for (std::size_t i = 1; i <= N; ++i)
1077 for (
const auto& ed : l.
dag.row[i])
1078 if (ed.second != zero) in.
dag(i - 1, ed.first - 1) = 1.0;
1081 in.
isref.assign(N,
false);
1090 for (std::size_t i = 1; i <= N; ++i) {
1091 const double m = i <= NT ? l.
mult[i] : std::numeric_limits<double>::infinity();
1094 switch (l.
type[i]) {
1105 for (std::size_t i = 1; i <= NT; ++i)
1106 l.
maxmult[i] = mm[i - 1].infinite ? std::numeric_limits<double>::infinity()
1111 for (std::size_t e = 1; e <= l.
nentries; ++e) {
1112 const std::size_t eidx = l.
eshift + e;
1113 bool sync =
false, async =
false;
1114 for (std::size_t c = 1; c <= l.
ncalls; ++c) {
1116 if (l.
calltype[c] == CallType::SYNC) sync =
true;
1117 if (l.
calltype[c] == CallType::ASYNC) async =
true;
1121 "' is called both synchronously and asynchronously");
1144inline std::string fmt_num(
double v) {
1146 std::snprintf(buf,
sizeof(buf),
"%g", v);
1147 return std::string(buf);
1151inline double attr_num(
const std::string& s,
double dflt) {
1152 if (s.empty())
return dflt;
1154 return std::stod(s);
1155 }
catch (
const std::exception&) {
1156 return std::numeric_limits<double>::quiet_NaN();
1161inline bool iequals(
const std::string& a,
const std::string& b) {
1162 if (a.size() != b.size())
return false;
1163 for (std::size_t i = 0; i < a.size(); ++i)
1164 if (std::tolower(
static_cast<unsigned char>(a[i])) !=
1165 std::tolower(
static_cast<unsigned char>(b[i])))
1180inline void validate_input_model(
const xml::Element& doc) {
1181 const double tol = 1e-6;
1182 std::vector<std::string> proc_names;
1183 std::vector<std::string> task_names;
1184 std::vector<std::string> entry_names;
1185 std::vector<std::string> entry_owner;
1186 std::vector<bool> is_ref_entry;
1187 std::vector<std::string> call_dests;
1188 std::vector<std::string> reply_entries;
1189 bool has_ref_task =
false;
1190 bool has_open_arrival =
false;
1192 for (
const xml::Element* pe : doc.by_tag(
"processor")) {
1193 const std::string proc_name = pe->attr(
"name");
1194 if (std::find(proc_names.begin(), proc_names.end(), proc_name) != proc_names.end())
1195 throw InputError(
"Duplicate processor name \"" + proc_name +
"\".");
1196 proc_names.push_back(proc_name);
1198 for (
const xml::Element* te : pe->by_tag(
"task")) {
1199 const std::string task_name = te->attr(
"name");
1200 if (std::find(task_names.begin(), task_names.end(), task_name) != task_names.end())
1201 throw InputError(
"Duplicate task name \"" + task_name +
"\".");
1202 task_names.push_back(task_name);
1203 const bool is_ref = iequals(te->attr(
"scheduling"),
"ref");
1204 has_ref_task = has_ref_task || is_ref;
1206 const std::vector<const xml::Element*> entry_els = te->by_tag(
"entry");
1207 if (entry_els.empty())
1208 throw InputError(
"Task \"" + task_name +
"\" has no entries.");
1209 for (
const xml::Element* ee : entry_els) {
1210 const std::string entry_name = ee->attr(
"name");
1211 if (std::find(entry_names.begin(), entry_names.end(), entry_name) !=
1213 throw InputError(
"Duplicate entry name \"" + entry_name +
"\".");
1214 entry_names.push_back(entry_name);
1215 entry_owner.push_back(task_name);
1216 is_ref_entry.push_back(is_ref);
1218 const double arrival_rate =
1219 attr_num(ee->attr(
"open-arrival-rate"), std::numeric_limits<double>::quiet_NaN());
1220 if (arrival_rate > 0.0) {
1221 has_open_arrival =
true;
1223 throw InputError(
"Entry \"" + entry_name +
"\" belongs to reference task \"" +
1224 task_name +
"\" and cannot have open arrivals.");
1227 const std::vector<const xml::Element*> fwd_els = ee->by_tag(
"forwarding");
1228 if (is_ref && !fwd_els.empty())
1229 throw InputError(
"Entry \"" + entry_name +
"\" belongs to reference task \"" +
1230 task_name +
"\" and cannot forward requests.");
1231 double fwd_total = 0.0;
1232 for (
const xml::Element* fe : fwd_els) {
1233 const double prob = attr_num(fe->attr(
"prob"), 1.0);
1234 if (std::isnan(prob) || prob < 0.0 || prob > 1.0)
1235 throw InputError(
"Forwarding from entry \"" + entry_name +
"\" to entry \"" +
1236 fe->attr(
"dest") +
"\" has an invalid probability of " +
1237 fmt_num(prob) +
".");
1240 if (fwd_total > 1.0 + tol)
1242 "\" has a total forwarding probability of " +
1243 fmt_num(fwd_total) +
".");
1247 std::vector<std::string> act_names;
1248 for (
const xml::Element* ae : te->by_tag(
"activity")) {
1249 if (ae->parent ==
nullptr)
continue;
1250 if (ae->parent->name !=
"task-activities" &&
1251 ae->parent->name !=
"entry-phase-activities")
1253 const std::string act_name = ae->attr(
"name");
1254 if (std::find(act_names.begin(), act_names.end(), act_name) != act_names.end())
1255 throw InputError(
"Duplicate activity name \"" + act_name +
"\" in task \"" +
1257 act_names.push_back(act_name);
1260 for (
const xml::Element* ce : te->by_tag(
"synch-call"))
1261 call_dests.push_back(ce->attr(
"dest"));
1262 for (
const xml::Element* ce : te->by_tag(
"asynch-call"))
1263 call_dests.push_back(ce->attr(
"dest"));
1264 for (
const xml::Element* fe : te->by_tag(
"forwarding"))
1265 call_dests.push_back(fe->attr(
"dest"));
1267 for (
const xml::Element* oe : te->by_tag(
"post-OR")) {
1268 double branch_total = 0.0;
1269 for (
const xml::Element* be : oe->by_tag(
"activity")) {
1270 const double prob = attr_num(be->attr(
"prob"), 1.0);
1271 if (std::isnan(prob) || prob < 0.0 || prob > 1.0)
1272 throw InputError(
"Activity \"" + be->attr(
"name") +
"\" in task \"" +
1273 task_name +
"\" has an invalid branch probability of " +
1274 fmt_num(prob) +
".");
1275 branch_total += prob;
1277 if (std::fabs(branch_total - 1.0) > tol)
1278 throw InputError(
"Branch probabilities of an OR-fork in task \"" + task_name +
1279 "\" sum to " + fmt_num(branch_total) +
" instead of 1.");
1282 for (
const xml::Element* re : te->by_tag(
"reply-entry"))
1283 reply_entries.push_back(re->attr(
"name"));
1287 for (
const std::string& dest : call_dests) {
1288 const std::vector<std::string>::const_iterator it =
1289 std::find(entry_names.begin(), entry_names.end(), dest);
1290 if (it == entry_names.end())
continue;
1291 const std::size_t idx =
static_cast<std::size_t
>(it - entry_names.begin());
1292 if (is_ref_entry[idx])
1293 throw InputError(
"Entry \"" + entry_names[idx] +
"\" belongs to reference task \"" +
1294 entry_owner[idx] +
"\" and cannot receive requests.");
1297 for (
const std::string& reply_name : reply_entries) {
1298 const std::vector<std::string>::const_iterator it =
1299 std::find(entry_names.begin(), entry_names.end(), reply_name);
1300 if (it == entry_names.end())
continue;
1301 const std::size_t idx =
static_cast<std::size_t
>(it - entry_names.begin());
1302 if (is_ref_entry[idx])
1303 throw InputError(
"Entry \"" + entry_names[idx] +
"\" belongs to reference task \"" +
1304 entry_owner[idx] +
"\" and cannot be replied to.");
1307 if (!has_ref_task && !has_open_arrival)
1308 throw InputError(
"The model has no reference task and no open arrivals.");
1317 std::vector<detail::RawProc>& procs = m.
procs;
1318 std::vector<detail::RawTask<T>>& tasks = m.
tasks;
1319 std::vector<detail::RawEntry<T>>& entries = m.
entries;
1320 std::vector<detail::RawActivity<T>>& acts = m.
acts;
1324 detail::validate_input_model(*doc);
1328 const std::vector<const xml::Element*> proc_els = doc->by_tag(
"processor");
1331 pr.name = pe->attr(
"name");
1332 const std::string psched = pe->attr(
"scheduling");
1337 if (pr.sched == SchedStrategy::INF) {
1340 pr.mult = std::numeric_limits<double>::infinity();
1344 const std::size_t proc_slot = procs.size();
1345 procs.push_back(pr);
1348 detail::RawTask<T> tk;
1349 tk.name = te->attr(
"name");
1350 const std::string tsched = te->attr(
"scheduling");
1353 if (tk.sched == SchedStrategy::INF) {
1354 tk.mult = std::numeric_limits<double>::infinity();
1358 const std::string think_s = te->attr(
"think-time");
1362 tk.proc_slot = proc_slot;
1367 tk.fanout.push_back(
1368 std::make_pair(fe->attr(
"dest"),
dbl_from_decimal(fe->attr(
"value"), 1.0)));
1371 std::make_pair(fe->attr(
"source"),
dbl_from_decimal(fe->attr(
"value"), 1.0)));
1380 tk.setuptime = detail::setup_time<T>(se->attr(
"mean"), se->attr(
"scv"));
1382 tk.delayofftime = detail::setup_time<T>(se->attr(
"mean"), se->attr(
"scv"));
1392 const std::string items_s = ce->attr(
"items");
1394 tk.nitems = items_d > 0.0 ?
static_cast<std::size_t
>(items_d) : 1;
1395 tk.replacestrat = detail::replacement_from_lqnx(ce->attr(
"replacement"));
1399 tk.itemcap.push_back(cap_d > 0.0 ?
static_cast<int>(cap_d) : 1);
1404 if (tk.itemcap.empty()) tk.itemcap.push_back(1);
1406 const std::size_t task_slot = tasks.size();
1407 tasks.push_back(tk);
1410 detail::RawEntry<T> en;
1411 en.name = ee->attr(
"name");
1412 en.task_slot = task_slot;
1413 const std::string arr_s = ee->attr(
"open-arrival-rate");
1414 if (!arr_s.empty()) {
1417 en.has_arrival =
true;
1422 en.fwd_dest.push_back(fe->attr(
"dest"));
1423 const std::string ps = fe->attr(
"prob");
1432 en.cardinality = card_d > 0.0 ?
static_cast<std::size_t
>(card_d) : 1;
1433 en.popularity = detail::popularity_from_lqnx<T>(ie2, en.cardinality);
1435 const std::size_t entry_slot = entries.size();
1436 entries.push_back(en);
1438 const std::vector<const xml::Element*> epa = ee->by_tag(
"entry-phase-activities");
1441 std::map<int, std::string> by_phase;
1442 for (
const xml::Element* ae : epa[0]->by_tag(
"activity")) {
1443 const int phase =
static_cast<int>(
dbl_from_decimal(ae->attr(
"phase"), 1.0));
1444 detail::RawActivity<T> ac;
1445 ac.name = ae->attr(
"name");
1446 ac.hostdem = detail::host_demand<T>(ae->attr(
"host-demand-mean"),
1447 ae->attr(
"host-demand-cvsq"));
1448 const std::string att = ae->attr(
"think-time");
1450 ac.thinktime = att_d > 0.0
1453 ac.bound_to_entry = phase == 1 ? entries[entry_slot].name : std::string();
1455 ac.task_slot = task_slot;
1457 ac.sync_calls.push_back(
1460 ac.async_calls.push_back(
1462 detail::read_call_groups<T>(ae, ac);
1463 by_phase[phase] = ac.name;
1467 for (
auto it = by_phase.begin(); it != by_phase.end(); ++it) {
1468 auto nx = std::next(it);
1469 if (nx == by_phase.end())
break;
1470 detail::RawPrecedence<T> pr;
1471 pr.pretype = PrecedenceType::PRE_SEQ;
1472 pr.posttype = PrecedenceType::POST_SEQ;
1473 pr.preacts.push_back(it->second);
1474 pr.postacts.push_back(nx->second);
1475 tasks[task_slot].precedences.push_back(pr);
1477 if (!by_phase.empty() && by_phase.count(1))
1478 entries[entry_slot].reply_activities.push_back(by_phase[1]);
1482 const std::vector<const xml::Element*> tal = te->by_tag(
"task-activities");
1487 if (ae->parent != ta)
continue;
1488 detail::RawActivity<T> ac;
1489 ac.name = ae->attr(
"name");
1490 ac.hostdem = detail::host_demand<T>(ae->attr(
"host-demand-mean"),
1491 ae->attr(
"host-demand-cvsq"));
1492 const std::string att = ae->attr(
"think-time");
1496 ac.bound_to_entry = ae->attr(
"bound-to-entry");
1498 ac.task_slot = task_slot;
1500 ac.sync_calls.push_back(
1503 ac.async_calls.push_back(
1505 detail::read_call_groups<T>(ae, ac);
1510 detail::RawPrecedence<T> pr;
1512 if (!pe2->child_tags(
"pre").empty()) {
1514 pr.pretype = PrecedenceType::PRE_SEQ;
1515 }
else if (!pe2->child_tags(
"pre-AND").empty()) {
1517 pr.pretype = PrecedenceType::PRE_AND;
1518 }
else if (!pe2->child_tags(
"pre-OR").empty()) {
1520 pr.pretype = PrecedenceType::PRE_OR;
1522 throw InputError(
"lqn reader: <precedence> without a pre element");
1525 pr.preacts.push_back(ae->attr(
"name"));
1526 if (pr.pretype == PrecedenceType::PRE_OR)
1529 if (pr.pretype == PrecedenceType::PRE_SEQ && pr.preacts.size() > 1)
1530 pr.preacts.resize(1);
1531 if (pr.pretype == PrecedenceType::PRE_AND) {
1532 const std::string q = pre->
attr(
"quorum");
1534 pr.has_quorum =
true;
1540 if (!pe2->child_tags(
"post").empty()) {
1542 pr.posttype = PrecedenceType::POST_SEQ;
1543 }
else if (!pe2->child_tags(
"post-AND").empty()) {
1545 pr.posttype = PrecedenceType::POST_AND;
1546 }
else if (!pe2->child_tags(
"post-OR").empty()) {
1548 pr.posttype = PrecedenceType::POST_OR;
1549 }
else if (!pe2->child_tags(
"post-LOOP").empty()) {
1551 pr.posttype = PrecedenceType::POST_LOOP;
1552 }
else if (!pe2->child_tags(
"post-CACHE").empty()) {
1554 pr.posttype = PrecedenceType::POST_CACHE;
1563 pr.postacts.push_back(ae->attr(
"name"));
1564 if (pr.posttype == PrecedenceType::POST_OR)
1566 if (pr.posttype == PrecedenceType::POST_LOOP)
1569 if (pr.posttype == PrecedenceType::POST_CACHE) {
1577 std::vector<std::string> hit, miss, unlabelled;
1578 const std::vector<const xml::Element*> aes = post->
by_tag(
"activity");
1579 for (std::size_t k = 0; k < aes.size(); ++k) {
1580 const std::string res = aes[k]->attr(
"cache-result");
1582 hit.push_back(pr.postacts[k]);
1583 else if (res ==
"miss")
1584 miss.push_back(pr.postacts[k]);
1586 unlabelled.push_back(pr.postacts[k]);
1588 if (!hit.empty() || !miss.empty()) {
1590 pr.postacts.insert(pr.postacts.end(), miss.begin(), miss.end());
1591 pr.postacts.insert(pr.postacts.end(), unlabelled.begin(),
1594 if (pr.postacts.size() < 2)
1596 "lqn reader: a <post-CACHE> branches on a hit and a miss, so it "
1597 "names two activities");
1599 if (pr.posttype == PrecedenceType::POST_LOOP)
1600 pr.postacts.push_back(post->
attr(
"end"));
1601 tasks[task_slot].precedences.push_back(pr);
1605 const std::string ename = re->attr(
"name");
1606 std::size_t slot = entries.size();
1607 for (std::size_t s = 0; s < entries.size(); ++s)
1608 if (entries[s].name == ename) {
1612 if (slot == entries.size())
1613 throw InputError(
"lqn reader: <reply-entry> names unknown entry '" + ename +
1616 entries[slot].reply_activities.push_back(ra->attr(
"name"));
UnsupportedError(const std::string &what)
Decimal literal -> T, without a detour through double when T is exact.
The moment fitters the reference distributions carry as STATIC FACTORIES: Erlang.fitMeanAndOrder,...
The exception types the port throws.
LayeredNetworkStruct, the flattened description of a layered queueing network.
Maximum sustainable multiplicity (concurrency level) of every element of a layered software network.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
PrecedenceType
Activity precedence kinds, with the values of MATLAB ActivityPrecedenceType.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
SchedStrategy sched_from_lqnx(const std::string &s)
Parse the scheduling attribute of an .lqnx processor or task.
CallType
Call kinds, with the values of MATLAB CallType.
Distrib< T > aph_fit_mean_scv(const T &mean, const T &scv)
APH.fitMeanAndSCV(MEAN, SCV), through mam::aph_fit_mean_scv.
Distrib< T > hyperexp_fit_mean_scv(const T &mean, const T &scv)
HyperExp.fitMeanAndSCV(MEAN, SCV), which is map_hyperexp at p = 0.99 read back as (p,...
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
ReplacementStrategy
Cache replacement policies, with the values of MATLAB ReplacementStrategy.
LqnModel< T > read_lqnx_model(const std::string &path)
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
LqnStruct< T > read_lqnx(const std::string &path)
Read a .lqnx model.
std::vector< Multiplicity< T > > lsn_max_multiplicity(const LsnInput< T > &lsn)
Maximum sustainable multiplicity (concurrency level) of every element of a layered software network.
std::unique_ptr< Element > parse_file(const std::string &path)
Read and parse a file.
double dbl_from_decimal(const std::string &s, double fallback)
Parse a decimal literal as a plain double (multiplicities, populations, tolerances).
T num_from_decimal(const std::string &s)
Parse a decimal literal into T.
static constexpr double FineTol
void set(std::size_t i, std::size_t j)
void resize(std::size_t nn)
static Distrib exp_rate(const T &r)
static Distrib disabled_dist()
static Distrib det(const T &m)
static Distrib immediate()
The Immediate singleton.
static Distrib exp_mean(const T &m)
One routed call group: an activity, the strategy that picks among its targets, and the target ENTRIES...
std::vector< std::size_t > targets
absolute entry indices, in declaration order
lang::RoutingStrategy strategy
std::size_t caller
absolute index of the dispatching activity
The intermediate model, and the second stage that flattens it.
std::vector< detail::RawTask< T > > tasks
std::vector< detail::RawActivity< T > > acts
std::map< std::size_t, std::vector< T > > proc_jdscalingpeak
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
std::vector< detail::RawProc > procs
std::map< std::size_t, CdScaling< T > > proc_jdscaling
std::map< std::size_t, std::vector< T > > proc_cdscalingpeak
std::map< std::size_t, std::pair< Matrix< T >, std::vector< T > > > proc_lincon
std::map< std::size_t, std::vector< detail::RawLinConRow< T > > > proc_linconrows
Admission constraints declared on a HOST, by 0-based processor slot.
std::map< std::size_t, CdScaling< T > > proc_cdscaling
std::map< std::size_t, std::vector< T > > proc_lldscaling
Queue-dependent service rates and compatibility pools declared on a HOST, by 0-based processor slot.
std::vector< detail::RawEntry< T > > entries
One activity precedence of a task, with its activities resolved to indices.
std::vector< std::size_t > preacts
absolute activity indices
std::vector< T > preparams
PRE_OR shares, or a PRE_AND quorum.
std::vector< T > postparams
POST_OR probabilities or the POST_LOOP count.
std::vector< std::size_t > postacts
absolute activity indices
std::vector< Distrib< T > > hostdem
(nidx+1) host demand per activity (Immediate elsewhere)
std::vector< std::vector< T > > jdscalingpeak
(tshift+ntasks+1)
std::vector< int > actphase
(nacts+1) phase of each activity, 1-based by act
std::vector< CallType > calltype
(ncalls+1)
std::vector< std::size_t > callpair_dst
(ncalls+1) called entry
std::vector< std::vector< LqnPrecedence< T > > > precedences
Activity precedences of each task, as DECLARED, indexed by the task's absolute index.
std::vector< std::string > callhashnames
(ncalls+1)
std::vector< Distrib< T > > setuptime
Setup tasks: the server powers down when idle and pays to restart.
std::map< std::pair< std::size_t, std::size_t >, double > fanout
Fan-out and fan-in, keyed by task element index, absent = 0.
std::vector< bool > hassetup
(tshift+ntasks+1)
std::vector< LqnElement > type
(nidx+1)
std::vector< std::size_t > nitems
Cache tasks and item entries.
std::vector< Distrib< T > > actthink
(nidx+1) activity think time
std::vector< std::vector< T > > lldscaling
Queue-dependent service rates declared on a layer server (a host or a task), by element index,...
std::vector< double > mult
(tshift+ntasks+1) declared multiplicity, may be Inf
std::vector< SchedStrategy > sched
(tshift+ntasks+1)
std::vector< std::size_t > callpair_src
(ncalls+1) calling activity (entry for FWD)
std::vector< std::size_t > parent
(nidx+1) host of a task, task of an entry/activity
std::vector< std::vector< T > > lincon_b
std::vector< ServerPools< T > > pools
(tshift+ntasks+1)
std::vector< T > callproc_mean
(ncalls+1) mean number of calls
std::vector< std::size_t > actquorum
(nidx+1) AND-join quorum, on the join target
std::vector< bool > iscache
(tshift+ntasks+1)
std::vector< bool > has_arrival
(nidx+1) entry with an open arrival
std::vector< std::vector< std::size_t > > callsof
(nidx+1) call indices issued by an activity
std::vector< std::string > hashnames
(nidx+1) name prefixed by kind: P:/T:/R:/E:/A:
std::vector< std::string > callnames
(ncalls+1)
std::vector< CdScaling< T > > cdscaling
(tshift+ntasks+1)
std::vector< std::vector< int > > itemcap
(tshift+ntasks+1)
std::vector< std::vector< std::size_t > > entriesof
(tshift+ntasks+1)
std::vector< double > repl
(tshift+ntasks+1) replication
std::vector< std::string > names
(nidx+1) declared name
std::vector< Matrix< T > > lincon_A
Admission constraint A n <= b on the layer station of a host or task.
SparseGraph< T > dag
graph with entry-task edges reversed and loop back-edges removed
std::vector< Distrib< T > > think
(nidx+1) task think time
std::vector< std::vector< std::size_t > > tasksof
(nhosts+1)
std::vector< bool > isref
(tshift+ntasks+1)
std::vector< PrecedenceType > actpretype
(nidx+1)
std::vector< std::vector< std::size_t > > actsof
(ashift+1) by task and by entry
std::map< std::pair< std::size_t, std::size_t >, double > fanin
std::vector< PrecedenceType > actposttype
(nidx+1)
std::vector< LqnCallGroup > callgroups
Synchronous calls DISPATCHED AS A GROUP, lsn.callgroups.
std::vector< std::vector< T > > cdscalingpeak
(tshift+ntasks+1)
std::vector< Distrib< T > > delayofftime
std::vector< Distrib< T > > arrival
(nidx+1) open arrival process of an entry
std::vector< std::vector< T > > itemproc
(nidx+1) popularity pmf
std::vector< CdScaling< T > > jdscaling
(tshift+ntasks+1)
std::vector< ReplacementStrategy > replacestrat
(tshift+ntasks+1)
std::vector< double > maxmult
(tshift+ntasks+1) sustainable multiplicity
SparseGraph< T > graph
element call/precedence graph, edge weights are branch shares
SparseGraph< T > taskgraph
task-to-task calls
Heterogeneous server pools declared on a layer server, the twin of the nservertypes / servertypenames...
Matrix< T > compat
(npools x noperands), nonzero = eligible
std::vector< double > counts
(npools) servers held by each pool
std::vector< std::string > names
(npools) declared pool name
std::vector< T > rates
(npools) per-pool rate multiplier
std::size_t npools() const
static Multiplicity finite(const T &v)
static Multiplicity inf()
std::vector< const Element * > by_tag(const std::string &tag) const
Descendant-or-self search excluding self, in document order.
std::string attr(const std::string &key) const
Attribute value, or the empty string when absent (org.w3c.dom semantics).
std::vector< const Element * > child_tags(const std::string &tag) const
Direct children with the given tag, in document order.
A minimal XML DOM: read for the .lqnx interchange format, write for the JMT .jsimg and ....