145 std::vector<LqnRelation<T>>
eqs;
156 std::ostringstream os;
157 for (std::size_t i = 0; i <
text.size(); ++i) {
169inline std::string lqn_num(
const T& x) {
171 if (std::isinf(d))
return d > 0 ?
"Inf" :
"-Inf";
172 if (std::isnan(d))
return "NaN";
173 if (d == std::floor(d) && std::fabs(d) < 1e15) {
174 std::ostringstream os;
175 os << static_cast<long long>(d);
178 std::ostringstream os;
184inline std::string lqn_num_d(
double d) {
185 if (std::isinf(d))
return d > 0 ?
"Inf" :
"-Inf";
186 if (std::isnan(d))
return "NaN";
187 if (d == std::floor(d) && std::fabs(d) < 1e15) {
188 std::ostringstream os;
189 os << static_cast<long long>(d);
192 std::ostringstream os;
199inline std::string lqn_elem_name(
const LqnStruct<T>& lqn, std::size_t idx) {
200 if (idx < lqn.hashnames.size() && !lqn.hashnames[idx].empty())
return lqn.hashnames[idx];
201 std::ostringstream os;
207inline std::string lqn_call_name(
const LqnStruct<T>& lqn, std::size_t cidx) {
208 if (cidx < lqn.callhashnames.size() && !lqn.callhashnames[cidx].empty())
209 return lqn.callhashnames[cidx];
210 std::ostringstream os;
216inline std::string lqn_term_name(
const LqnStruct<T>& lqn,
bool isentry, std::size_t idx) {
217 return isentry ? lqn_elem_name(lqn, idx) : lqn_call_name(lqn, idx);
222inline T lqn_sol_at(
const std::vector<T>& v, std::size_t i) {
224 const double d = num_traits<T>::to_double(v[i]);
225 if (!std::isnan(d))
return v[i];
227 return num_traits<T>::from_int(0);
232inline double lqn_mult_of(
const std::vector<double>& v, std::size_t i) {
233 return i < v.size() ? v[i] : std::numeric_limits<double>::quiet_NaN();
247inline SparseGraph<T> lqn_join_scaled_graph(
const LqnStruct<T>& lqn) {
248 SparseGraph<T> G = lqn.graph;
249 std::vector<std::size_t> andpre;
250 for (std::size_t i = 1; i < lqn.actpretype.size() && i <= G.n; ++i)
252 if (andpre.empty())
return G;
253 const T zero = num_traits<T>::from_int(0);
254 std::map<std::size_t, std::vector<std::size_t>> joined;
255 for (std::size_t i : andpre)
256 for (
const std::pair<std::size_t, T>& e : G.row[i])
257 if (e.second != zero) joined[e.first].push_back(i);
258 for (std::map<std::size_t, std::vector<std::size_t>>::const_iterator it = joined.begin();
259 it != joined.end(); ++it) {
260 if (it->second.size() < 2)
continue;
261 const T k = num_traits<T>::from_int(
static_cast<int>(it->second.size()));
262 for (std::size_t i : it->second) G.set(i, it->first, T(G.get(i, it->first) / k));
276inline std::vector<T> lqn_act_visits(
const LqnStruct<T>& lqn) {
277 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
278 std::vector<T> v(lqn.nidx + 1, zero);
279 const SparseGraph<T> G = lqn_join_scaled_graph(lqn);
280 for (std::size_t t = 1; t <= lqn.ntasks; ++t) {
281 const std::size_t tidx = lqn.tshift + t;
282 if (tidx >= lqn.actsof.size())
continue;
283 std::vector<std::size_t> A = lqn.actsof[tidx];
284 std::sort(A.begin(), A.end());
285 A.erase(std::unique(A.begin(), A.end()), A.end());
286 if (A.empty())
continue;
287 std::map<std::size_t, std::size_t> pos;
288 for (std::size_t i = 0; i < A.size(); ++i) pos[A[i]] = i;
290 Matrix<T> M(A.size(), A.size(), zero);
291 for (std::size_t i = 0; i < A.size(); ++i)
292 for (std::size_t j = 0; j < A.size(); ++j)
293 M(j, i) = T((i == j ? one : zero) - (A[i] <= G.n ? G.get(A[i], A[j]) : zero));
294 for (std::size_t eidx : lqn.entriesof[tidx]) {
295 std::vector<T> e0(A.size(), zero);
298 for (
const std::pair<std::size_t, T>& e : G.row[eidx]) {
299 std::map<std::size_t, std::size_t>::const_iterator it = pos.find(e.first);
300 if (it != pos.end() && e.second != zero) {
301 e0[it->second] = one;
308 for (std::size_t i = 0; i < A.size(); ++i) v[A[i]] = T(v[A[i]] + x[i]);
315inline T lqn_arrival_rate(
const LqnStruct<T>& lqn, std::size_t eidx) {
316 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
317 if (eidx >= lqn.has_arrival.size() || !lqn.has_arrival[eidx])
return zero;
318 if (eidx >= lqn.arrival.size() || lqn.arrival[eidx].disabled)
return zero;
319 const T m = lqn.arrival[eidx].mean;
320 const double d = num_traits<T>::to_double(m);
321 if (std::isfinite(d) && d > 1e-12)
return T(one / m);
327inline std::map<std::size_t, std::vector<std::size_t>> lqn_calls_into(
const LqnStruct<T>& lqn) {
328 std::map<std::size_t, std::vector<std::size_t>> out;
329 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx) {
330 const std::size_t dst = lqn.callpair_dst[cidx];
331 if (dst < 1 || dst > lqn.nidx)
continue;
332 out[lqn.parent[dst]].push_back(cidx);
338inline std::vector<std::size_t> lqn_incoming_calls(
const LqnStruct<T>& lqn, std::size_t eidx) {
339 std::vector<std::size_t> inc;
340 for (std::size_t cidx = 1; cidx <= lqn.ncalls; ++cidx)
341 if (lqn.callpair_dst[cidx] == eidx) inc.push_back(cidx);
351inline std::size_t lqn_entry_of_activity(
const LqnStruct<T>& lqn, std::size_t aidx) {
352 const std::size_t tidx = lqn.parent[aidx];
353 if (tidx >= lqn.entriesof.size())
return 0;
354 for (std::size_t eidx : lqn.entriesof[tidx]) {
355 if (eidx >= lqn.actsof.size())
continue;
356 const std::vector<std::size_t>& as = lqn.actsof[eidx];
357 if (std::find(as.begin(), as.end(), aidx) != as.end())
return eidx;
363inline bool lqn_has_phase2(
const LqnStruct<T>& lqn, std::size_t tidx) {
364 if (tidx >= lqn.entriesof.size())
return false;
365 for (std::size_t eidx : lqn.entriesof[tidx]) {
366 if (eidx >= lqn.actsof.size())
continue;
367 for (std::size_t aidx : lqn.actsof[eidx]) {
368 const std::size_t a = aidx - lqn.ashift;
369 if (a >= 1 && a < lqn.actphase.size() && lqn.actphase[a] > 1)
return true;
380inline T lqn_ref_thinktime(
const LqnStruct<T>& lqn, std::size_t tidx) {
381 const T zero = num_traits<T>::from_int(0);
382 if (tidx >= lqn.isref.size() || !lqn.isref[tidx])
return zero;
383 if (tidx >= lqn.think.size() || lqn.think[tidx].disabled)
return zero;
384 const double d = num_traits<T>::to_double(lqn.think[tidx].mean);
385 if (!std::isfinite(d) || d < 0)
return zero;
386 return lqn.think[tidx].mean;
392 std::vector<std::size_t> terms;
393 std::vector<bool> isentry;
394 bool present =
false;
409inline LqnPool lqn_thread_pool_branch(
const LqnStruct<T>& lqn, std::size_t tidx,
410 const std::vector<std::size_t>& cin) {
411 const T zero = num_traits<T>::from_int(0);
413 std::vector<std::size_t> ents;
414 if (tidx < lqn.entriesof.size()) ents = lqn.entriesof[tidx];
415 std::vector<std::size_t> arv;
416 for (std::size_t eidx : ents)
417 if (lqn_arrival_rate(lqn, eidx) != zero) arv.push_back(eidx);
418 for (std::size_t c : cin) {
419 p.terms.push_back(c);
420 p.isentry.push_back(
false);
422 for (std::size_t e : arv) {
423 p.terms.push_back(e);
424 p.isentry.push_back(
true);
426 if (tidx < lqn.isref.size() && lqn.isref[tidx]) {
428 for (std::size_t e : ents)
429 if (std::find(arv.begin(), arv.end(), e) == arv.end()) {
430 p.terms.push_back(e);
431 p.isentry.push_back(
true);
436 bool blocking =
false;
437 for (std::size_t c : cin)
444 }
else if (!cin.empty()) {
446 }
else if (!arv.empty()) {
447 p.branch =
"arrival";
468 using namespace detail;
470 const std::size_t nidx =
lqn.nidx;
474 const std::map<std::size_t, std::vector<std::size_t>> callsinto = lqn_calls_into(
lqn);
478 std::vector<T> calltput(
lqn.ncalls + 1, zero);
480 for (std::size_t cidx = 1; cidx <=
lqn.ncalls; ++cidx) {
481 const std::size_t src =
lqn.callpair_src[cidx];
482 if (src >= 1 && src <= nidx)
483 calltput[cidx] = T(lqn_sol_at(sol->tput, src) *
lqn.callproc_mean[cidx]);
488 for (std::size_t t = 1; t <=
lqn.ntasks; ++t) {
489 const std::size_t tidx =
lqn.tshift + t;
490 std::vector<std::size_t> cin;
491 std::map<std::size_t, std::vector<std::size_t>>::const_iterator ci = callsinto.find(tidx);
492 if (ci != callsinto.end()) cin = ci->second;
493 const LqnPool pool = lqn_thread_pool_branch(
lqn, tidx, cin);
494 if (!pool.present)
continue;
501 r.
terms = pool.terms;
503 r.
coeff.assign(pool.terms.size(), one);
504 r.
mult = lqn_mult_of<T>(
lqn.mult, tidx);
505 r.
maxmult = lqn_mult_of<T>(
lqn.maxmult, tidx);
506 r.
repl = (tidx <
lqn.repl.size() &&
lqn.repl[tidx] > 1.0) ?
lqn.repl[tidx] : 1.0;
508 r.
scaled = pool.branch !=
"inf";
510 r.
setup = tidx <
lqn.hassetup.size() &&
lqn.hassetup[tidx];
513 const T z = lqn_ref_thinktime(
lqn, tidx);
515 std::ostringstream lhs;
519 for (std::size_t i = 0; i < r.
terms.size(); ++i)
521 std::ostringstream txt;
522 txt << lhs.str() <<
" = " << lqn_num_d(r.
mult);
525 <<
") = " << lqn_num_d(r.
mult) <<
"*(1";
526 for (std::size_t i = 0; i < r.
terms.size(); ++i)
539 !std::isfinite(r.
mult) || r.
mult <= 0;
540 for (std::size_t i = 0; i < r.
terms.size(); ++i) {
543 b = T(lqn_sol_at(sol->tput, r.
terms[i]) * lqn_sol_at(sol->servt, r.
terms[i]));
545 b = T(calltput[r.
terms[i]] *
546 lqn_sol_at(sol->servt,
lqn.callpair_dst[r.
terms[i]]));
551 const T zt = lqn_sol_at(sol->thinkt, tidx);
552 r.
lhs = T(X * T(zt + z) + sumB);
561 out.
eqs.push_back(r);
565 for (std::size_t cidx = 1; cidx <=
lqn.ncalls; ++cidx) {
575 const std::size_t src =
lqn.callpair_src[cidx];
576 const T y =
lqn.callproc_mean[cidx];
577 r.
terms.push_back(src);
578 r.
coeff.push_back(y);
580 std::ostringstream txt;
581 txt <<
"X(" << r.
targetname <<
") = X(" << lqn_elem_name(
lqn, src) <<
") * " << lqn_num(y);
584 r.
lhs = calltput[cidx];
585 r.
rhs = T(lqn_sol_at(sol->tput, src) * y);
591 out.
eqs.push_back(r);
594 for (std::size_t e = 1; e <=
lqn.nentries; ++e) {
595 const std::size_t eidx =
lqn.eshift + e;
596 const std::vector<std::size_t> inc = lqn_incoming_calls(
lqn, eidx);
597 const T lam = lqn_arrival_rate(
lqn, eidx);
598 if (inc.empty() && lam == zero)
continue;
600 r.
kind =
"entryflow";
604 r.
coeff.assign(inc.size(), one);
606 std::ostringstream txt;
608 for (std::size_t i = 0; i < inc.size(); ++i)
609 txt << (i == 0 ?
" X(" :
" + X(") << lqn_call_name(
lqn, inc[i]) <<
")";
611 txt << (inc.empty() ?
" " :
" + ") << lqn_num(lam) <<
" (open arrival)";
614 r.
lhs = lqn_sol_at(sol->tput, eidx);
616 for (std::size_t c : inc) rhs = T(rhs + calltput[c]);
623 out.
eqs.push_back(r);
626 for (std::size_t a = 1; a <=
lqn.nacts; ++a) {
627 const std::size_t aidx =
lqn.ashift + a;
628 const std::size_t eidx = lqn_entry_of_activity(
lqn, aidx);
629 if (eidx == 0)
continue;
634 r.
terms.push_back(eidx);
637 std::ostringstream txt;
638 txt <<
"X(" << r.
targetname <<
") = X(" << lqn_elem_name(
lqn, eidx) <<
") * "
639 << lqn_num(out.
visits[aidx]);
642 r.
lhs = lqn_sol_at(sol->tput, aidx);
643 r.
rhs = T(lqn_sol_at(sol->tput, eidx) * out.
visits[aidx]);
649 out.
eqs.push_back(r);
653 for (std::size_t h = 1; h <=
lqn.nhosts; ++h) {
654 const std::size_t hidx =
lqn.hshift + h;
659 r.
mult = lqn_mult_of<T>(
lqn.mult, hidx);
660 r.
repl = (hidx <
lqn.repl.size() &&
lqn.repl[hidx] > 1.0) ?
lqn.repl[hidx] : 1.0;
662 if (hidx <
lqn.tasksof.size()) {
663 for (std::size_t tidx :
lqn.tasksof[hidx]) {
664 if (tidx >=
lqn.actsof.size())
continue;
665 for (std::size_t aidx :
lqn.actsof[tidx]) {
666 if (aidx >=
lqn.hostdem.size() ||
lqn.hostdem[aidx].disabled)
continue;
667 const T d =
lqn.hostdem[aidx].mean;
668 if (d == zero)
continue;
669 r.
terms.push_back(aidx);
670 r.
coeff.push_back(d);
678 if (!r.
scaled || !std::isfinite(m)) m = 1.0;
682 std::ostringstream txt;
683 for (std::size_t i = 0; i < r.
terms.size(); ++i) {
685 txt <<
"X(" << lqn_elem_name(
lqn, r.
terms[i]) <<
")*" << lqn_num(r.
coeff[i]);
687 std::string lhs = txt.str();
688 if (lhs.empty()) lhs =
"0";
689 std::ostringstream full;
690 full << lhs <<
" = " << lqn_num_d(m) <<
"*U(" << r.
targetname <<
")";
696 for (std::size_t i = 0; i < r.
terms.size(); ++i)
697 v = T(v + lqn_sol_at(sol->tput, r.
terms[i]) * r.
coeff[i]);
705 out.
eqs.push_back(r);
713 if (r.
kind ==
"little") {
716 for (std::size_t i = 0; i < r.
terms.size(); ++i)
718 }
else if (r.
kind ==
"callflow") {
720 }
else if (r.
kind ==
"hostutil") {
721 for (std::size_t i = 0; i < r.
terms.size(); ++i)
737 " little : rates and populations PER REPLICA (X = tput/repl, N = mult of one copy).");
739 " B(t,k) is the per-class utilization in JOB units; for a queueing task");
741 " B = mult*U with U in [0,1], at an infinite server B = U directly.");
743 " S(k) is the entry SERVICE time (phase 1 + phase 2), the thread hold time.");
745 " other : throughputs and utilizations as the solver reports them, totalled over "
748 " N(t) : lqn.mult. SolverLN iterates on njobs (interlocking corrections, maxmult "
750 out.
convention.push_back(
" replication), reported per record as mult/maxmult.");
754 std::ostringstream hdr;
755 hdr <<
"LQN balance equations: " <<
lqn.nhosts <<
" hosts, " <<
lqn.ntasks <<
" tasks, "
756 <<
lqn.nentries <<
" entries, " <<
lqn.nacts <<
" activities, " <<
lqn.ncalls
758 out.
text.push_back(hdr.str());
760 for (
const std::string& c : out.
convention) out.
text.push_back(c);
761 const char* kinds[5] = {
"little",
"callflow",
"entryflow",
"actflow",
"hostutil"};
762 const char* titles[5] = {
"thread-pool Little's law",
"call-flow balance",
"entry-flow balance",
763 "activity-flow balance",
"host utilization law"};
764 for (
int k = 0; k < 5; ++k) {
765 std::vector<std::size_t> sel;
766 for (std::size_t i = 0; i < out.
eqs.size(); ++i)
767 if (out.
eqs[i].kind == kinds[k]) sel.push_back(i);
768 if (sel.empty())
continue;
769 out.
text.push_back(
"");
771 std::ostringstream os;
772 os <<
"--- " << titles[k] <<
" (kind='" << kinds[k] <<
"', " << sel.size()
773 <<
" relations) ---";
774 out.
text.push_back(os.str());
776 for (std::size_t i : sel) {
778 std::ostringstream head;
779 head <<
"[" << (i + 1) <<
"] " << r.
targetname;
780 if (r.
kind ==
"little" || r.
kind ==
"hostutil")
781 head <<
" " << r.
branch <<
" mult=" << lqn_num_d(r.
mult)
782 <<
" repl=" << lqn_num_d(r.
repl);
783 else if (!r.
branch.empty())
785 out.
text.push_back(head.str());
786 std::string body = r.
text;
787 std::size_t start = 0;
788 while (start <= body.size()) {
789 const std::size_t nl = body.find(
'\n', start);
790 const std::string
line =
791 body.substr(start, nl == std::string::npos ? std::string::npos : nl - start);
793 if (nl == std::string::npos)
break;
796 std::vector<std::string> notes;
797 if (r.
phase2) notes.push_back(
"phase-2 tail on Z");
798 if (r.
setup) notes.push_back(
"setup charge on Z");
799 if (r.
degenerate) notes.push_back(
"DEGENERATE (not usable as a residual)");
800 if (r.
clamped) notes.push_back(
"SATURATED (equality unattainable, use a hinge)");
802 notes.push_back(
"not instantiated (pass un for the host utilization law)");
803 if (!notes.empty()) {
804 std::ostringstream os;
806 for (std::size_t j = 0; j < notes.size(); ++j) {
810 out.
text.push_back(os.str());
813 std::ostringstream os;
819 out.
text.push_back(os.str());
827 out.
text.push_back(
"");
828 std::ostringstream os;
830 os <<
"max |residual| over " << nd <<
" non-degenerate relations: "
832 : std::numeric_limits<double>::quiet_NaN());
833 out.
text.push_back(os.str());