LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
lqn_focus.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_LQN_LQN_FOCUS_H
6#define LINE_API_LQN_LQN_FOCUS_H
7
8/**
9 * @file
10 * @ingroup api_lqn
11 * Focused layered performance model by aggregation (FSPT).
12 *
13 * Port of `matlab/src/api/lqn/lqn_focus.m`, on the raw model `LqnModel<double>`
14 * (the form LqnBuilder and the .lqnx reader produce). Reduces a layered network
15 * to a focused model (FM) that keeps a set of preserved tasks and hosts as they
16 * are and replaces every other task by one aggregate task per dependency group,
17 * following Islam, Petriu and Woodside, "Focused Layered Performance Modelling
18 * by Aggregation", ACM TOMPECS 7(2-4), 2022.
19 *
20 * The original model (OM) is solved once with SolverLN to obtain the
21 * saturations S = U/m of every task and host. The preserved set is chosen by
22 * the `sat` (forced elements plus every resource with S > Smax/r), `acc`
23 * (forced elements plus the i most saturated resources, i grown until the
24 * reference-task throughput error is below x) or `manual` (forced elements,
25 * the most saturated resource and `preserve`) strategy. A preserved task
26 * preserves its host and a preserved host all of its tasks. Tasks using
27 * features FSPT does not aggregate (priorities, AND forks/joins, second
28 * phases, asynchronous calls, forwarding, open arrivals, replication, fan-in
29 * or fan-out, setup/delay-off and activity think times) are always preserved.
30 *
31 * Non-preserved tasks with the same set Pi(T) of preserved tasks depending on
32 * them form a group, replaced by one aggregate task with one entry and one
33 * activity (eqs. 7-11). A host shared by several groups is split into shadow
34 * hosts of speed s_H (1 - saturation by the other groups) (eq. 15), or
35 * preserved when that capacity is not positive. Aggregate processors have unit
36 * speed, the aggregate speed of eq. 9 being folded into the aggregate demand.
37 */
38
39#include <algorithm>
40#include <cctype>
41#include <cmath>
42#include <cstddef>
43#include <functional>
44#include <limits>
45#include <map>
46#include <set>
47#include <stdexcept>
48#include <string>
49#include <utility>
50#include <vector>
51
56
57namespace line {
58namespace lqn {
59
60/** Options of lqn_focus, as the MATLAB name/value pairs. */
62 std::string strategy = "sat"; ///< "sat", "acc" or "manual"
63 double r = 3.3; ///< SAT saturation ratio threshold
64 double x = 0.02; ///< ACC relative throughput error target
65 std::vector<std::string> preserve; ///< task or host names forced into the focus
66 /** Solver of a finalized struct; empty uses ln::SolverLN with default options. */
67 std::function<ln::LnSolution<double>(const LqnStruct<double>&)> solver;
68 bool validate = true; ///< solve the FM and report errors (always on for "acc")
69};
70
71/** One dependency group and its aggregate task. */
73 std::vector<std::string> tasks, pi;
75 double mult = 0, hostMult = 0, hostSpeed = 1, demand = 0, rate = 0;
76 std::vector<std::string> callDests;
77 std::vector<double> callMeans;
78};
79
80/** OM and FM metrics of the retained tasks, entries and hosts. */
82 std::vector<std::string> names, types;
83 std::vector<double> X_om, X_fm, R_om, R_fm, Q_om, Q_fm, U_om, U_fm;
84};
85
86/** Focused model and diagnostics, fields named as the MATLAB info struct. */
90 std::vector<bool> preservedMask; ///< (ntasks + nhosts) tasks then hosts
91 std::vector<std::string> taskNames, hostNames;
92 std::vector<double> taskSat, hostSat;
93 std::vector<LqnFocusGroup> groups;
94 /** {original, focused}; focused is "" for a dropped element. */
95 std::vector<std::pair<std::string, std::string>> taskMap, entryMap, activityMap, hostMap;
96 double SR = 0;
97 double trustedRange[2] = {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()};
98 double FT = 0, F = 0;
99 double err = std::numeric_limits<double>::quiet_NaN();
100 double errResp = std::numeric_limits<double>::quiet_NaN();
101 std::vector<std::string> refTasks;
102 std::vector<double> errRef, errRespRef;
104 std::string strategy;
105 double r = 0, x = 0;
106 std::vector<std::vector<double>> accHistory; ///< rows [i, nPreserved, F, err]
107 double Smax = 0;
108};
109
110namespace focus_detail {
111
112using namespace ::line::lang;
113
114inline double nan_() { return std::numeric_limits<double>::quiet_NaN(); }
115
116inline bool is_prio(SchedStrategy s) {
117 std::string n = sched_to_text(s);
118 for (auto& c : n) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
119 return n.find("prio") != std::string::npos || n == "hol";
120}
121
122inline double dmean(const Distrib<double>& d) {
123 if (d.disabled || d.type == ProcessType::IMMEDIATE || d.type == ProcessType::DISABLED) return 0.0;
124 return std::isnan(d.mean) ? 0.0 : d.mean;
125}
126
127inline bool has_time(const Distrib<double>& d) { return dmean(d) > GlobalConstants::FineTol; }
128
129struct Call {
130 std::size_t src, dst;
131 double mean;
132};
133
134struct Inv {
135 std::size_t nH = 0, nT = 0, nE = 0, nA = 0;
136 std::vector<std::string> hName, tName, eName, aName;
137 std::vector<double> hMult, hSpeed, tMult, aDem;
138 std::vector<std::size_t> tHost, eTask, aTask;
139 std::vector<bool> tIsRef, forcedT, forcedH;
140 std::vector<Call> sync, async, fwd;
141 std::vector<std::vector<bool>> reach;
142};
143
144struct Om {
145 std::vector<double> hQ, hU, tQ, tU, tX, eQ, eU, eR, eX, aX;
146};
147
148struct Sat {
149 std::vector<std::vector<double>> SHT;
150 std::vector<double> SH, ST;
151};
152
153[[noreturn]] inline void fail(const std::string& msg) { throw std::invalid_argument("lqn_focus: " + msg); }
154
155inline std::size_t index_of(const std::vector<std::string>& v, const std::string& s) {
156 for (std::size_t i = 0; i < v.size(); ++i)
157 if (v[i] == s) return i;
158 return v.size();
159}
160
162 Inv inv;
163 inv.nH = m.procs.size();
164 inv.nT = m.tasks.size();
165 inv.nE = m.entries.size();
166 inv.nA = m.acts.size();
167 for (const auto& p : m.procs) inv.hName.push_back(p.name);
168 for (const auto& t : m.tasks) inv.tName.push_back(t.name);
169 for (const auto& e : m.entries) inv.eName.push_back(e.name);
170 for (const auto& a : m.acts) inv.aName.push_back(a.name);
171 for (std::size_t h = 0; h < inv.nH; ++h) {
172 if (m.proc_linconrows.count(h) || m.proc_lincon.count(h) || m.proc_lldscaling.count(h) ||
173 m.proc_cdscaling.count(h) || m.proc_jdscaling.count(h) || m.proc_pools.count(h))
174 fail("Element \"" + inv.hName[h] + "\" uses admission constraints, load-dependent scaling or "
175 "server pools, which lqn_focus does not support.");
176 }
177 inv.hMult.resize(inv.nH);
178 inv.hSpeed.resize(inv.nH);
179 inv.forcedH.assign(inv.nH, false);
180 for (std::size_t h = 0; h < inv.nH; ++h) {
181 const auto& p = m.procs[h];
182 inv.hMult[h] = p.mult;
183 inv.hSpeed[h] = p.speed_factor;
184 inv.forcedH[h] = p.repl > 1 || is_prio(p.sched);
185 }
186 inv.tMult.resize(inv.nT);
187 inv.tHost.resize(inv.nT);
188 inv.tIsRef.assign(inv.nT, false);
189 inv.forcedT.assign(inv.nT, false);
190 for (std::size_t t = 0; t < inv.nT; ++t) {
191 const auto& tk = m.tasks[t];
192 if (tk.nitems > 0) fail("Task \"" + tk.name + "\" is a cache task, which lqn_focus does not support.");
193 if (!tk.linconrows.empty() || tk.lincon_A.rows() > 0 || !tk.lldscaling.empty() ||
194 static_cast<bool>(tk.cdscaling) || static_cast<bool>(tk.jdscaling) || !tk.pools.empty())
195 fail("Element \"" + tk.name + "\" uses admission constraints, load-dependent scaling or "
196 "server pools, which lqn_focus does not support.");
197 if (tk.proc_slot >= inv.nH) fail("Task \"" + tk.name + "\" has no host.");
198 inv.tMult[t] = tk.mult;
199 inv.tHost[t] = tk.proc_slot;
200 inv.tIsRef[t] = tk.sched == SchedStrategy::REF;
201 bool f = inv.tIsRef[t] || tk.repl > 1 || tk.priority != 0 || is_prio(tk.sched) ||
202 has_time(tk.setuptime) || has_time(tk.delayofftime) || !tk.fanin.empty() || !tk.fanout.empty();
203 for (const auto& pr : tk.precedences)
204 if (pr.pretype == PrecedenceType::PRE_AND || pr.posttype == PrecedenceType::POST_AND) f = true;
205 inv.forcedT[t] = f;
206 }
207 inv.eTask.resize(inv.nE);
208 for (std::size_t e = 0; e < inv.nE; ++e) {
209 const auto& en = m.entries[e];
210 if (en.cardinality > 0) fail("Entry \"" + en.name + "\" is not a plain entry, which lqn_focus does not support.");
211 inv.eTask[e] = en.task_slot;
212 }
213 auto dest = [&](const std::string& n) {
214 std::size_t e2 = index_of(inv.eName, n);
215 if (e2 == inv.nE) fail("Call destination \"" + n + "\" is not an entry of the model.");
216 return e2;
217 };
218 inv.aTask.resize(inv.nA);
219 inv.aDem.resize(inv.nA);
220 for (std::size_t a = 0; a < inv.nA; ++a) {
221 const auto& ac = m.acts[a];
222 const std::size_t t = ac.task_slot;
223 inv.aTask[a] = t;
224 inv.aDem[a] = dmean(ac.hostdem);
225 if (!ac.call_groups.empty())
226 fail("Activity \"" + ac.name + "\" uses call groups, which lqn_focus does not support.");
227 if (ac.phase > 1 || has_time(ac.thinktime)) inv.forcedT[t] = true;
228 for (const auto& c : ac.sync_calls) inv.sync.push_back({a, dest(c.dest), c.mean});
229 for (const auto& c : ac.async_calls) {
230 const std::size_t e2 = dest(c.dest);
231 inv.async.push_back({a, e2, c.mean});
232 inv.forcedT[t] = true;
233 inv.forcedT[inv.eTask[e2]] = true;
234 }
235 }
236 for (std::size_t e = 0; e < inv.nE; ++e) {
237 const auto& en = m.entries[e];
238 if (en.has_arrival) inv.forcedT[inv.eTask[e]] = true;
239 for (std::size_t k = 0; k < en.fwd_dest.size(); ++k) {
240 const std::size_t e2 = dest(en.fwd_dest[k]);
241 inv.fwd.push_back({e, e2, en.fwd_prob[k]});
242 inv.forcedT[inv.eTask[e]] = true;
243 inv.forcedT[inv.eTask[e2]] = true;
244 }
245 }
246 auto& R = inv.reach;
247 R.assign(inv.nT, std::vector<bool>(inv.nT, false));
248 for (const auto& c : inv.sync) R[inv.aTask[c.src]][inv.eTask[c.dst]] = true;
249 for (const auto& c : inv.async) R[inv.aTask[c.src]][inv.eTask[c.dst]] = true;
250 for (const auto& c : inv.fwd) R[inv.eTask[c.src]][inv.eTask[c.dst]] = true;
251 for (std::size_t k = 0; k < inv.nT; ++k)
252 for (std::size_t i = 0; i < inv.nT; ++i)
253 if (R[i][k])
254 for (std::size_t j = 0; j < inv.nT; ++j)
255 if (R[k][j]) R[i][j] = true;
256 for (std::size_t t = 0; t < inv.nT; ++t)
257 if (R[t][t]) fail("The task call graph has a cycle, which FSPT does not support.");
258 return inv;
259}
260
261inline double z(double v) { return std::isnan(v) ? 0.0 : v; }
262
263inline Om solve(const LqnModel<double>& m, const LqnFocusOptions& opt, const Inv& inv) {
264 const LqnStruct<double> l = lqn_finalize(m);
266 Om om;
267 for (std::size_t h = 0; h < inv.nH; ++h) {
268 om.hQ.push_back(s.QN[1 + h]);
269 om.hU.push_back(z(s.UN[1 + h]));
270 }
271 for (std::size_t t = 0; t < inv.nT; ++t) {
272 const std::size_t i = l.tshift + 1 + t;
273 om.tQ.push_back(z(s.QN[i]));
274 om.tU.push_back(s.UN[i]);
275 om.tX.push_back(z(s.TN[i]));
276 }
277 for (std::size_t e = 0; e < inv.nE; ++e) {
278 const std::size_t i = l.eshift + 1 + e;
279 om.eQ.push_back(s.QN[i]);
280 om.eU.push_back(s.UN[i]);
281 om.eR.push_back(s.RN[i]);
282 om.eX.push_back(z(s.TN[i]));
283 }
284 for (std::size_t a = 0; a < inv.nA; ++a) om.aX.push_back(z(s.TN[l.ashift + 1 + a]));
285 return om;
286}
287
288inline Sat saturation(const Inv& inv, const Om& om) {
289 Sat s;
290 s.SHT.assign(inv.nH, std::vector<double>(inv.nT, 0.0));
291 s.SH.assign(inv.nH, 0.0);
292 s.ST.assign(inv.nT, 0.0);
293 for (std::size_t a = 0; a < inv.nA; ++a) {
294 const std::size_t t = inv.aTask[a], h = inv.tHost[t];
295 if (std::isfinite(inv.hMult[h])) s.SHT[h][t] += om.aX[a] * inv.aDem[a] / inv.hSpeed[h] / inv.hMult[h];
296 }
297 for (std::size_t h = 0; h < inv.nH; ++h)
298 for (std::size_t t = 0; t < inv.nT; ++t) s.SH[h] += s.SHT[h][t];
299 for (std::size_t t = 0; t < inv.nT; ++t)
300 if (std::isfinite(inv.tMult[t]) && !inv.tIsRef[t]) s.ST[t] = om.tQ[t] / inv.tMult[t];
301 return s;
302}
303
304inline void closure(const Inv& inv, std::vector<bool>& PT, std::vector<bool>& PH) {
305 for (std::size_t t = 0; t < inv.nT; ++t)
306 if (PT[t]) PH[inv.tHost[t]] = true;
307 for (std::size_t t = 0; t < inv.nT; ++t)
308 if (PH[inv.tHost[t]]) PT[t] = true;
309}
310
311inline std::string unique_name(std::set<std::string>& all, std::string nm) {
312 while (all.count(nm)) nm += "_";
313 all.insert(nm);
314 return nm;
315}
316
317inline std::string map_name(int g, const std::string& name, const std::vector<LqnFocusGroup>& grp, bool task) {
318 if (g == 0) return name;
319 if (g < 0) return "";
320 return task ? grp[g - 1].aggTask : grp[g - 1].aggActivity;
321}
322
323inline LqnFocusResult build(const LqnModel<double>& model, const Inv& inv, const Om& om, const Sat& sat,
324 std::vector<bool> PT, std::vector<bool> PH, const LqnFocusOptions& opt, bool validate) {
325 const double tol = GlobalConstants::CoarseTol;
326 const std::size_t nH = inv.nH, nT = inv.nT, nE = inv.nE, nA = inv.nA;
327 std::vector<int> gid;
328 std::vector<std::size_t> pidx;
329 std::vector<std::vector<double>> shadow;
330 std::size_t ng = 0;
331 while (true) {
332 closure(inv, PT, PH);
333 pidx.clear();
334 for (std::size_t t = 0; t < nT; ++t)
335 if (PT[t]) pidx.push_back(t);
336 gid.assign(nT, 0);
337 std::vector<std::vector<bool>> keys;
338 for (std::size_t t = 0; t < nT; ++t) {
339 if (PT[t]) continue;
340 std::vector<bool> key;
341 bool any = false;
342 for (std::size_t p : pidx) {
343 key.push_back(inv.reach[p][t]);
344 any = any || inv.reach[p][t];
345 }
346 if (!any) {
347 gid[t] = -1;
348 continue;
349 }
350 auto it = std::find(keys.begin(), keys.end(), key);
351 if (it == keys.end()) {
352 keys.push_back(key);
353 gid[t] = static_cast<int>(keys.size());
354 } else {
355 gid[t] = static_cast<int>(it - keys.begin()) + 1;
356 }
357 }
358 ng = keys.size();
359 shadow.assign(ng + 1, std::vector<double>(nH, 1.0));
360 bool changed = false;
361 for (std::size_t h = 0; h < nH && !changed; ++h) {
362 if (PH[h]) continue;
363 std::set<int> gs;
364 for (std::size_t t = 0; t < nT; ++t)
365 if (inv.tHost[t] == h && gid[t] > 0) gs.insert(gid[t]);
366 if (gs.size() < 2) continue;
367 for (int g : gs) {
368 double frac = 1.0;
369 for (std::size_t t = 0; t < nT; ++t)
370 if (inv.tHost[t] == h && gid[t] > 0 && gid[t] != g) frac -= sat.SHT[h][t];
371 if (frac <= tol) {
372 PH[h] = true;
373 changed = true;
374 break;
375 }
376 shadow[g][h] = frac;
377 }
378 }
379 if (!changed) break;
380 }
381
382 std::set<std::string> all;
383 for (const auto* v : {&inv.hName, &inv.tName, &inv.eName, &inv.aName}) all.insert(v->begin(), v->end());
384 std::vector<LqnFocusGroup> grp(ng);
385 for (std::size_t g = 1; g <= ng; ++g) {
386 LqnFocusGroup& d = grp[g - 1];
387 std::set<std::size_t> hg;
388 std::size_t first = nT;
389 for (std::size_t t = 0; t < nT; ++t) {
390 if (gid[t] != static_cast<int>(g)) continue;
391 if (first == nT) first = t;
392 d.tasks.push_back(inv.tName[t]);
393 d.mult += inv.tMult[t];
394 hg.insert(inv.tHost[t]);
395 }
396 for (std::size_t p : pidx)
397 if (inv.reach[p][first]) d.pi.push_back(inv.tName[p]);
398 const std::string k = std::to_string(g);
399 d.aggTask = unique_name(all, "AggT" + k);
400 d.aggEntry = unique_name(all, "AggE" + k);
401 d.aggActivity = unique_name(all, "AggA" + k);
402 d.aggHost = unique_name(all, "AggP" + k);
403 for (std::size_t h : hg) d.hostMult += inv.hMult[h];
404 if (std::isfinite(d.hostMult)) {
405 double s = 0;
406 for (std::size_t h : hg) s += inv.hSpeed[h] * shadow[g][h] * inv.hMult[h];
407 d.hostSpeed = s / d.hostMult;
408 }
409 }
410 std::vector<std::string> fmEntry(nE);
411 for (std::size_t e = 0; e < nE; ++e) {
412 const int g = gid[inv.eTask[e]];
413 fmEntry[e] = g == 0 ? inv.eName[e] : (g > 0 ? grp[g - 1].aggEntry : std::string());
414 }
415 for (const auto& c : inv.sync) {
416 const int gs = gid[inv.aTask[c.src]], gd = gid[inv.eTask[c.dst]];
417 if (gd > 0 && gs != gd && gs >= 0) grp[gd - 1].rate += om.aX[c.src] * c.mean;
418 }
419 for (std::size_t g = 1; g <= ng; ++g) {
420 LqnFocusGroup& d = grp[g - 1];
421 const double rateA = std::max(d.rate, GlobalConstants::Zero);
422 double work = 0, time = 0;
423 for (std::size_t a = 0; a < nA; ++a) {
424 const std::size_t t = inv.aTask[a];
425 if (gid[t] != static_cast<int>(g)) continue;
426 const std::size_t h = inv.tHost[t];
427 const double w = om.aX[a] * inv.aDem[a];
428 work += w;
429 time += w / (inv.hSpeed[h] * shadow[g][h]);
430 }
431 d.demand = std::isfinite(d.hostMult) ? work / rateA / d.hostSpeed : time / rateA;
432 for (const auto& c : inv.sync) {
433 if (gid[inv.aTask[c.src]] == static_cast<int>(g) && gid[inv.eTask[c.dst]] != static_cast<int>(g)) {
434 const std::string& dn = fmEntry[c.dst];
435 std::size_t k = index_of(d.callDests, dn);
436 if (k == d.callDests.size()) {
437 d.callDests.push_back(dn);
438 d.callMeans.push_back(0.0);
439 }
440 d.callMeans[k] += om.aX[c.src] * c.mean / rateA;
441 }
442 }
443 }
444
445 // focused raw model: preserved elements copied verbatim, slots remapped
447 fm.name = model.name + "_focus";
448 std::vector<std::size_t> hslot(nH, nH), tslot(nT, nT);
449 for (std::size_t h = 0; h < nH; ++h) {
450 if (!PH[h]) continue;
451 hslot[h] = fm.procs.size();
452 fm.procs.push_back(model.procs[h]);
453 }
454 for (std::size_t t = 0; t < nT; ++t) {
455 if (!PT[t]) continue;
456 tslot[t] = fm.tasks.size();
457 auto tk = model.tasks[t];
458 tk.proc_slot = hslot[inv.tHost[t]];
459 fm.tasks.push_back(tk);
460 }
461 std::vector<std::size_t> aggTslot(ng);
462 for (std::size_t g = 0; g < ng; ++g) {
463 const LqnFocusGroup& d = grp[g];
464 detail::RawProc p;
465 p.name = d.aggHost;
466 if (std::isfinite(d.hostMult)) {
467 p.sched = SchedStrategy::PS;
468 p.mult = d.hostMult;
469 } else {
470 p.sched = SchedStrategy::INF;
471 p.mult = std::numeric_limits<double>::infinity();
472 }
473 fm.procs.push_back(p);
474 detail::RawTask<double> tk;
475 tk.name = d.aggTask;
476 if (std::isfinite(d.mult)) {
477 tk.sched = SchedStrategy::FCFS;
478 tk.mult = d.mult;
479 } else {
480 tk.sched = SchedStrategy::INF;
481 tk.mult = std::numeric_limits<double>::infinity();
482 }
483 tk.proc_slot = fm.procs.size() - 1;
484 aggTslot[g] = fm.tasks.size();
485 fm.tasks.push_back(tk);
486 }
487 for (std::size_t e = 0; e < nE; ++e) {
488 if (!PT[inv.eTask[e]]) continue;
489 auto en = model.entries[e];
490 en.task_slot = tslot[inv.eTask[e]];
491 fm.entries.push_back(en);
492 }
493 for (std::size_t g = 0; g < ng; ++g) {
494 detail::RawEntry<double> en;
495 en.name = grp[g].aggEntry;
496 en.task_slot = aggTslot[g];
497 en.reply_activities.push_back(grp[g].aggActivity);
498 fm.entries.push_back(en);
499 }
500 for (std::size_t a = 0; a < nA; ++a) {
501 if (!PT[inv.aTask[a]]) continue;
502 auto ac = model.acts[a];
503 ac.task_slot = tslot[inv.aTask[a]];
504 ac.sync_calls.clear();
505 for (const auto& c : model.acts[a].sync_calls) {
506 const std::string& dn = fmEntry[index_of(inv.eName, c.dest)];
507 auto it = std::find_if(ac.sync_calls.begin(), ac.sync_calls.end(),
508 [&](const detail::RawCall<double>& x) { return x.dest == dn; });
509 if (it == ac.sync_calls.end()) ac.sync_calls.push_back({dn, c.mean});
510 else it->mean += c.mean;
511 }
512 fm.acts.push_back(ac);
513 }
514 for (std::size_t g = 0; g < ng; ++g) {
515 const LqnFocusGroup& d = grp[g];
516 detail::RawActivity<double> ac;
517 ac.name = d.aggActivity;
518 ac.hostdem = d.demand > GlobalConstants::FineTol ? Distrib<double>::exp_mean(d.demand)
520 ac.bound_to_entry = d.aggEntry;
521 ac.task_slot = aggTslot[g];
522 for (std::size_t k = 0; k < d.callDests.size(); ++k) ac.sync_calls.push_back({d.callDests[k], d.callMeans[k]});
523 fm.acts.push_back(ac);
524 }
525
526 LqnFocusResult res;
527 std::size_t nPT = 0, nPH = 0;
528 for (std::size_t t = 0; t < nT; ++t) {
529 if (PT[t]) {
530 res.preservedTasks.push_back(inv.tName[t]);
531 ++nPT;
532 }
533 if (inv.forcedT[t]) res.forcedTasks.push_back(inv.tName[t]);
534 if (gid[t] == -1) res.droppedTasks.push_back(inv.tName[t]);
535 }
536 for (std::size_t h = 0; h < nH; ++h)
537 if (PH[h]) {
538 res.preservedHosts.push_back(inv.hName[h]);
539 ++nPH;
540 }
541 res.preservedMask = PT;
542 res.preservedMask.insert(res.preservedMask.end(), PH.begin(), PH.end());
543 res.taskNames = inv.tName;
544 res.taskSat = sat.ST;
545 res.hostNames = inv.hName;
546 res.hostSat = sat.SH;
547 for (std::size_t t = 0; t < nT; ++t) res.taskMap.push_back({inv.tName[t], map_name(gid[t], inv.tName[t], grp, true)});
548 for (std::size_t e = 0; e < nE; ++e) res.entryMap.push_back({inv.eName[e], fmEntry[e]});
549 for (std::size_t a = 0; a < nA; ++a)
550 res.activityMap.push_back({inv.aName[a], map_name(gid[inv.aTask[a]], inv.aName[a], grp, false)});
551 for (std::size_t h = 0; h < nH; ++h) {
552 if (PH[h]) {
553 res.hostMap.push_back({inv.hName[h], inv.hName[h]});
554 continue;
555 }
556 std::set<int> gs;
557 for (std::size_t t = 0; t < nT; ++t)
558 if (inv.tHost[t] == h && gid[t] > 0) gs.insert(gid[t]);
559 if (gs.empty()) res.hostMap.push_back({inv.hName[h], ""});
560 for (int g : gs) res.hostMap.push_back({inv.hName[h], grp[g - 1].aggHost});
561 }
562 double soutMax = 0, sallMax = 0;
563 bool anyOut = false;
564 for (std::size_t t = 0; t < nT; ++t) {
565 sallMax = std::max(sallMax, sat.ST[t]);
566 if (!PT[t]) {
567 soutMax = std::max(soutMax, sat.ST[t]);
568 anyOut = true;
569 }
570 }
571 for (std::size_t h = 0; h < nH; ++h) {
572 sallMax = std::max(sallMax, sat.SH[h]);
573 if (!PH[h]) {
574 soutMax = std::max(soutMax, sat.SH[h]);
575 anyOut = true;
576 }
577 }
578 res.SR = (!anyOut || soutMax <= 0) ? std::numeric_limits<double>::infinity() : sallMax / soutMax;
579 if (res.SR > 2) {
580 res.trustedRange[0] = 1.0 / (res.SR - 2);
581 res.trustedRange[1] = res.SR - 2;
582 }
583 res.FT = static_cast<double>(nPT) / nT;
584 res.F = static_cast<double>(nPT + nPH + 2 * ng) / (nT + nH);
585 std::vector<std::size_t> refs;
586 for (std::size_t t = 0; t < nT; ++t)
587 if (inv.tIsRef[t]) {
588 refs.push_back(t);
589 res.refTasks.push_back(inv.tName[t]);
590 }
591 res.errRef.assign(refs.size(), nan_());
592 res.errRespRef.assign(refs.size(), nan_());
593 if (validate) {
594 const LqnStruct<double> l = lqn_finalize(fm);
596 auto find = [&](const std::string& n, std::size_t lo, std::size_t hi) {
597 for (std::size_t i = lo + 1; i <= hi; ++i)
598 if (l.names[i] == n) return i;
599 fail("element " + n + " not found in the focused model.");
600 };
601 LqnFocusRetained& ret = res.retained;
602 auto add = [&](const std::string& n, const char* ty, double xo, double xf, double ro, double rf, double qo,
603 double qf, double uo, double uf) {
604 ret.names.push_back(n);
605 ret.types.push_back(ty);
606 ret.X_om.push_back(xo);
607 ret.X_fm.push_back(xf);
608 ret.R_om.push_back(ro);
609 ret.R_fm.push_back(rf);
610 ret.Q_om.push_back(qo);
611 ret.Q_fm.push_back(qf);
612 ret.U_om.push_back(uo);
613 ret.U_fm.push_back(uf);
614 };
615 std::map<std::size_t, std::size_t> taskRow;
616 for (std::size_t t = 0; t < nT; ++t) {
617 if (!PT[t]) continue;
618 const std::size_t i = find(inv.tName[t], l.tshift, l.eshift);
619 taskRow[t] = ret.names.size();
620 add(inv.tName[t], "task", om.tX[t], s.TN[i], nan_(), nan_(), om.tQ[t], s.QN[i], om.tU[t], s.UN[i]);
621 }
622 for (std::size_t e = 0; e < nE; ++e) {
623 if (!PT[inv.eTask[e]]) continue;
624 const std::size_t i = find(inv.eName[e], l.eshift, l.ashift);
625 add(inv.eName[e], "entry", om.eX[e], s.TN[i], om.eR[e], s.RN[i], om.eQ[e], s.QN[i], om.eU[e], s.UN[i]);
626 }
627 for (std::size_t h = 0; h < nH; ++h) {
628 if (!PH[h]) continue;
629 const std::size_t i = find(inv.hName[h], 0, l.nhosts);
630 add(inv.hName[h], "host", nan_(), nan_(), nan_(), nan_(), om.hQ[h], s.QN[i], om.hU[h], s.UN[i]);
631 }
632 std::size_t kmax = refs.size(), kmaxR = refs.size();
633 for (std::size_t k = 0; k < refs.size(); ++k) {
634 const std::size_t t = refs[k], row = taskRow[t];
635 const double xo = ret.X_om[row], xf = ret.X_fm[row];
636 double zt = dmean(model.tasks[t].thinktime);
637 if (zt <= GlobalConstants::FineTol) zt = 0;
638 res.errRef[k] = (xf - xo) / xo;
639 const double ro = inv.tMult[t] / xo - zt, rf = inv.tMult[t] / xf - zt;
640 res.errRespRef[k] = (rf - ro) / ro;
641 if (kmax == refs.size() || std::fabs(res.errRef[k]) > std::fabs(res.errRef[kmax])) kmax = k;
642 if (kmaxR == refs.size() || std::fabs(res.errRespRef[k]) > std::fabs(res.errRespRef[kmaxR])) kmaxR = k;
643 }
644 if (kmax < refs.size()) {
645 res.err = res.errRef[kmax];
646 res.errResp = res.errRespRef[kmaxR];
647 }
648 }
649 res.fm = std::move(fm);
650 res.groups = std::move(grp);
651 return res;
652}
653
654} // namespace focus_detail
655
656/**
657 * Focused model of a layered network by FSPT aggregation.
658 *
659 * @param model raw original model (LqnBuilder::model() or read_lqnx_model).
660 * @param opt strategy, thresholds, user focus and solver.
661 * @return the focused raw model (finalize it with lqn_finalize) and the
662 * diagnostics of the MATLAB `info` struct.
663 */
665 using namespace focus_detail;
666 std::string strategy = opt.strategy;
667 for (auto& c : strategy) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
668 if (strategy != "sat" && strategy != "acc" && strategy != "manual")
669 fail("unknown strategy \"" + opt.strategy + "\": use sat, acc or manual.");
670 if (!(opt.r > 1)) fail("r must be greater than 1.");
671 const Inv inv = inventory(model);
672 const Om om = solve(model, opt, inv);
673 const Sat sat = saturation(inv, om);
674 std::vector<bool> baseT = inv.forcedT, baseH = inv.forcedH;
675 for (const auto& nm : opt.preserve) {
676 const std::size_t t = index_of(inv.tName, nm), h = index_of(inv.hName, nm);
677 if (t == inv.nT && h == inv.nH)
678 fail("Element \"" + nm + "\" in preserve is neither a task nor a host of the model.");
679 if (t < inv.nT) baseT[t] = true;
680 if (h < inv.nH) baseH[h] = true;
681 }
682 struct Cand {
683 int kind;
684 std::size_t i;
685 double s;
686 };
687 std::vector<Cand> cand;
688 for (std::size_t t = 0; t < inv.nT; ++t)
689 if (sat.ST[t] > 0) cand.push_back({1, t, sat.ST[t]});
690 for (std::size_t h = 0; h < inv.nH; ++h)
691 if (sat.SH[h] > 0) cand.push_back({2, h, sat.SH[h]});
692 std::stable_sort(cand.begin(), cand.end(), [](const Cand& a, const Cand& b) { return a.s > b.s; });
693 const double Smax = cand.empty() ? 0.0 : cand[0].s;
694 auto select = [&](std::size_t k, std::vector<bool>& T, std::vector<bool>& H) {
695 T = baseT;
696 H = baseH;
697 for (std::size_t i = 0; i < k; ++i) (cand[i].kind == 1 ? T[cand[i].i] : H[cand[i].i]) = true;
698 };
699 LqnFocusResult res;
700 std::vector<std::vector<double>> history;
701 std::vector<bool> selT, selH;
702 if (strategy == "sat") {
703 std::size_t k = 0;
704 while (k < cand.size() && Smax > 0 && cand[k].s > Smax / opt.r) ++k;
705 select(k, selT, selH);
706 res = build(model, inv, om, sat, selT, selH, opt, opt.validate);
707 } else if (strategy == "manual") {
708 select(std::min<std::size_t>(1, cand.size()), selT, selH);
709 res = build(model, inv, om, sat, selT, selH, opt, opt.validate);
710 } else {
711 const std::size_t nc = cand.size();
712 std::vector<bool> last;
713 for (std::size_t i = 1; i <= std::max<std::size_t>(1, nc); ++i) {
714 select(std::min(i, nc), selT, selH);
715 closure(inv, selT, selH);
716 std::vector<bool> key = selT;
717 key.insert(key.end(), selH.begin(), selH.end());
718 if (key == last && i < nc) continue;
719 last = key;
720 res = build(model, inv, om, sat, selT, selH, opt, true);
721 std::size_t np = 0;
722 for (bool b : res.preservedMask) np += b ? 1 : 0;
723 history.push_back({static_cast<double>(i), static_cast<double>(np), res.F, res.err});
724 if (std::fabs(res.err) < opt.x) break;
725 }
726 }
727 res.strategy = strategy;
728 res.r = opt.r;
729 res.x = opt.x;
730 res.accHistory = history;
731 res.Smax = Smax;
732 return res;
733}
734
735} // namespace lqn
736} // namespace line
737
738#endif // LINE_API_LQN_LQN_FOCUS_H
LnSolution< T > get_ensemble_avg()
Port of getEnsembleAvg: run the iteration and aggregate onto LQN elements.
Definition solver_ln.h:600
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LayeredNetworkStruct, the flattened description of a layered queueing network.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:188
bool is_prio(SchedStrategy s)
Definition lqn_focus.h:116
void fail(const std::string &msg)
Definition lqn_focus.h:153
double z(double v)
Definition lqn_focus.h:261
void closure(const Inv &inv, std::vector< bool > &PT, std::vector< bool > &PH)
Definition lqn_focus.h:304
Om solve(const LqnModel< double > &m, const LqnFocusOptions &opt, const Inv &inv)
Definition lqn_focus.h:263
std::string unique_name(std::set< std::string > &all, std::string nm)
Definition lqn_focus.h:311
Inv inventory(const LqnModel< double > &m)
Definition lqn_focus.h:161
bool has_time(const Distrib< double > &d)
Definition lqn_focus.h:127
LqnFocusResult build(const LqnModel< double > &model, const Inv &inv, const Om &om, const Sat &sat, std::vector< bool > PT, std::vector< bool > PH, const LqnFocusOptions &opt, bool validate)
Definition lqn_focus.h:323
Sat saturation(const Inv &inv, const Om &om)
Definition lqn_focus.h:288
double dmean(const Distrib< double > &d)
Definition lqn_focus.h:122
std::string map_name(int g, const std::string &name, const std::vector< LqnFocusGroup > &grp, bool task)
Definition lqn_focus.h:317
std::size_t index_of(const std::vector< std::string > &v, const std::string &s)
Definition lqn_focus.h:155
LqnFocusResult lqn_focus(const LqnModel< double > &model, const LqnFocusOptions &opt=LqnFocusOptions())
Focused model of a layered network by FSPT aggregation.
Definition lqn_focus.h:664
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Definition lqn_reader.h:501
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
SolverLN: layered decomposition of a layered queueing network.
Options of SolverLN.
Definition solver_ln.h:275
The LQN-level answer, indexed by element 1..nidx.
Definition solver_ln.h:420
std::vector< T > QN
Definition solver_ln.h:421
std::vector< T > RN
Definition solver_ln.h:421
std::vector< T > UN
Definition solver_ln.h:421
std::vector< T > TN
Definition solver_ln.h:421
static Distrib immediate()
The point mass at zero.
static Distrib exp_mean(const T &m)
One dependency group and its aggregate task.
Definition lqn_focus.h:72
std::vector< std::string > pi
Definition lqn_focus.h:73
std::vector< std::string > tasks
Definition lqn_focus.h:73
std::vector< std::string > callDests
Definition lqn_focus.h:76
std::vector< double > callMeans
Definition lqn_focus.h:77
std::string aggActivity
Definition lqn_focus.h:74
Options of lqn_focus, as the MATLAB name/value pairs.
Definition lqn_focus.h:61
std::vector< std::string > preserve
task or host names forced into the focus
Definition lqn_focus.h:65
double x
ACC relative throughput error target.
Definition lqn_focus.h:64
double r
SAT saturation ratio threshold.
Definition lqn_focus.h:63
bool validate
solve the FM and report errors (always on for "acc")
Definition lqn_focus.h:68
std::function< ln::LnSolution< double >(const LqnStruct< double > &)> solver
Solver of a finalized struct; empty uses ln::SolverLN with default options.
Definition lqn_focus.h:67
std::string strategy
"sat", "acc" or "manual"
Definition lqn_focus.h:62
Focused model and diagnostics, fields named as the MATLAB info struct.
Definition lqn_focus.h:87
std::vector< bool > preservedMask
(ntasks + nhosts) tasks then hosts
Definition lqn_focus.h:90
std::vector< std::pair< std::string, std::string > > entryMap
Definition lqn_focus.h:95
std::vector< double > hostSat
Definition lqn_focus.h:92
std::vector< double > errRef
Definition lqn_focus.h:102
std::vector< std::string > preservedTasks
Definition lqn_focus.h:89
std::vector< std::pair< std::string, std::string > > activityMap
Definition lqn_focus.h:95
std::vector< std::string > refTasks
Definition lqn_focus.h:101
std::vector< std::pair< std::string, std::string > > hostMap
Definition lqn_focus.h:95
std::vector< std::string > hostNames
Definition lqn_focus.h:91
std::vector< std::string > droppedTasks
Definition lqn_focus.h:89
std::vector< double > taskSat
Definition lqn_focus.h:92
std::vector< std::string > preservedHosts
Definition lqn_focus.h:89
std::vector< std::vector< double > > accHistory
rows [i, nPreserved, F, err]
Definition lqn_focus.h:106
LqnModel< double > fm
Definition lqn_focus.h:88
std::vector< std::string > taskNames
Definition lqn_focus.h:91
LqnFocusRetained retained
Definition lqn_focus.h:103
std::vector< double > errRespRef
Definition lqn_focus.h:102
std::vector< std::string > forcedTasks
Definition lqn_focus.h:89
std::vector< std::pair< std::string, std::string > > taskMap
{original, focused}; focused is "" for a dropped element.
Definition lqn_focus.h:95
std::vector< LqnFocusGroup > groups
Definition lqn_focus.h:93
OM and FM metrics of the retained tasks, entries and hosts.
Definition lqn_focus.h:81
std::vector< double > R_fm
Definition lqn_focus.h:83
std::vector< double > Q_fm
Definition lqn_focus.h:83
std::vector< std::string > types
Definition lqn_focus.h:82
std::vector< double > U_fm
Definition lqn_focus.h:83
std::vector< std::string > names
Definition lqn_focus.h:82
std::vector< double > U_om
Definition lqn_focus.h:83
std::vector< double > Q_om
Definition lqn_focus.h:83
std::vector< double > X_om
Definition lqn_focus.h:83
std::vector< double > X_fm
Definition lqn_focus.h:83
std::vector< double > R_om
Definition lqn_focus.h:83
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:415
std::vector< detail::RawActivity< T > > acts
Definition lqn_reader.h:417
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
Definition lqn_reader.h:441
std::vector< detail::RawProc > procs
Definition lqn_reader.h:414
std::map< std::size_t, CdScaling< T > > proc_jdscaling
Definition lqn_reader.h:439
std::map< std::size_t, std::pair< Matrix< T >, std::vector< T > > > proc_lincon
Definition lqn_reader.h:427
std::map< std::size_t, std::vector< detail::RawLinConRow< T > > > proc_linconrows
Admission constraints declared on a HOST, by 0-based processor slot.
Definition lqn_reader.h:426
std::map< std::size_t, CdScaling< T > > proc_cdscaling
Definition lqn_reader.h:437
std::string name
LayeredNetwork.getName(); empty when unnamed.
Definition lqn_reader.h:413
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.
Definition lqn_reader.h:436
std::vector< detail::RawEntry< T > > entries
Definition lqn_reader.h:416
std::vector< std::string > names
(nidx+1) declared name
Definition lqn_struct.h:212
std::vector< bool > forcedH
Definition lqn_focus.h:139
std::vector< double > aDem
Definition lqn_focus.h:137
std::vector< std::string > aName
Definition lqn_focus.h:136
std::vector< double > hSpeed
Definition lqn_focus.h:137
std::vector< Call > fwd
Definition lqn_focus.h:140
std::vector< Call > async
Definition lqn_focus.h:140
std::vector< bool > tIsRef
Definition lqn_focus.h:139
std::vector< double > tMult
Definition lqn_focus.h:137
std::vector< std::size_t > aTask
Definition lqn_focus.h:138
std::vector< std::string > tName
Definition lqn_focus.h:136
std::vector< std::size_t > eTask
Definition lqn_focus.h:138
std::vector< double > hMult
Definition lqn_focus.h:137
std::vector< bool > forcedT
Definition lqn_focus.h:139
std::vector< std::vector< bool > > reach
Definition lqn_focus.h:141
std::vector< Call > sync
Definition lqn_focus.h:140
std::vector< std::string > eName
Definition lqn_focus.h:136
std::vector< std::size_t > tHost
Definition lqn_focus.h:138
std::vector< std::string > hName
Definition lqn_focus.h:136
std::vector< double > tQ
Definition lqn_focus.h:145
std::vector< double > eR
Definition lqn_focus.h:145
std::vector< double > eQ
Definition lqn_focus.h:145
std::vector< double > tX
Definition lqn_focus.h:145
std::vector< double > eX
Definition lqn_focus.h:145
std::vector< double > eU
Definition lqn_focus.h:145
std::vector< double > hQ
Definition lqn_focus.h:145
std::vector< double > hU
Definition lqn_focus.h:145
std::vector< double > tU
Definition lqn_focus.h:145
std::vector< double > aX
Definition lqn_focus.h:145
std::vector< double > SH
Definition lqn_focus.h:150
std::vector< double > ST
Definition lqn_focus.h:150
std::vector< std::vector< double > > SHT
Definition lqn_focus.h:149