5#ifndef LINE_LANG_LQN_LQN_BUILDER_H
6#define LINE_LANG_LQN_LQN_BUILDER_H
55 p.mult = sched == SchedStrategy::INF ? std::numeric_limits<double>::infinity() : mult;
56 m_.procs.push_back(p);
57 return m_.procs.size() - 1;
62 const std::string& on_processor,
double repl = 1.0) {
67 t.mult = sched == SchedStrategy::INF ? std::numeric_limits<double>::infinity() : mult;
69 t.proc_slot = find_proc(on_processor);
70 m_.tasks.push_back(t);
71 return m_.tasks.size() - 1;
83 m_.tasks[find_task(task_name)].thinktime = d;
95 const std::string& on_processor, std::size_t nitems,
99 throw InputError(
"LqnBuilder::cache_task: '" + name +
"' caches no item");
101 throw InputError(
"LqnBuilder::cache_task: '" + name +
"' has no cache list");
103 for (std::size_t i = 0; i < itemcap.size(); ++i) total += itemcap[i];
104 if (total <= 0 ||
static_cast<std::size_t
>(total) >= nitems)
105 throw InputError(
"LqnBuilder::cache_task: '" + name +
106 "' has a total capacity that is not between 1 and nitems-1; "
107 "a cache that holds every item never misses");
108 const std::size_t t =
task(name, mult, sched, on_processor, repl);
109 m_.tasks[t].nitems = nitems;
110 m_.tasks[t].itemcap = itemcap;
111 m_.tasks[t].replacestrat = replacestrat;
117 detail::RawTask<T>& t = m_.tasks[find_task(task_name)];
119 throw InputError(
"LqnBuilder::cache_retrieval: '" + task_name +
"' is not a cache task");
135 throw InputError(
"LqnBuilder::setup_time: '" + task_name +
"' has no setup time");
138 "LqnBuilder::setup_time: '" + task_name +
139 "' has a setup time but no delay-off time; a server that never shuts down pays "
140 "the setup at most once and the reference declines to model it");
141 detail::RawTask<T>& t = m_.tasks[find_task(task_name)];
143 t.delayofftime = delayoff;
153 std::size_t
item_entry(
const std::string& name,
const std::string& on_task,
154 std::size_t cardinality,
const std::vector<T>& popularity) {
155 if (cardinality == 0)
156 throw InputError(
"LqnBuilder::item_entry: '" + name +
"' indexes no item");
157 if (popularity.size() != cardinality)
158 throw InputError(
"LqnBuilder::item_entry: '" + name +
159 "' needs one popularity per item");
160 const std::size_t e =
entry(name, on_task);
161 m_.entries[e].cardinality = cardinality;
162 m_.entries[e].popularity = popularity;
167 std::size_t
entry(
const std::string& name,
const std::string& on_task) {
168 detail::RawEntry<T> e;
170 e.task_slot = find_task(on_task);
171 m_.entries.push_back(e);
172 return m_.entries.size() - 1;
177 detail::RawEntry<T>& e = m_.entries[find_entry(entry_name)];
178 e.has_arrival =
true;
184 const std::string& on_task) {
185 detail::RawActivity<T> a;
189 a.task_slot = find_task(on_task);
191 m_.acts.push_back(a);
192 return m_.acts.size() - 1;
196 void bound_to(
const std::string& act,
const std::string& entry_name) {
197 m_.acts[find_act(act)].bound_to_entry = entry_name;
201 void sync_call(
const std::string& act,
const std::string& dest_entry,
const T& mean) {
202 m_.acts[find_act(act)].sync_calls.push_back({dest_entry, mean});
206 void async_call(
const std::string& act,
const std::string& dest_entry,
const T& mean) {
207 m_.acts[find_act(act)].async_calls.push_back({dest_entry, mean});
220 const std::vector<std::string>& dest_entries,
const T& mean) {
222 "sync_call_round_robin");
226 void sync_call_jsq(
const std::string& act,
const std::vector<std::string>& dest_entries,
238 void forward(
const std::string& src_entry,
const std::string& dest_entry,
const T& prob) {
239 detail::RawEntry<T>& e = m_.entries[find_entry(src_entry)];
240 e.fwd_dest.push_back(dest_entry);
241 e.fwd_prob.push_back(prob);
245 void replies_to(
const std::string& act,
const std::string& entry_name) {
246 m_.entries[find_entry(entry_name)].reply_activities.push_back(act);
251 m_.acts[find_act(act)].phase = phase;
256 m_.acts[find_act(act)].thinktime = d;
269 m_.tasks[find_task(task_name)].priority = priority;
273 void entry_type(
const std::string& entry_name,
const std::string& type) {
274 m_.entries[find_entry(entry_name)].type = type;
278 void call_order(
const std::string& act,
const std::string& order) {
279 m_.acts[find_act(act)].call_order = detail::call_order_from_text(order);
283 void host_class(
const std::string& proc_name,
bool is_host =
true) {
284 m_.procs[find_proc(proc_name)].is_host_class = is_host;
290 void serial(
const std::string& pre,
const std::string& post) {
291 detail::RawPrecedence<T> p;
292 p.pretype = PrecedenceType::PRE_SEQ;
293 p.posttype = PrecedenceType::POST_SEQ;
294 p.preacts.push_back(pre);
295 p.postacts.push_back(post);
300 void and_fork(
const std::string& pre,
const std::vector<std::string>& posts) {
301 detail::RawPrecedence<T> p;
302 p.pretype = PrecedenceType::PRE_SEQ;
303 p.posttype = PrecedenceType::POST_AND;
304 p.preacts.push_back(pre);
310 void and_join(
const std::vector<std::string>& pres,
const std::string& post,
311 std::size_t quorum = 0) {
312 detail::RawPrecedence<T> p;
313 p.pretype = PrecedenceType::PRE_AND;
314 p.posttype = PrecedenceType::POST_SEQ;
316 p.postacts.push_back(post);
321 add_prec(pres.at(0), p);
325 void or_fork(
const std::string& pre,
const std::vector<std::string>& posts,
326 const std::vector<T>& probs) {
327 if (posts.size() != probs.size())
328 throw InputError(
"LqnBuilder::or_fork: one probability per branch is required");
329 detail::RawPrecedence<T> p;
330 p.pretype = PrecedenceType::PRE_SEQ;
331 p.posttype = PrecedenceType::POST_OR;
332 p.preacts.push_back(pre);
334 p.postparams = probs;
345 void cache_access(
const std::string& pre,
const std::string& hit,
const std::string& miss) {
346 detail::RawPrecedence<T> p;
347 p.pretype = PrecedenceType::PRE_SEQ;
348 p.posttype = PrecedenceType::POST_CACHE;
349 p.preacts.push_back(pre);
350 p.postacts.push_back(hit);
351 p.postacts.push_back(miss);
356 void or_join(
const std::vector<std::string>& pres,
const std::string& post) {
357 detail::RawPrecedence<T> p;
358 p.pretype = PrecedenceType::PRE_OR;
359 p.posttype = PrecedenceType::POST_SEQ;
361 p.postacts.push_back(post);
364 add_prec(pres.at(0), p);
374 void loop(
const std::string& pre,
const std::vector<std::string>& body,
375 const std::string& end,
const T& count) {
376 detail::RawPrecedence<T> p;
377 p.pretype = PrecedenceType::PRE_SEQ;
378 p.posttype = PrecedenceType::POST_LOOP;
379 p.preacts.push_back(pre);
381 p.postacts.push_back(end);
382 p.postparams.assign(body.size(), count);
396 void add_constraint(
const std::string& elem,
const std::vector<std::string>& operands,
397 const std::vector<T>& coeffs,
const T& cap) {
398 if (operands.size() != coeffs.size())
399 throw InputError(
"LqnBuilder::add_constraint: one coefficient per operand is required");
400 if (operands.empty())
401 throw InputError(
"LqnBuilder::add_constraint: the constraint names no operand");
402 for (std::size_t i = 0; i < operands.size(); ++i)
403 for (std::size_t j = i + 1; j < operands.size(); ++j)
404 if (operands[i] == operands[j])
405 throw InputError(
"LqnBuilder::add_constraint: operand '" + operands[i] +
407 detail::RawLinConRow<T> row;
408 row.names = operands;
411 linconrows_of(elem).push_back(row);
421 if (A.
rows() != b.size())
422 throw InputError(
"LqnBuilder::set_constraint: A and b disagree on the number of rows");
423 for (std::size_t i = 0; i < m_.tasks.size(); ++i)
424 if (m_.tasks[i].name == elem) {
425 m_.tasks[i].lincon_A = A;
426 m_.tasks[i].lincon_b = b;
429 for (std::size_t i = 0; i < m_.procs.size(); ++i)
430 if (m_.procs[i].name == elem) {
431 m_.proc_lincon[i] = std::make_pair(A, b);
434 throw InputError(
"LqnBuilder::set_constraint: unknown task or processor '" + elem +
"'");
442 assert_rate_dependent(elem,
"Load");
444 throw InputError(
"LqnBuilder::set_load_dependence: alpha is empty");
446 m_.tasks[find_task(elem)].lldscaling = alpha;
448 m_.proc_lldscaling[find_proc(elem)] = alpha;
458 const std::vector<T>& peak) {
459 assert_rate_dependent(elem,
"Class");
460 assert_dependence_handle(beta, peak,
"Class");
462 const std::size_t t = find_task(elem);
463 m_.tasks[t].cdscaling = beta;
464 m_.tasks[t].cdscalingpeak = peak;
466 const std::size_t p = find_proc(elem);
467 m_.proc_cdscaling[p] = beta;
468 m_.proc_cdscalingpeak[p] = peak;
477 const std::vector<T>& peak) {
478 assert_rate_dependent(elem,
"Joint");
479 assert_dependence_handle(eta, peak,
"Joint");
480 assert_no_pools(elem,
"a joint dependence");
482 const std::size_t t = find_task(elem);
483 m_.tasks[t].jdscaling = eta;
484 m_.tasks[t].jdscalingpeak = peak;
486 const std::size_t p = find_proc(elem);
487 m_.proc_jdscaling[p] = eta;
488 m_.proc_jdscalingpeak[p] = peak;
503 const std::vector<std::string>& compatible,
const T& rate) {
504 assert_rate_dependent(elem,
"Compatibility");
507 throw InputError(
"LqnBuilder::add_server_type: pool '" + pool +
508 "' must hold at least one server");
509 if (compatible.empty())
510 throw InputError(
"LqnBuilder::add_server_type: pool '" + pool +
511 "' is compatible with no operand, so it can never serve");
512 detail::RawServerPool<T> sp;
516 sp.compatible = compatible;
517 raw_pools_of(elem).push_back(sp);
528 std::size_t find_proc(
const std::string& n)
const {
529 for (std::size_t i = 0; i < m_.
procs.size(); ++i)
530 if (m_.
procs[i].name == n)
return i;
531 throw InputError(
"LqnBuilder: unknown processor '" + n +
"'");
533 std::size_t find_task(
const std::string& n)
const {
534 for (std::size_t i = 0; i < m_.
tasks.size(); ++i)
535 if (m_.
tasks[i].name == n)
return i;
536 throw InputError(
"LqnBuilder: unknown task '" + n +
"'");
538 std::size_t find_entry(
const std::string& n)
const {
539 for (std::size_t i = 0; i < m_.
entries.size(); ++i)
540 if (m_.
entries[i].name == n)
return i;
541 throw InputError(
"LqnBuilder: unknown entry '" + n +
"'");
543 std::size_t find_act(
const std::string& n)
const {
544 for (std::size_t i = 0; i < m_.acts.size(); ++i)
545 if (m_.acts[i].name == n)
return i;
546 throw InputError(
"LqnBuilder: unknown activity '" + n +
"'");
549 std::vector<detail::RawLinConRow<T>>& linconrows_of(
const std::string& n) {
550 for (std::size_t i = 0; i < m_.tasks.size(); ++i)
551 if (m_.tasks[i].name == n)
return m_.tasks[i].linconrows;
552 for (std::size_t i = 0; i < m_.procs.size(); ++i)
553 if (m_.procs[i].name == n)
return m_.proc_linconrows[i];
554 throw InputError(
"LqnBuilder: unknown task or processor '" + n +
"'");
557 bool is_task(
const std::string& n)
const {
558 for (std::size_t i = 0; i < m_.tasks.size(); ++i)
559 if (m_.tasks[i].name == n)
return true;
564 std::vector<detail::RawServerPool<T>>& raw_pools_of(
const std::string& n) {
565 for (std::size_t i = 0; i < m_.tasks.size(); ++i)
566 if (m_.tasks[i].name == n)
return m_.tasks[i].pools;
567 for (std::size_t i = 0; i < m_.procs.size(); ++i)
568 if (m_.procs[i].name == n)
return m_.proc_pools[i];
569 throw InputError(
"LqnBuilder: unknown task or processor '" + n +
"'");
576 void assert_rate_dependent(
const std::string& elem,
const char* what)
const {
579 for (std::size_t i = 0; i < m_.tasks.size() && !found; ++i)
580 if (m_.tasks[i].name == elem) {
581 sched = m_.tasks[i].sched;
584 for (std::size_t i = 0; i < m_.procs.size() && !found; ++i)
585 if (m_.procs[i].name == elem) {
586 sched = m_.procs[i].sched;
591 "-dependence can only be set on a Task or a Host, which are the only "
592 "elements that become server stations in a layer; '" +
593 elem +
"' is neither");
594 if (sched != SchedStrategy::PS && sched != SchedStrategy::FCFS)
596 "-dependence supported only for processor sharing (PS) and "
597 "first-come first-serve (FCFS) servers, but '" +
598 elem +
"' is scheduled otherwise");
602 void assert_dependence_handle(
const CdScaling<T>& f,
const std::vector<T>& peak,
603 const char* what)
const {
605 throw InputError(std::string(what) +
"-dependence needs a handle");
608 "-dependence needs a peak rate per operand, which normalizes "
609 "utilization as U = T*S/peak");
613 void assert_no_pools(
const std::string& elem,
const char* what) {
614 if (!raw_pools_of(elem).empty())
615 throw InputError(
"LqnBuilder: '" + elem +
"' already declares server pools, which are "
616 "themselves a rate law, so it cannot also take " + what);
619 void assert_no_jd(
const std::string& elem) {
621 for (std::size_t i = 0; i < m_.tasks.size(); ++i)
622 if (m_.tasks[i].name == elem && m_.tasks[i].jdscaling) has =
true;
623 for (std::size_t i = 0; i < m_.procs.size(); ++i)
624 if (m_.procs[i].name == elem && m_.proc_jdscaling.count(i)) has =
true;
626 throw InputError(
"LqnBuilder: '" + elem +
"' already declares a joint dependence, so "
627 "it cannot also declare server pools, which are a rate law of their "
632 void add_prec(
const std::string& anchor_act,
const detail::RawPrecedence<T>& p) {
633 m_.tasks[m_.acts[find_act(anchor_act)].task_slot].precedences.push_back(p);
642 void add_call_group(
const std::string& act,
const std::vector<std::string>& dest_entries,
644 if (dest_entries.size() < 2)
645 throw InputError(std::string(
"LqnBuilder::") + who +
646 " needs at least two target entries: a group of one is not a "
647 "dispatch decision");
648 const T share = T(mean / num_traits<T>::from_int(
int(dest_entries.size())));
649 for (
const std::string& d : dest_entries)
sync_call(act, d, share);
650 m_.acts[find_act(act)].call_groups.push_back(std::make_pair(rs, dest_entries));
void sync_call_jsq(const std::string &act, const std::vector< std::string > &dest_entries, const T &mean)
As above, with the least loaded target taking the call (synchCallJSQ).
void bound_to(const std::string &act, const std::string &entry_name)
Bind an activity to an entry: it is the entry's first activity.
std::size_t activity(const std::string &name, const Distrib< T > &hostdem, const std::string &on_task)
Add an activity on a task, with its host demand.
void entry_type(const std::string &entry_name, const std::string &type)
Entry.setType: PH1PH2 (the default) or NONE; carried for the code generators only.
void cache_retrieval(const std::string &task_name, bool on=true)
CacheTask.setRetrieval(true): concurrent misses of one item coalesce on a single fetch.
LqnStruct< T > build() const
Flatten into the struct SolverLN consumes.
void act_think_time(const std::string &act, const Distrib< T > &d)
An activity think time, in series with the host demand.
void loop(const std::string &pre, const std::vector< std::string > &body, const std::string &end, const T &count)
pre -> body, repeated count times in expectation, then -> end.
void activity_phase(const std::string &act, int phase)
Activity phase: 1 before the entry replies, 2 after it (the second phase).
void open_arrival(const std::string &entry_name, const Distrib< T > &d)
An open arrival stream at an entry.
void setup_time(const std::string &task_name, const Distrib< T > &setup, const Distrib< T > &delayoff)
A SetupTask: a server that powers down when idle and pays to restart.
std::size_t processor(const std::string &name, double mult, SchedStrategy sched, double repl=1.0)
Add a processor.
void set_joint_dependence(const std::string &elem, const CdScaling< T > &eta, const std::vector< T > &peak)
elem.setJointDependence(eta, peak): the non-product-form handle, read at the whole per-operand vector...
void host_class(const std::string &proc_name, bool is_host=true)
Declare a processor as a Host rather than a Processor: the same element to every solver.
void task_priority(const std::string &task_name, int priority)
Task.setPriority, on the lqns scale the .lqnx priority attribute carries: a LARGER value is served fi...
void replies_to(const std::string &act, const std::string &entry_name)
Mark an activity as the one that replies to an entry.
void forward(const std::string &src_entry, const std::string &dest_entry, const T &prob)
Forwarding: whenever src_entry is invoked, with probability prob the request is handed onward to dest...
void and_fork(const std::string &pre, const std::vector< std::string > &posts)
pre -> every post, concurrently.
void cache_access(const std::string &pre, const std::string &hit, const std::string &miss)
ActivityPrecedence.CacheAccess(pre, {hit, miss}).
std::size_t cache_task(const std::string &name, double mult, SchedStrategy sched, const std::string &on_processor, std::size_t nitems, const std::vector< int > &itemcap, ReplacementStrategy replacestrat, double repl=1.0)
A CacheTask: a task whose entries are looked up in a cache of nitems.
void set_constraint(const std::string &elem, const Matrix< T > &A, const std::vector< T > &b)
The positional form, elem.setConstraint(A, b).
void add_constraint(const std::string &elem, const std::vector< std::string > &operands, const std::vector< T > &coeffs, const T &cap)
elem.addConstraint(operands, coeffs, cap): sum(coeffs .
void async_call(const std::string &act, const std::string &dest_entry, const T &mean)
An asynchronous call from an activity to an entry of another task.
void and_join(const std::vector< std::string > &pres, const std::string &post, std::size_t quorum=0)
all pres (or quorum of them) -> post.
void serial(const std::string &pre, const std::string &post)
pre -> post, a plain sequence.
void sync_call(const std::string &act, const std::string &dest_entry, const T &mean)
A synchronous call from an activity to an entry of another task.
void call_order(const std::string &act, const std::string &order)
Activity.setCallOrder: STOCHASTIC or DETERMINISTIC, any other value reading as STOCHASTIC.
void set_class_dependence(const std::string &elem, const CdScaling< T > &beta, const std::vector< T > &peak)
elem.setClassDependence(beta, peak): the product-form handle, whose argument is the per-OPERAND popul...
void think_time(const std::string &task_name, const Distrib< T > &d)
Set a task's think time.
const LqnModel< T > & model() const
void sync_call_round_robin(const std::string &act, const std::vector< std::string > &dest_entries, const T &mean)
ONE synchronous call per invocation, its destination CYCLING over the targets in the order given (syn...
void add_server_type(const std::string &elem, const std::string &pool, double count, const std::vector< std::string > &compatible, const T &rate)
elem.addServerType(ServerType(pool, count, compatible)): one pool of COUNT identical servers,...
void or_fork(const std::string &pre, const std::vector< std::string > &posts, const std::vector< T > &probs)
pre -> one of the posts, with the given branch probabilities.
void set_load_dependence(const std::string &elem, const std::vector< T > &alpha)
elem.setLoadDependence(alpha): alpha(n) scales the rate of the layer station of ELEM when it holds n ...
void or_join(const std::vector< std::string > &pres, const std::string &post)
any of the pres -> post.
std::size_t item_entry(const std::string &name, const std::string &on_task, std::size_t cardinality, const std::vector< T > &popularity)
An ItemEntry: the entry a cache read enters, over cardinality items.
std::size_t entry(const std::string &name, const std::string &on_task)
Add an entry on a task.
std::size_t task(const std::string &name, double mult, SchedStrategy sched, const std::string &on_processor, double repl=1.0)
Add a task on a processor.
The exception types the port throws.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
RoutingStrategy
Routing strategies, with the values of MATLAB RoutingStrategy.
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.
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Conservation laws of a layered queueing network, enumerated from its structure.
static Distrib immediate()
The Immediate singleton.
The intermediate model, and the second stage that flattens it.
std::vector< detail::RawTask< T > > tasks
std::vector< detail::RawProc > procs
std::vector< detail::RawEntry< T > > entries