LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
code_gen.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_IO_CODE_GEN_H
6#define LINE_IO_CODE_GEN_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Port of the model -> source code generators of `matlab/src/io/`:
12 * `QN2MATLAB.m`, `QN2JAVA.m`, `LQN2JAVA.m`, the generator form of `LQN2MATLAB.m`,
13 * and the `LINE2MATLAB.m` / `LINE2JAVA.m` dispatchers. LQN2MATLAB, unlike the
14 * others, re-declares the model rather than its struct; see lqn2matlab below.
15 *
16 * WHAT IS WRITTEN IS THE STRUCT, NOT THE DECLARATIONS. Exactly as in MATLAB the
17 * queueing generators read `sn` after refresh: every process is re-expressed
18 * from the moments of its Markovian representation (`sn.proc`), so an SCV of
19 * at least 0.5 is written as `Exp.fitMean` (SCV 1) or `APH.fitMeanAndSCV`, a
20 * smaller one as an Erlang with round(1/SCV) phases, and a process with no
21 * representation as `Disabled`. The routing is the node-level `sn.rtnodes`,
22 * with a ClassSwitch written as a Router because the switching is already in
23 * the matrix. The layered generator reads the `LayeredNetworkStruct`, plus the
24 * declared reply activities and AND-join quorums the struct does not keep.
25 *
26 * NUMERIC TEXT FOLLOWS MATLAB'S fprintf. `%d` of a non-integer double prints in
27 * `%e` form there (a routing share of 1/4 is `2.500000e-01`), infinities print
28 * as `Inf` and NaN as `NaN`; the helpers below reproduce that, so a line of
29 * output is byte for byte the line MATLAB writes for the same struct.
30 *
31 * WHERE THE C++ OUTPUT DELIBERATELY DIFFERS FROM MATLAB, it is because the
32 * MATLAB text is not valid source for its target:
33 * - QN2JAVA: `new Erlang(rate, n)` with an integer phase count and
34 * `new Replayer("f")`, where MATLAB writes `Erlang(%f,%f)` and
35 * `Replayer("f")`, neither of which compiles.
36 * LQN2JAVA follows the MATLAB text exactly, distributions included (`javaDist`).
37 * A node type the MATLAB generators have no statement for (Cache, Logger,
38 * Place, Transition) is refused by name instead of being omitted, since
39 * omitting it writes a script that builds a different model.
40 */
41
42#include <algorithm>
43#include <cctype>
44#include <cmath>
45#include <cstdio>
46#include <cstdlib>
47#include <fstream>
48#include <limits>
49#include <map>
50#include <ostream>
51#include <set>
52#include <sstream>
53#include <string>
54#include <vector>
55
65#include "line/util/error.h"
66
67namespace line {
68namespace io {
69
70namespace code_gen_detail {
71
72/** `%f`, `%e` or `%g` as MATLAB's fprintf prints it: C's text, with `Inf` / `NaN` spelt MATLAB's way. */
73inline std::string mfmt(const char* spec, double x) {
74 if (std::isnan(x)) return "NaN";
75 if (std::isinf(x)) return x > 0 ? "Inf" : "-Inf";
76 char buf[64];
77 std::snprintf(buf, sizeof(buf), spec, x);
78 return std::string(buf);
79}
80
81inline std::string fmt_f(double x) { return mfmt("%f", x); }
82inline std::string fmt_g(double x) { return mfmt("%g", x); }
83
84/** MATLAB `%d` of a double: the integer when it is one, else MATLAB's `%e` override. */
85inline std::string fmt_d(double x) {
86 if (std::isnan(x) || std::isinf(x)) return mfmt("%f", x);
87 if (x == std::floor(x) && std::fabs(x) < 9.007199254740992e15) {
88 char buf[64];
89 std::snprintf(buf, sizeof(buf), "%.0f", x == 0.0 ? 0.0 : x);
90 return std::string(buf);
91 }
92 return mfmt("%e", x);
93}
94
95inline std::string fmt_d(std::size_t x) { return std::to_string(x); }
96inline std::string fmt_d(int x) { return std::to_string(x); }
97
98/** `SchedStrategy.toProperty(SchedStrategy.toText(s))`: the enum constant name. */
99inline std::string sched_property(lang::SchedStrategy s) {
100 const std::string t = lang::sched_to_text(s);
101 if (t == "none")
102 throw UnsupportedError("code_gen: a station carries no scheduling strategy to write");
103 std::string out;
104 for (std::size_t i = 0; i < t.size(); ++i)
105 out.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(t[i]))));
106 return out;
107}
108
109/** `strrep(SchedStrategy.toFeature(s),'_','.')`, the layered generator's spelling. */
110inline std::string sched_feature(lang::SchedStrategy s) { return "SchedStrategy." + sched_property(s); }
111
112/** One (station, class) process, reduced to what QN2MATLAB / QN2JAVA decide on. */
113struct ProcSpec {
115 bool arrival = false; ///< the station is EXT scheduled: setArrival, not setService
116 double mean = 0.0;
117 double scv = 0.0;
118 double nphases = 0.0;
119 std::string file;
120};
121
122/**
123 * The branch of QN2MATLAB's process block for station `i`, class `k` (0-based).
124 *
125 * The Replayer test is MATLAB's `isprop(station,'serviceProcess')`, a property
126 * only Queue and its Delay subclass declare, so a trace at a Source goes down
127 * the moment path and is written as the fit of its moments, as in MATLAB.
128 */
129template <class T>
130ProcSpec proc_spec(const qn::NetworkStruct<T>& sn, std::size_t i, std::size_t k) {
131 ProcSpec ps;
132 const std::size_t nd = sn.station_to_node[i];
133 const lang::NodeType nt = sn.nodes[nd - 1].nodetype;
134 if (nt == lang::NodeType::Join) return ps;
135 ps.arrival = sn.stations[i].sched == lang::SchedStrategy::EXT;
136 const lang::Distrib<T>& d = sn.service[i][k];
137 const bool has_service_prop = nt == lang::NodeType::Queue || nt == lang::NodeType::Delay;
138 if (has_service_prop && !d.disabled && d.type == lang::ProcessType::REPLAYER) {
139 if (d.trace_file.empty())
140 throw UnsupportedError("code_gen: the Replayer of class '" + sn.classes[k].name + "' at '" +
141 sn.nodes[nd - 1].name +
142 "' was built from in-memory samples, so there is no trace file to name");
144 ps.file = d.trace_file;
145 return ps;
146 }
147 if (d.disabled) { // sn.proc is NaN: map_scv is NaN, so MATLAB reaches the isnan arm
149 return ps;
150 }
151 const mam::Map<T> m = lang::dist_to_map(d);
154 if (ps.scv >= 0.5) {
155 if (ps.scv == 1.0)
157 else
158 ps.kind = ProcSpec::APH;
159 } else {
161 ps.nphases = std::max(1.0, std::round(1.0 / ps.scv));
162 }
163 return ps;
164}
165
166/** `find(sn.fj(:,j))`: the 1-based Fork node the 1-based Join node `j` closes. */
167template <class T>
168std::size_t fork_of(const qn::NetworkStruct<T>& sn, std::size_t j) {
169 for (std::size_t f = 0; f < sn.fj.size(); ++f)
170 if (sn.fj[f].second == j) return sn.fj[f].first;
171 throw InputError("code_gen: Join '" + sn.nodes[j - 1].name +
172 "' closes no Fork, so the model cannot be written as source");
173}
174
175/**
176 * `zeroPopRefNode(sn, k)` of QN2MATLAB / QN2JAVA: the NODE index (1-based) of the
177 * reference station of zero-population closed class `k` (0-based). It is the
178 * refstat of the first populated closed class of k's chain (k's own refstat if
179 * there is none), else the first station whose `sn.proc{i}{k}{1}` has a nonzero
180 * entry, NaN counting as nonzero under `nnz`, so a disabled pair qualifies.
181 */
182template <class T>
183std::size_t empty_class_ref(const qn::NetworkStruct<T>& sn, std::size_t k) {
184 std::size_t ist = 0;
185 for (std::size_t c = 0; c < sn.chains.size(); ++c) {
186 if (k >= sn.chains[c].size() || !sn.chains[c][k]) continue;
187 std::size_t cand = 0;
188 for (std::size_t r = 0; r < sn.classes.size() && r < sn.chains[c].size(); ++r) {
189 const double n = sn.classes[r].population;
190 if (sn.chains[c][r] && n > 0 && std::isfinite(n)) {
191 cand = sn.classes[r].refstat;
192 break;
193 }
194 }
195 if (cand == 0) cand = sn.classes[k].refstat;
196 if (cand >= 1 && cand <= sn.stations.size()) ist = cand;
197 break;
198 }
199 for (std::size_t i = 0; ist == 0 && i < sn.stations.size(); ++i) {
200 const lang::Distrib<T>& d = sn.service[i][k];
201 if (d.disabled || sn.nodes[sn.station_to_node[i] - 1].nodetype == lang::NodeType::Join) {
202 ist = i + 1;
203 break;
204 }
205 const mam::Map<T> m = lang::dist_to_map(d);
206 for (std::size_t a = 0; a < m.D0.rows() && ist == 0; ++a)
207 for (std::size_t b = 0; b < m.D0.cols(); ++b)
208 if (num_traits<T>::to_double(m.D0(a, b)) != 0.0) {
209 ist = i + 1;
210 break;
211 }
212 }
213 return ist > 0 ? sn.station_to_node[ist - 1] : 0;
214}
215
216/** Refuses a node type neither MATLAB generator has a statement for. */
217template <class T>
218void check_node_types(const qn::NetworkStruct<T>& sn, const char* who) {
219 for (std::size_t i = 0; i < sn.nodes.size(); ++i) {
220 switch (sn.nodes[i].nodetype) {
229 break;
230 default:
231 throw UnsupportedError(std::string(who) + ": node '" + sn.nodes[i].name + "' is a " +
232 lang::node_type_to_text(sn.nodes[i].nodetype) +
233 ", which the generator has no statement for");
234 }
235 }
236}
237
238/** The (k, c, i, m, p) of every positive `sn.rtnodes` entry, in QN2MATLAB's loop order, 0-based.
239 * Neither generator writes a Sink row or a closed-class Source row. */
240struct Route {
241 std::size_t k, c, i, m;
242 double p;
243};
244
245template <class T>
246std::vector<Route> routes(const qn::NetworkStruct<T>& sn) {
247 std::vector<Route> out;
248 const std::size_t K = sn.classes.size(), I = sn.nodes.size();
249 if (sn.rtnodes.rows() != I * K) return out;
250 for (std::size_t k = 0; k < K; ++k)
251 for (std::size_t c = 0; c < K; ++c)
252 for (std::size_t i = 0; i < I; ++i)
253 for (std::size_t m = 0; m < I; ++m) {
254 const double p = num_traits<T>::to_double(sn.rtnodes(i * K + k, m * K + c));
255 const lang::NodeType nt = sn.nodes[i].nodetype;
256 if (!(p > 0) || nt == lang::NodeType::Sink) continue;
257 if (nt == lang::NodeType::Source && std::isfinite(sn.classes[k].population)) continue;
258 out.push_back(Route{k, c, i, m, p});
259 }
260 return out;
261}
262
263/** Opens `path` for writing or throws, naming it. */
264inline std::ofstream open_out(const std::string& path) {
265 std::ofstream f(path.c_str());
266 if (!f) throw InputError("code_gen: cannot open " + path + " for writing");
267 return f;
268}
269
270} // namespace code_gen_detail
271
272// ---------------------------------------------------------------------------
273// QN2MATLAB
274// ---------------------------------------------------------------------------
275
276/**
277 * Port of `QN2MATLAB(model, modelName, fid)`: a MATLAB script that rebuilds the
278 * network from its refreshed struct.
279 */
280template <class T>
281void qn2matlab(const qn::NetworkStruct<T>& sn, const std::string& model_name, std::ostream& os) {
282 using namespace code_gen_detail;
283 using lang::NodeType;
284 check_node_types(sn, "QN2MATLAB");
285 const std::size_t K = sn.classes.size();
286 os << "model = Network('" << model_name << "');\n";
287 os << "\n%% Block 1: nodes";
288 os << "\n";
289 for (std::size_t n = 0; n < sn.nodes.size(); ++n) {
290 const std::size_t i = n + 1;
291 const qn::NodeDef& nd = sn.nodes[n];
292 switch (nd.nodetype) {
293 case NodeType::Source:
294 os << "node{" << i << "} = Source(model, '" << nd.name << "');\n";
295 break;
296 case NodeType::Delay:
297 os << "node{" << i << "} = DelayStation(model, '" << nd.name << "');\n";
298 break;
299 case NodeType::Queue: {
300 const qn::Station<T>& st = sn.stations[nd.station - 1];
301 os << "node{" << i << "} = Queue(model, '" << nd.name << "', SchedStrategy."
302 << sched_property(st.sched) << ");\n";
303 if (st.nservers > 1) os << "node{" << i << "}.setNumServers(" << fmt_d(st.nservers) << ");\n";
304 break;
305 }
306 case NodeType::Router:
307 os << "node{" << i << "} = Router(model, '" << nd.name << "');\n";
308 break;
309 case NodeType::Fork:
310 os << "node{" << i << "} = Fork(model, '" << nd.name << "');\n";
311 break;
312 case NodeType::Join:
313 os << "node{" << i << "} = Join(model, '" << nd.name << "', node{" << fork_of(sn, i) << "});\n";
314 break;
315 case NodeType::Sink:
316 os << "node{" << i << "} = Sink(model, '" << nd.name << "');\n";
317 break;
318 case NodeType::ClassSwitch:
319 os << "node{" << i << "} = Router(model, '" << nd.name
320 << "'); % Class switching is embedded in the routing matrix \n";
321 break;
322 default:
323 break;
324 }
325 }
326 os << "\n%% Block 2: classes\n";
327 for (std::size_t k = 0; k < K; ++k) {
328 const qn::JobClass& jc = sn.classes[k];
329 const std::string kk = fmt_d(k + 1);
330 if (std::isinf(jc.population)) {
331 os << "jobclass{" << kk << "} = OpenClass(model, '" << jc.name << "', " << fmt_d(jc.prio) << ");\n";
332 } else {
333 const std::size_t ref = jc.population > 0 ? sn.station_to_node[jc.refstat - 1] : empty_class_ref(sn, k);
334 os << "jobclass{" << kk << "} = ClosedClass(model, '" << jc.name << "', " << fmt_d(jc.population)
335 << ", node{" << ref << "}, " << fmt_d(jc.prio) << ");\n";
336 }
337 }
338 os << "\n";
339 for (std::size_t i = 0; i < sn.stations.size(); ++i)
340 for (std::size_t k = 0; k < K; ++k) {
341 const ProcSpec ps = proc_spec(sn, i, k);
342 if (ps.kind == ProcSpec::SKIP) continue;
343 const std::size_t nd = sn.station_to_node[i];
344 const std::string head =
345 "node{" + fmt_d(nd) + "}." + (ps.arrival ? "setArrival" : "setService") + "(jobclass{" + fmt_d(k + 1) + "}, ";
346 const std::string tail = "); % (" + sn.nodes[nd - 1].name + "," + sn.classes[k].name + ")\n";
347 switch (ps.kind) {
348 case ProcSpec::REPLAYER: os << head << "Replayer('" << ps.file << "')" << tail; break;
349 case ProcSpec::IMMEDIATE: os << head << "Immediate()" << tail; break;
350 case ProcSpec::EXP: os << head << "Exp.fitMean(" << fmt_f(ps.mean) << ")" << tail; break;
351 case ProcSpec::APH:
352 os << head << "APH.fitMeanAndSCV(" << fmt_f(ps.mean) << "," << fmt_f(ps.scv) << ")" << tail;
353 break;
354 case ProcSpec::DISABLED: os << head << "Disabled.getInstance()" << tail; break;
355 case ProcSpec::ERLANG:
356 os << head << "Erlang(" << fmt_f(ps.nphases / ps.mean) << "," << fmt_f(ps.nphases) << ")" << tail;
357 break;
358 default: break;
359 }
360 }
361 os << "\n%% Block 3: topology";
362 os << "\n";
363 os << "P = model.initRoutingMatrix(); % initialize routing matrix \n";
364 for (const Route& r : routes(sn)) {
365 const bool fork = sn.nodes[r.i].nodetype == NodeType::Fork;
366 os << "P{" << r.k + 1 << "," << r.c + 1 << "}(" << r.i + 1 << "," << r.m + 1
367 << ") = " << (fork ? std::string("1.0") : fmt_d(r.p)) << "; % (" << sn.nodes[r.i].name << ","
368 << sn.classes[r.k].name << ") -> (" << sn.nodes[r.m].name << "," << sn.classes[r.c].name << ")\n";
369 }
370 os << "model.link(P);\n";
371}
372
373/** QN2MATLAB on a model under construction; refreshes it first. */
374template <class T>
375void qn2matlab(qn::Network<T>& model, const std::string& model_name, std::ostream& os) {
376 qn2matlab(model.get_struct(), model_name, os);
377}
378
379/** QN2MATLAB with MATLAB's default model name. */
380template <class T>
381void qn2matlab(qn::Network<T>& model, std::ostream& os) {
382 qn2matlab(model.get_struct(), "myModel", os);
383}
384
385/** QN2MATLAB into a file, as MATLAB does when `fid` is a file name. */
386template <class T>
387void qn2matlab(qn::Network<T>& model, const std::string& model_name, const std::string& path) {
388 std::ofstream f = code_gen_detail::open_out(path);
389 qn2matlab(model.get_struct(), model_name, f);
390}
391
392/** QN2MATLAB returned as a string. */
393template <class T>
394std::string qn2matlab_string(qn::Network<T>& model, const std::string& model_name = "myModel") {
395 std::ostringstream os;
396 qn2matlab(model.get_struct(), model_name, os);
397 return os.str();
398}
399
400// ---------------------------------------------------------------------------
401// QN2JAVA
402// ---------------------------------------------------------------------------
403
404/**
405 * Port of `QN2JAVA(model, modelName, fid, headers)`: the body of a JLINE method
406 * `public static Network ex()` that rebuilds the network. With `headers` false
407 * only the statements are written, for pasting into an existing method.
408 */
409template <class T>
410void qn2java(const qn::NetworkStruct<T>& sn, const std::string& model_name, std::ostream& os,
411 bool headers = true) {
412 using namespace code_gen_detail;
413 using lang::NodeType;
414 check_node_types(sn, "QN2JAVA");
415 const std::size_t K = sn.classes.size();
416 if (headers) os << "\tpublic static Network ex() {\n";
417 os << "\t\tNetwork model = new Network(\"" << model_name << "\");\n";
418 os << "\n\t\t// Block 1: nodes";
419 os << "\t\t\t\n";
420 for (std::size_t n = 0; n < sn.nodes.size(); ++n) {
421 const std::size_t i = n + 1;
422 const qn::NodeDef& nd = sn.nodes[n];
423 switch (nd.nodetype) {
424 case NodeType::Source:
425 os << "\t\tSource node" << i << " = new Source(model, \"" << nd.name << "\");\n";
426 break;
427 case NodeType::Delay:
428 os << "\t\tDelay node" << i << " = new Delay(model, \"" << nd.name << "\");\n";
429 break;
430 case NodeType::Queue: {
431 const qn::Station<T>& st = sn.stations[nd.station - 1];
432 os << "\t\tQueue node" << i << " = new Queue(model, \"" << nd.name << "\", SchedStrategy."
433 << sched_property(st.sched) << ");\n";
434 if (st.nservers > 1) {
435 if (std::isinf(st.nservers))
436 os << "\t\tnode" << i << ".setNumberOfServers(Integer.MAX_VALUE);\n";
437 else
438 os << "\t\tnode" << i << ".setNumberOfServers(" << fmt_d(st.nservers) << ");\n";
439 }
440 break;
441 }
442 case NodeType::Router:
443 os << "\t\tRouter node" << i << " = new Router(model, \"" << nd.name << "\");\n";
444 break;
445 case NodeType::Fork:
446 os << "\t\tFork node" << i << " = new Fork(model, \"" << nd.name << "\");\n";
447 break;
448 case NodeType::Join:
449 os << "\t\tJoin node" << i << " = new Join(model, \"" << nd.name << "\", node" << fork_of(sn, i)
450 << ");\n";
451 break;
452 case NodeType::Sink:
453 os << "\t\tSink node" << i << " = new Sink(model, \"" << nd.name << "\");\n";
454 break;
455 case NodeType::ClassSwitch:
456 os << "\t\tRouter node" << i << " = new Router(model, \"" << nd.name
457 << "\"); // Dummy node, class switching is embedded in the routing matrix P \n";
458 break;
459 default:
460 break;
461 }
462 }
463 os << "\n\t\t// Block 2: classes\n";
464 for (std::size_t k = 0; k < K; ++k) {
465 const qn::JobClass& jc = sn.classes[k];
466 if (std::isinf(jc.population)) {
467 os << "\t\tOpenClass jobclass" << k + 1 << " = new OpenClass(model, \"" << jc.name << "\", "
468 << fmt_d(jc.prio) << ");\n";
469 } else {
470 const std::size_t ref = jc.population > 0 ? sn.station_to_node[jc.refstat - 1] : empty_class_ref(sn, k);
471 os << "\t\tClosedClass jobclass" << k + 1 << " = new ClosedClass(model, \"" << jc.name << "\", "
472 << fmt_d(jc.population) << ", node" << ref << ", " << fmt_d(jc.prio) << ");\n";
473 }
474 }
475 os << "\t\t\n";
476 for (std::size_t i = 0; i < sn.stations.size(); ++i)
477 for (std::size_t k = 0; k < K; ++k) {
478 const ProcSpec ps = proc_spec(sn, i, k);
479 if (ps.kind == ProcSpec::SKIP) continue;
480 const std::size_t nd = sn.station_to_node[i];
481 const std::string head = "\t\tnode" + fmt_d(nd) + "." + (ps.arrival ? "setArrival" : "setService") +
482 "(jobclass" + fmt_d(k + 1) + ", ";
483 const std::string tail = "); // (" + sn.nodes[nd - 1].name + "," + sn.classes[k].name + ")\n";
484 // The weight argument is written only for a service, and not for an Immediate or Disabled one.
485 double w = 1.0;
486 const std::vector<T>& sp = sn.stations[i].schedparam;
487 if (k < sp.size()) w = num_traits<T>::to_double(sp[k]);
488 const std::string weight = (!ps.arrival && w != 1.0) ? ", " + fmt_f(w) : std::string();
489 switch (ps.kind) {
490 case ProcSpec::REPLAYER:
491 os << head << "new Replayer(\"" << ps.file << "\")" << weight << tail;
492 break;
493 case ProcSpec::IMMEDIATE: os << head << "Immediate.getInstance()" << tail; break;
494 case ProcSpec::EXP: os << head << "Exp.fitMean(" << fmt_f(ps.mean) << ")" << weight << tail; break;
495 case ProcSpec::APH:
496 os << head << "APH.fitMeanAndSCV(" << fmt_f(ps.mean) << "," << fmt_f(ps.scv) << ")" << weight
497 << tail;
498 break;
499 case ProcSpec::DISABLED: os << head << "Disabled.getInstance()" << tail; break;
500 case ProcSpec::ERLANG:
501 os << head << "new Erlang(" << fmt_f(ps.nphases / ps.mean) << "," << fmt_d(ps.nphases) << ")"
502 << weight << tail;
503 break;
504 default: break;
505 }
506 }
507 os << "\n\t\t// Block 3: topology";
508 os << "\t\n";
509 os << "\t\tRoutingMatrix routingMatrix = model.initRoutingMatrix(); \n";
510 os << "\t\n";
511 for (const Route& r : routes(sn)) {
512 const bool fork = sn.nodes[r.i].nodetype == NodeType::Fork;
513 os << "\t\troutingMatrix.set(jobclass" << r.k + 1 << ", jobclass" << r.c + 1 << ", node" << r.i + 1
514 << ", node" << r.m + 1 << ", " << fmt_f(fork ? sn.nodes[r.i].tasks_per_link : r.p) << "); // ("
515 << sn.nodes[r.i].name << "," << sn.classes[r.k].name << ") -> (" << sn.nodes[r.m].name << ","
516 << sn.classes[r.c].name << ")\n";
517 }
518 os << "\n\t\tmodel.link(routingMatrix);\n\n";
519 if (headers) {
520 os << "\t\treturn model;\n";
521 os << "\t}\n";
522 }
523}
524
525/** QN2JAVA on a model under construction; refreshes it first. */
526template <class T>
527void qn2java(qn::Network<T>& model, const std::string& model_name, std::ostream& os, bool headers = true) {
528 qn2java(model.get_struct(), model_name, os, headers);
529}
530
531/** QN2JAVA with MATLAB's default model name. */
532template <class T>
533void qn2java(qn::Network<T>& model, std::ostream& os) {
534 qn2java(model.get_struct(), "myModel", os, true);
535}
536
537/** QN2JAVA into a file. */
538template <class T>
539void qn2java(qn::Network<T>& model, const std::string& model_name, const std::string& path,
540 bool headers = true) {
541 std::ofstream f = code_gen_detail::open_out(path);
542 qn2java(model.get_struct(), model_name, f, headers);
543}
544
545/** QN2JAVA returned as a string. */
546template <class T>
547std::string qn2java_string(qn::Network<T>& model, const std::string& model_name = "myModel",
548 bool headers = true) {
549 std::ostringstream os;
550 qn2java(model.get_struct(), model_name, os, headers);
551 return os.str();
552}
553
554// ---------------------------------------------------------------------------
555// LQN2JAVA
556// ---------------------------------------------------------------------------
557
558namespace code_gen_detail {
559
560/** `jnum`: a Java double literal at the shortest spelling that reads back as `v`. */
561inline std::string j_num(double v) {
562 if (std::isnan(v)) return "Double.NaN";
563 if (std::isinf(v)) return v > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY";
564 char buf[40];
565 if (v == std::round(v) && std::fabs(v) < 9007199254740992.0) {
566 std::snprintf(buf, sizeof(buf), "%.0f.0", v == 0.0 ? 0.0 : v);
567 return buf;
568 }
569 for (int p = 15; p <= 17; ++p) {
570 std::snprintf(buf, sizeof(buf), "%.*g", p, v);
571 if (std::strtod(buf, nullptr) == v) break;
572 }
573 return buf;
574}
575
576template <class T>
577std::string j_num_t(const T& v) {
579}
580
581/** `jarr`: a `new double[]{...}` literal. */
582template <class T>
583std::string j_arr(const std::vector<T>& v) {
584 std::string s = "new double[]{";
585 for (std::size_t i = 0; i < v.size(); ++i) s += (i ? ", " : "") + j_num_t(v[i]);
586 return s + "}";
587}
588
589/** `jmat` of a row vector: `new Matrix(new double[][]{{a, b}})`. */
590template <class T>
591std::string j_mat_row(const std::vector<T>& v) {
592 std::string s = "new Matrix(new double[][]{{";
593 for (std::size_t i = 0; i < v.size(); ++i) s += (i ? ", " : "") + j_num_t(v[i]);
594 return s + "}})";
595}
596
597/** `jmat` of a matrix, row by row. */
598template <class T>
599std::string j_mat(const Matrix<T>& m) {
600 std::string s = "new Matrix(new double[][]{";
601 for (std::size_t i = 0; i < m.rows(); ++i) {
602 s += i ? ", {" : "{";
603 for (std::size_t j = 0; j < m.cols(); ++j) s += (j ? ", " : "") + j_num_t(m(i, j));
604 s += "}";
605 }
606 return s + "})";
607}
608
609inline std::string j_round(double v) {
610 char buf[32];
611 std::snprintf(buf, sizeof(buf), "%.0f", std::round(v) == 0.0 ? 0.0 : std::round(v));
612 return buf;
613}
614
615/**
616 * `javaDist`: the Java constructor call rebuilding `d` with the same parameters.
617 *
618 * A law with no Java constructor listed here is written as an APH fitted to its
619 * mean and SCV, where MATLAB also warns.
620 */
621template <class T>
622std::string java_dist(const lang::Distrib<T>& d) {
623 using lang::ProcessType;
624 const std::vector<T>& p = d.params;
625 auto par = [&](std::size_t k) -> const T& {
626 if (k >= p.size())
627 throw UnsupportedError(std::string("LQN2JAVA: the ") + lang::process_to_text(d.type) +
628 " carries fewer parameters than its constructor takes");
629 return p[k];
630 };
631 auto jn = [&](std::size_t k) { return j_num_t(par(k)); };
632 auto jr = [&](std::size_t k) { return j_round(num_traits<T>::to_double(par(k))); };
633 auto half = [&](std::size_t from, std::size_t n) { return std::vector<T>(p.begin() + from, p.begin() + from + n); };
634 auto cls2 = [&](const char* c) { return std::string("new ") + c + "(" + jn(0) + ", " + jn(1) + ")"; };
635 if (d.disabled || d.type == ProcessType::DISABLED) return "new Disabled()";
636 switch (d.type) {
637 case ProcessType::IMMEDIATE: return "new Immediate()";
638 case ProcessType::EXP:
639 return "new Exp(" + (p.empty() ? j_num(1.0 / num_traits<T>::to_double(d.mean)) : jn(0)) + ")";
640 case ProcessType::DET: return "new Det(" + jn(0) + ")";
641 case ProcessType::GEOMETRIC: return "new Geometric(" + jn(0) + ")";
642 case ProcessType::POISSON: return "new Poisson(" + jn(0) + ")";
643 case ProcessType::BERNOULLI: return "new Bernoulli(" + jn(0) + ")";
644 case ProcessType::ERLANG: return "new Erlang(" + jn(0) + ", " + jr(1) + ")";
645 case ProcessType::HYPEREXP:
646 if (p.size() == 3) return "new HyperExp(" + jn(0) + ", " + jn(1) + ", " + jn(2) + ")";
647 return "new HyperExp(" + j_arr(half(0, p.size() / 2)) + ", " + j_arr(half(p.size() / 2, p.size() / 2)) + ")";
648 case ProcessType::GAMMA: return cls2("Gamma");
649 case ProcessType::LOGNORMAL: return cls2("Lognormal");
650 case ProcessType::UNIFORM: return cls2("Uniform");
651 case ProcessType::PARETO: return cls2("Pareto");
652 case ProcessType::NORMAL: return cls2("Normal");
653 case ProcessType::DUNIFORM: return cls2("DiscreteUniform");
654 case ProcessType::BINOMIAL: return "new Binomial(" + jr(0) + ", " + jn(1) + ")";
655 case ProcessType::WEIBULL: return "new Weibull(" + jn(1) + ", " + jn(0) + ")"; // stored (scale, shape)
656 case ProcessType::COXIAN:
657 case ProcessType::COX2:
658 // the 3-argument Coxian(mu1, mu2, phi1) is written as mu = [mu1, mu2], phi = [phi1, 1], which cox2() stores
659 return "new Coxian(" + j_mat_row(half(0, p.size() / 2)) + ", " + j_mat_row(half(p.size() / 2, p.size() / 2)) + ")";
660 case ProcessType::PH: return "new PH(" + j_mat_row(p) + ", " + j_mat(d.D0) + ")";
661 case ProcessType::APH: return "new APH(" + j_mat_row(p) + ", " + j_mat(d.D0) + ")";
662 case ProcessType::ME: return "new ME(" + j_mat_row(p) + ", " + j_mat(d.D0) + ")";
663 case ProcessType::MAP: return "new MAP(" + j_mat(d.D0) + ", " + j_mat(d.D1) + ")";
664 case ProcessType::RAP: return "new RAP(" + j_mat(d.D0) + ", " + j_mat(d.D1) + ")";
665 case ProcessType::MMPP2: return "new MMPP2(" + jn(0) + ", " + jn(1) + ", " + jn(2) + ", " + jn(3) + ")";
666 case ProcessType::ZIPF: return "new Zipf(" + jn(0) + ", " + jr(1) + ")";
667 case ProcessType::DISCRETESAMPLER: {
668 std::vector<T> x = d.trace;
669 if (x.empty())
670 for (std::size_t k = 0; k < p.size(); ++k) x.push_back(num_traits<T>::from_int(static_cast<long>(k + 1)));
671 return "new DiscreteSampler(" + j_mat_row(p) + ", " + j_mat_row(x) + ")";
672 }
673 case ProcessType::REPLAYER: {
674 if (d.trace_file.empty())
675 throw UnsupportedError("LQN2JAVA: a Replayer built from in-memory samples has no trace file to name");
676 std::string f;
677 for (char c : d.trace_file) {
678 if (c == '\\' || c == '"') f.push_back('\\');
679 f.push_back(c);
680 }
681 return "new Replayer(\"" + f + "\")";
682 }
683 default:
684 return "APH.fitMeanAndSCV(" + j_num_t(d.mean) + ", " + j_num_t(d.scv) + ")";
685 }
686}
687
688/**
689 * `sn.replygraph`: (nacts+1) x (nentries+1), 1-based, true where the activity
690 * replies to the entry. Declared replies, plus the implicit leaf replies of
691 * getStruct.m for an entry that declares none.
692 */
693template <class T>
694std::vector<std::vector<bool>> reply_graph(const lqn::LqnModel<T>& m, const lqn::LqnStruct<T>& sn) {
695 std::vector<std::vector<bool>> g(sn.nacts + 1, std::vector<bool>(sn.nentries + 1, false));
696 std::map<std::string, std::size_t> ent, act;
697 for (std::size_t e = 1; e <= sn.nentries; ++e) ent[sn.names[sn.eshift + e]] = e;
698 for (std::size_t a = 1; a <= sn.nacts; ++a) act[sn.names[sn.ashift + a]] = a;
699 const std::map<std::string, std::vector<std::string>> rep = lqn::detail::reply_activities(m, sn);
700 for (const auto& kv : rep) {
701 const auto ei = ent.find(kv.first);
702 if (ei == ent.end()) continue;
703 for (const std::string& an : kv.second) {
704 const auto ai = act.find(an);
705 if (ai != act.end()) g[ai->second][ei->second] = true;
706 }
707 }
708 return g;
709}
710
711/** The quorum of the AND-join into `post_name` on task slot `tslot`, 0 when it declares none. */
712template <class T>
713std::size_t and_join_quorum(const lqn::LqnModel<T>& m, std::size_t tslot, const std::string& post_name) {
714 if (tslot >= m.tasks.size()) return 0;
715 for (const lqn::detail::RawPrecedence<T>& p : m.tasks[tslot].precedences) {
716 if (p.pretype != lang::PrecedenceType::PRE_AND) continue;
717 if (std::find(p.postacts.begin(), p.postacts.end(), post_name) == p.postacts.end()) continue;
718 if (p.has_quorum) return p.quorum;
719 if (!p.preparams.empty()) return static_cast<std::size_t>(num_traits<T>::to_double(p.preparams[0]));
720 return 0;
721 }
722 return 0;
723}
724
725inline std::string upper_nospace(const std::string& s) {
726 std::string out;
727 for (std::size_t i = 0; i < s.size(); ++i)
728 if (s[i] != ' ') out.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(s[i]))));
729 return out;
730}
731
732} // namespace code_gen_detail
733
734/**
735 * Port of `LQN2JAVA(model, modelName, fid)`: a JLINE program that rebuilds the
736 * layered network and solves it with SolverLN.
737 *
738 * It takes the intermediate `LqnModel` rather than the struct alone, because
739 * the struct keeps neither the declared reply activities nor an AND-join's
740 * quorum, both of which MATLAB reads off the model object.
741 */
742template <class T>
743void lqn2java(const lqn::LqnModel<T>& model, const std::string& model_name, std::ostream& os) {
744 using namespace code_gen_detail;
747 const std::vector<std::vector<bool>> replygraph = reply_graph(model, sn);
748 const T zero = num_traits<T>::from_int(0);
749 auto w = [&](std::size_t i, std::size_t j) { return num_traits<T>::to_double(sn.graph.get(i, j)); };
750
751 os << "package jline.examples;\n\n";
752 os << "import java.util.ArrayList;\n";
753 os << "import jline.lang.*;\n";
754 os << "import jline.lang.layered.*;\n";
755 os << "import jline.lang.constant.*;\n";
756 os << "import jline.lang.processes.*;\n";
757 os << "import jline.util.matrix.Matrix;\n";
758 os << "import jline.solvers.ln.SolverLN;\n\n";
759 os << "public class TestSolver" << upper_nospace(model_name) << " {\n\n";
760 os << "\tpublic static void main(String[] args) throws Exception{\n\n";
761 os << "\tLayeredNetwork model = new LayeredNetwork(\"" << model_name << "\");\n";
762 os << "\n";
763 for (std::size_t h = 1; h <= sn.nhosts; ++h) {
764 const std::string mult = std::isinf(sn.mult[h]) ? std::string("Integer.MAX_VALUE") : fmt_d(sn.mult[h]);
765 os << "\tProcessor P" << h << " = new Processor(model, \"" << sn.names[h] << "\", " << mult << ", "
766 << sched_feature(sn.sched[h]) << ");\n";
767 if (sn.repl[h] != 1) os << "P" << h << ".setReplication(" << fmt_d(sn.repl[h]) << ");\n";
768 }
769 os << "\n";
770 for (std::size_t t = 1; t <= sn.ntasks; ++t) {
771 const std::size_t tidx = sn.tshift + t;
772 const std::string mult =
773 std::isinf(sn.mult[tidx]) ? std::string("Integer.MAX_VALUE") : fmt_d(sn.mult[tidx]);
774 os << "\tTask T" << t << " = new Task(model, \"" << sn.names[tidx] << "\", " << mult << ", "
775 << sched_feature(sn.sched[tidx]) << ").on(P" << sn.parent[tidx] << ");\n";
776 if (sn.repl[tidx] != 1) os << "\tT" << t << ".setReplication(" << fmt_d(sn.repl[tidx]) << ");\n";
777 const lang::Distrib<T>& th = sn.think[tidx];
779 os << "\tT" << t << ".setThinkTime(" << java_dist(th) << ");\n";
780 }
781 os << "\n";
782 for (std::size_t e = 1; e <= sn.nentries; ++e) {
783 const std::size_t eidx = sn.eshift + e;
784 os << "\tEntry E" << e << " = new Entry(model, \"" << sn.names[eidx] << "\").on(T"
785 << sn.parent[eidx] - sn.tshift << ");\n";
786 }
787 os << "\n";
788 for (std::size_t a = 1; a <= sn.nacts; ++a) {
789 const std::size_t aidx = sn.ashift + a;
790 const std::size_t tidx = sn.parent[aidx];
791 std::string bound;
792 for (std::size_t e = 1; e <= sn.nentries; ++e)
793 if (sn.graph.get(sn.eshift + e, aidx) != zero) bound += ".boundTo(E" + std::to_string(e) + ")";
794 std::string replies;
795 if (sn.sched[tidx] != lang::SchedStrategy::REF) {
796 std::vector<std::size_t> rt;
797 bool all_nonref = true;
798 for (std::size_t e = 1; e <= sn.nentries; ++e)
799 if (replygraph[a][e]) {
800 rt.push_back(e);
801 if (sn.isref[sn.parent[sn.eshift + e]]) all_nonref = false;
802 }
803 if (!rt.empty() && all_nonref)
804 for (std::size_t e : rt) replies += ".repliesTo(E" + std::to_string(e) + ")";
805 }
806 std::string calls;
807 for (std::size_t c = 1; c <= sn.ncalls; ++c) {
808 if (sn.callpair_src[c] != aidx) continue;
809 const std::string target = std::to_string(sn.callpair_dst[c] - sn.eshift);
810 const std::string mean = fmt_g(num_traits<T>::to_double(sn.callproc_mean[c]));
811 if (sn.calltype[c] == lang::CallType::SYNC) calls += ".synchCall(E" + target + "," + mean + ")";
812 else if (sn.calltype[c] == lang::CallType::ASYNC) calls += ".asynchCall(E" + target + "," + mean + ")";
813 }
814 os << "\tActivity A" << a << " = new Activity(model, \"" << sn.names[aidx] << "\", "
815 << java_dist(sn.hostdem[aidx]) << ").on(T" << tidx - sn.tshift << ");";
816 if (!bound.empty()) os << " A" << a << bound << ";";
817 if (!calls.empty()) os << " A" << a << calls << ";";
818 if (!replies.empty()) os << " A" << a << replies << ";";
819 os << "\n";
820 // activity think time; the Activity default is Immediate, so only a non-default one is written
821 const lang::Distrib<T>& ath = sn.actthink[aidx];
823 os << "\tA" << a << ".setThinkTime(" << java_dist(ath) << ");\n";
824 }
825 os << "\n";
826
827 // Sequential precedences
828 for (std::size_t ai = 1; ai <= sn.nacts; ++ai) {
829 const std::size_t aidx = sn.ashift + ai;
830 const std::size_t tidx = sn.parent[aidx];
831 for (std::size_t bidx : sn.graph.succ(aidx))
832 if (bidx > sn.ashift && sn.actpretype[aidx] == PrecedenceType::PRE_SEQ &&
833 sn.actposttype[bidx] == PrecedenceType::POST_SEQ)
834 os << "\tT" << tidx - sn.tshift << ".addPrecedence(ActivityPrecedence.Serial(\"" << sn.names[aidx]
835 << "\", \"" << sn.names[bidx] << "\"));\n";
836 }
837
838 // Loop precedences (POST_LOOP): follow the chain of loop successors to the end activity, whose weight is 1/count
839 bool has_pre_acts = false, has_post_acts = false;
840 std::vector<bool> processed(sn.nacts + 1, false);
841 for (std::size_t ai = 1; ai <= sn.nacts; ++ai) {
842 const std::size_t aidx = sn.ashift + ai;
843 const std::size_t tidx = sn.parent[aidx];
844 if (processed[ai]) continue;
845 for (std::size_t bidx : sn.graph.succ(aidx)) {
846 if (!(bidx > sn.ashift && sn.actposttype[bidx] == PrecedenceType::POST_LOOP)) continue;
847 if (processed[bidx - sn.ashift]) continue;
848 const std::size_t loop_start = bidx;
849 std::vector<std::string> names;
850 std::size_t cur = loop_start;
851 while (true) {
852 names.push_back(sn.names[cur]);
853 processed[cur - sn.ashift] = true;
854 std::size_t end_idx = 0, next_idx = 0;
855 for (std::size_t s : sn.graph.succ(cur)) {
856 if (s <= sn.ashift || sn.actposttype[s] != PrecedenceType::POST_LOOP) continue;
857 if (s == loop_start) continue;
858 const double wt = w(cur, s);
859 if (wt > 0 && wt < 1) end_idx = s;
860 else next_idx = s;
861 }
862 if (end_idx > 0) {
863 const double wt = w(cur, end_idx);
864 const double counts = wt > 0 ? 1.0 / wt : 1.0;
865 names.push_back(sn.names[end_idx]);
866 processed[end_idx - sn.ashift] = true;
867 os << "\n\t// Loop Activity Precedence \n";
868 if (!has_pre_acts) {
869 os << "\tArrayList<String> precActs = new ArrayList<String>();\n";
870 has_pre_acts = true;
871 } else {
872 os << "\tprecActs = new ArrayList<String>();\n";
873 }
874 for (const std::string& n : names) os << "\tprecActs.add(\"" << n << "\");\n";
875 os << "\tT" << tidx - sn.tshift << ".addPrecedence(ActivityPrecedence.Loop(\"" << sn.names[aidx]
876 << "\", precActs, Matrix.singleton(" << fmt_g(counts) << ")));\n";
877 break;
878 } else if (next_idx > 0) {
879 cur = next_idx;
880 } else {
881 break;
882 }
883 }
884 break;
885 }
886 }
887
888 // OrFork precedences (POST_OR)
889 bool has_probs = false;
890 {
891 std::size_t prec_marker = 0;
892 std::string prec_acts, prob_string;
893 for (std::size_t ai = 1; ai <= sn.nacts; ++ai) {
894 const std::size_t aidx = sn.ashift + ai;
895 const std::size_t tidx = sn.parent[aidx];
896 std::size_t prob_ctr = 0;
897 for (std::size_t bidx : sn.graph.succ(aidx)) {
898 if (!(bidx > sn.ashift && sn.actposttype[bidx] == PrecedenceType::POST_OR)) continue;
899 const std::string add = "\tprecActs.add(\"" + sn.names[bidx] + "\");\n";
900 ++prob_ctr;
901 const std::string prob =
902 "\tprobs.set(0," + std::to_string(prob_ctr - 1) + "," + fmt_g(w(aidx, bidx)) + ");\n";
903 if (prec_marker == 0) {
904 prec_marker = aidx - sn.ashift;
905 prec_acts = add;
906 prob_string = prob;
907 } else {
908 prec_acts += add;
909 prob_string += prob;
910 }
911 }
912 if (prec_marker > 0) {
913 os << "\n\t// OrFork Activity Precedence \n";
914 if (!has_pre_acts) {
915 os << "\tArrayList<String> precActs = new ArrayList<String>();\n";
916 has_pre_acts = true;
917 } else {
918 os << "\tprecActs = new ArrayList<String>();\n";
919 }
920 if (!has_probs) {
921 os << "\tMatrix probs = new Matrix(1," << prob_ctr << ");\n";
922 has_probs = true;
923 } else {
924 os << "\tprobs = new Matrix(1," << prob_ctr << ");\n";
925 }
926 os << prec_acts << prob_string;
927 os << "\tT" << tidx - sn.tshift << ".addPrecedence(ActivityPrecedence.OrFork(\""
928 << sn.names[prec_marker + sn.ashift] << "\", precActs, probs));\n";
929 prec_marker = 0;
930 }
931 }
932 }
933
934 // AndFork precedences (POST_AND)
935 for (std::size_t ai = 1; ai <= sn.nacts; ++ai) {
936 const std::size_t aidx = sn.ashift + ai;
937 const std::size_t tidx = sn.parent[aidx];
938 std::string post_acts;
939 for (std::size_t bidx : sn.graph.succ(aidx))
940 if (bidx > sn.ashift && sn.actposttype[bidx] == PrecedenceType::POST_AND)
941 post_acts += "\tpostActs.add(\"" + sn.names[bidx] + "\");\n";
942 if (post_acts.empty()) continue;
943 os << "\n\t// AndFork Activity Precedence \n";
944 if (!has_post_acts) {
945 os << "\tArrayList<String> postActs = new ArrayList<String>();\n";
946 has_post_acts = true;
947 } else {
948 os << "\t postActs = new ArrayList<String>();\n";
949 }
950 os << post_acts;
951 os << "\tT" << tidx - sn.tshift << ".addPrecedence(ActivityPrecedence.AndFork(\"" << sn.names[aidx]
952 << "\", postActs));\n";
953 }
954
955 // OrJoin (PRE_OR) and AndJoin (PRE_AND) precedences, scanned from the last activity back
956 for (int pass = 0; pass < 2; ++pass) {
957 const PrecedenceType want = pass == 0 ? PrecedenceType::PRE_OR : PrecedenceType::PRE_AND;
958 for (std::size_t bi = sn.nacts; bi >= 1; --bi) {
959 const std::size_t bidx = sn.ashift + bi;
960 const std::size_t tidx = sn.parent[bidx];
961 std::string prec_acts;
962 for (std::size_t aidx : sn.graph.pred(bidx))
963 if (aidx > sn.ashift && sn.actpretype[aidx] == want)
964 prec_acts += "\tprecActs.add(\"" + sn.names[aidx] + "\");\n";
965 if (prec_acts.empty()) continue;
966 os << (pass == 0 ? "\n\t// OrJoin Activity Precedence \n" : "\n\t// AndJoin Activity Precedence \n");
967 if (!has_pre_acts) {
968 os << "\tArrayList<String> precActs = new ArrayList<String>();\n";
969 has_pre_acts = true;
970 } else {
971 os << "\tprecActs = new ArrayList<String>();\n";
972 }
973 os << prec_acts;
974 const std::size_t local = tidx - sn.tshift;
975 if (pass == 0) {
976 os << "\tT" << local << ".addPrecedence(ActivityPrecedence.OrJoin(precActs, \"" << sn.names[bidx]
977 << "\"));\n";
978 } else {
979 const std::size_t q = and_join_quorum(model, local - 1, sn.names[bidx]);
980 if (q == 0)
981 os << "\tT" << local << ".addPrecedence(ActivityPrecedence.AndJoin(precActs, \""
982 << sn.names[bidx] << "\"));\n";
983 else
984 os << "\tT" << local << ".addPrecedence(ActivityPrecedence.AndJoin(precActs, \""
985 << sn.names[bidx] << "\", Matrix.singleton(" << j_round(double(q)) << ")));\n";
986 }
987 }
988 }
989
990 os << "\n\t// Model solution \n";
991 os << "\tSolverLN solver = new SolverLN(model);\n";
992 os << "\tsolver.getEnsembleAvg();\n";
993 os << "\t}\n}\n";
994}
995
996/** LQN2JAVA on a model under construction. */
997template <class T>
998void lqn2java(const lqn::LqnBuilder<T>& b, const std::string& model_name, std::ostream& os) {
999 lqn2java(b.model(), model_name, os);
1000}
1001
1002/** LQN2JAVA with MATLAB's default model name. */
1003template <class T>
1004void lqn2java(const lqn::LqnModel<T>& model, std::ostream& os) {
1005 lqn2java(model, "myLayeredModel", os);
1006}
1007
1008/** LQN2JAVA into a file. */
1009template <class T>
1010void lqn2java(const lqn::LqnModel<T>& model, const std::string& model_name, const std::string& path) {
1011 std::ofstream f = code_gen_detail::open_out(path);
1012 lqn2java(model, model_name, f);
1013}
1014
1015/** LQN2JAVA returned as a string. */
1016template <class T>
1017std::string lqn2java_string(const lqn::LqnModel<T>& model, const std::string& model_name = "myLayeredModel") {
1018 std::ostringstream os;
1019 lqn2java(model, model_name, os);
1020 return os.str();
1021}
1022
1023// ---------------------------------------------------------------------------
1024// LQN2MATLAB
1025// ---------------------------------------------------------------------------
1026
1027namespace code_gen_detail {
1028
1029/** `scalar2code`: a double at the shortest of 15..17 significant digits that reads back as itself. */
1030inline std::string m_scalar(double v) {
1031 if (std::isnan(v)) return "NaN";
1032 if (std::isinf(v)) return v > 0 ? "Inf" : "-Inf";
1033 if (v == std::round(v) && std::fabs(v) < 1e15) {
1034 char buf[32];
1035 std::snprintf(buf, sizeof(buf), "%.0f", v == 0.0 ? 0.0 : v);
1036 return buf;
1037 }
1038 char buf[40];
1039 for (int p = 15; p <= 17; ++p) {
1040 std::snprintf(buf, sizeof(buf), "%.*g", p, v);
1041 if (std::strtod(buf, nullptr) == v) break;
1042 }
1043 return buf;
1044}
1045
1046template <class T>
1047std::string m_scalar_t(const T& v) {
1049}
1050
1051/** `num2code` of a row vector: `[a, b]`, a scalar when it has one element, `[]` when empty. */
1052template <class T>
1053std::string m_row(const std::vector<T>& v) {
1054 if (v.empty()) return "[]";
1055 if (v.size() == 1) return m_scalar_t(v[0]);
1056 std::string s = "[";
1057 for (std::size_t i = 0; i < v.size(); ++i) s += (i ? ", " : "") + m_scalar_t(v[i]);
1058 return s + "]";
1059}
1060
1061/** `num2code` of a matrix: `[a, b; c, d]`. */
1062template <class T>
1063std::string m_matrix(const Matrix<T>& M) {
1064 if (M.rows() == 0 || M.cols() == 0) return "[]";
1065 if (M.rows() == 1 && M.cols() == 1) return m_scalar_t(M(0, 0));
1066 std::string s = "[";
1067 for (std::size_t i = 0; i < M.rows(); ++i) {
1068 if (i) s += "; ";
1069 for (std::size_t j = 0; j < M.cols(); ++j) s += (j ? ", " : "") + m_scalar_t(M(i, j));
1070 }
1071 return s + "]";
1072}
1073
1074/** `q(str)`: a MATLAB single-quoted literal. */
1075inline std::string m_quote(const std::string& s) {
1076 std::string out = "'";
1077 for (char c : s) {
1078 if (c == '\'') out += "''";
1079 else out.push_back(c);
1080 }
1081 return out + "'";
1082}
1083
1084/** `cellstr2code`: `{'a', 'b'}`. */
1085inline std::string m_cellstr(const std::vector<std::string>& c) {
1086 std::string s = "{";
1087 for (std::size_t i = 0; i < c.size(); ++i) s += (i ? ", " : "") + m_quote(c[i]);
1088 return s + "}";
1089}
1090
1091inline std::string m_repl(lang::ReplacementStrategy r) {
1092 switch (r) {
1093 case lang::ReplacementStrategy::RR: return "ReplacementStrategy.RR";
1094 case lang::ReplacementStrategy::FIFO: return "ReplacementStrategy.FIFO";
1095 case lang::ReplacementStrategy::SFIFO: return "ReplacementStrategy.SFIFO";
1096 case lang::ReplacementStrategy::LRU: return "ReplacementStrategy.LRU";
1097 case lang::ReplacementStrategy::HLRU: return "ReplacementStrategy.HLRU";
1098 case lang::ReplacementStrategy::CLIMB: return "ReplacementStrategy.CLIMB";
1099 case lang::ReplacementStrategy::QLRU: return "ReplacementStrategy.QLRU";
1100 }
1101 return "ReplacementStrategy.RR";
1102}
1103
1104/**
1105 * `dist2code`: the constructor call that rebuilds `d` from its parameters, which
1106 * this port keeps in MATLAB getParam order. A law with no constructor spelling
1107 * here is written as the APH fit of its moments, as MATLAB does for a class it
1108 * does not know.
1109 */
1110template <class T>
1111std::string m_dist(const lang::Distrib<T>& d) {
1112 using lang::ProcessType;
1113 const std::vector<T>& p = d.params;
1114 auto par = [&](std::size_t k) -> std::string {
1115 if (k >= p.size())
1116 throw UnsupportedError(std::string("LQN2MATLAB: the ") + lang::process_to_text(d.type) +
1117 " carries fewer parameters than its constructor takes");
1118 return m_scalar_t(p[k]);
1119 };
1120 auto half = [&](std::size_t from, std::size_t n) {
1121 return m_row(std::vector<T>(p.begin() + from, p.begin() + from + n));
1122 };
1123 if (d.disabled || d.type == ProcessType::DISABLED) return "Disabled()";
1124 switch (d.type) {
1125 case ProcessType::IMMEDIATE: return "Immediate()";
1126 case ProcessType::EXP:
1127 return "Exp(" + (p.empty() ? m_scalar(1.0 / num_traits<T>::to_double(d.mean)) : par(0)) + ")";
1128 case ProcessType::DET: return "Det(" + par(0) + ")";
1129 case ProcessType::ERLANG: return "Erlang(" + par(0) + ", " + par(1) + ")";
1130 case ProcessType::HYPEREXP:
1131 if (p.size() == 3) return "HyperExp(" + par(0) + ", " + par(1) + ", " + par(2) + ")";
1132 return "HyperExp(" + half(0, p.size() / 2) + ", " + half(p.size() / 2, p.size() / 2) + ")";
1133 case ProcessType::GAMMA: return "Gamma(" + par(0) + ", " + par(1) + ")";
1134 case ProcessType::LOGNORMAL: return "Lognormal(" + par(0) + ", " + par(1) + ")";
1135 case ProcessType::UNIFORM: return "Uniform(" + par(0) + ", " + par(1) + ")";
1136 case ProcessType::PARETO: return "Pareto(" + par(0) + ", " + par(1) + ")";
1137 case ProcessType::NORMAL: return "Normal(" + par(0) + ", " + par(1) + ")";
1138 case ProcessType::BINOMIAL: return "Binomial(" + par(0) + ", " + par(1) + ")";
1139 case ProcessType::DUNIFORM: return "DiscreteUniform(" + par(0) + ", " + par(1) + ")";
1140 case ProcessType::WEIBULL: return "Weibull(" + par(1) + ", " + par(0) + ")"; // stored (scale, shape)
1141 case ProcessType::GEOMETRIC: return "Geometric(" + par(0) + ")";
1142 case ProcessType::POISSON: return "Poisson(" + par(0) + ")";
1143 case ProcessType::BERNOULLI: return "Bernoulli(" + par(0) + ")";
1144 case ProcessType::COXIAN:
1145 case ProcessType::COX2:
1146 // params are mu then phi; cox2() is MATLAB's 3-argument Coxian(mu1, mu2, phi1)
1147 if (d.cox_scalar_form && p.size() == 4) return "Coxian(" + par(0) + ", " + par(1) + ", " + par(2) + ")";
1148 return "Coxian(" + half(0, p.size() / 2) + ", " + half(p.size() / 2, p.size() / 2) + ")";
1149 case ProcessType::PH: return "PH(" + m_row(p) + ", " + m_matrix(d.D0) + ")";
1150 case ProcessType::APH: return "APH(" + m_row(p) + ", " + m_matrix(d.D0) + ")";
1151 case ProcessType::ME: return "ME(" + m_row(p) + ", " + m_matrix(d.D0) + ")";
1152 case ProcessType::MAP: return "MAP(" + m_matrix(d.D0) + ", " + m_matrix(d.D1) + ")";
1153 case ProcessType::RAP: return "RAP(" + m_matrix(d.D0) + ", " + m_matrix(d.D1) + ")";
1154 case ProcessType::MMPP2:
1155 return "MMPP2(" + par(0) + ", " + par(1) + ", " + par(2) + ", " + par(3) + ")";
1156 case ProcessType::ZIPF: return "Zipf(" + par(0) + ", " + par(1) + ")";
1157 case ProcessType::DISCRETESAMPLER: {
1158 std::vector<T> x = d.trace;
1159 if (x.empty())
1160 for (std::size_t k = 0; k < p.size(); ++k) x.push_back(num_traits<T>::from_int(static_cast<long>(k + 1)));
1161 return "DiscreteSampler(" + m_row(p) + ", " + m_row(x) + ")";
1162 }
1163 case ProcessType::REPLAYER:
1164 if (d.trace_file.empty())
1165 throw UnsupportedError("LQN2MATLAB: a Replayer built from in-memory samples has no trace file to name");
1166 return "Replayer(" + m_quote(d.trace_file) + ")";
1167 default:
1168 return "APH.fitMeanAndSCV(" + m_scalar_t(d.mean) + ", " + m_scalar_t(d.scv) + ")";
1169 }
1170}
1171
1172/** True for a think / setup / delay-off time left at its constructor default. */
1173template <class T>
1177
1178/** The `ActivityPrecedence` expression of one declared precedence (`precCode`). */
1179template <class T>
1180std::string m_prec(const lqn::detail::RawPrecedence<T>& ap) {
1182 const std::vector<std::string>& pre = ap.preacts;
1183 const std::vector<std::string>& post = ap.postacts;
1184 std::vector<T> preparams = ap.preparams;
1185 if (preparams.empty() && ap.has_quorum) preparams.push_back(num_traits<T>::from_int(static_cast<long>(ap.quorum)));
1186 const PrecedenceType a = ap.pretype, b = ap.posttype;
1187 if (a == PrecedenceType::PRE_SEQ && b == PrecedenceType::POST_SEQ && pre.size() == 1 && post.size() == 1 &&
1188 preparams.empty() && ap.postparams.empty())
1189 return "ActivityPrecedence.Serial(" + m_quote(pre[0]) + ", " + m_quote(post[0]) + ")";
1190 if (a == PrecedenceType::PRE_AND && b == PrecedenceType::POST_SEQ && post.size() == 1 && ap.postparams.empty()) {
1191 if (preparams.empty()) return "ActivityPrecedence.AndJoin(" + m_cellstr(pre) + ", " + m_quote(post[0]) + ")";
1192 return "ActivityPrecedence.AndJoin(" + m_cellstr(pre) + ", " + m_quote(post[0]) + ", " + m_row(preparams) + ")";
1193 }
1194 if (a == PrecedenceType::PRE_OR && b == PrecedenceType::POST_SEQ && post.size() == 1 && preparams.empty() &&
1195 ap.postparams.empty())
1196 return "ActivityPrecedence.OrJoin(" + m_cellstr(pre) + ", " + m_quote(post[0]) + ")";
1197 if (a == PrecedenceType::PRE_SEQ && b == PrecedenceType::POST_AND && pre.size() == 1 && preparams.empty() &&
1198 ap.postparams.empty())
1199 return "ActivityPrecedence.AndFork(" + m_quote(pre[0]) + ", " + m_cellstr(post) + ")";
1200 if (a == PrecedenceType::PRE_SEQ && b == PrecedenceType::POST_OR && pre.size() == 1 && preparams.empty())
1201 return "ActivityPrecedence.OrFork(" + m_quote(pre[0]) + ", " + m_cellstr(post) + ", " + m_row(ap.postparams) + ")";
1202 if (a == PrecedenceType::PRE_SEQ && b == PrecedenceType::POST_LOOP && pre.size() == 1 && preparams.empty()) {
1203 // The loop count is ONE number; this port stores it once per body activity.
1204 std::vector<T> count;
1205 if (!ap.postparams.empty()) count.push_back(ap.postparams[0]);
1206 return "ActivityPrecedence.Loop(" + m_quote(pre[0]) + ", " + m_cellstr(post) + ", " + m_row(count) + ")";
1207 }
1208 if (a == PrecedenceType::PRE_SEQ && b == PrecedenceType::POST_CACHE && pre.size() == 1 && preparams.empty() &&
1209 ap.postparams.empty())
1210 return "ActivityPrecedence.CacheAccess(" + m_quote(pre[0]) + ", " + m_cellstr(post) + ")";
1211 auto type_name = [](PrecedenceType t) -> std::string {
1212 switch (t) {
1213 case PrecedenceType::PRE_SEQ: return "ActivityPrecedenceType.PRE_SEQ";
1214 case PrecedenceType::PRE_AND: return "ActivityPrecedenceType.PRE_AND";
1215 case PrecedenceType::PRE_OR: return "ActivityPrecedenceType.PRE_OR";
1216 case PrecedenceType::POST_SEQ: return "ActivityPrecedenceType.POST_SEQ";
1217 case PrecedenceType::POST_AND: return "ActivityPrecedenceType.POST_AND";
1218 case PrecedenceType::POST_OR: return "ActivityPrecedenceType.POST_OR";
1219 case PrecedenceType::POST_LOOP: return "ActivityPrecedenceType.POST_LOOP";
1220 case PrecedenceType::POST_CACHE: return "ActivityPrecedenceType.POST_CACHE";
1221 }
1222 return m_scalar(double(static_cast<int>(t)));
1223 };
1224 return "ActivityPrecedence(" + m_cellstr(pre) + ", " + m_cellstr(post) + ", " + type_name(a) + ", " +
1225 type_name(b) + ", " + m_row(preparams) + ", " + m_row(ap.postparams) + ")";
1226}
1227
1228/** `emitServerExtras`: admission constraints, load dependence and server pools of a host or task. */
1229template <class T>
1230void m_server_extras(std::ostream& os, const std::string& v, const std::string& owner, const Matrix<T>* A,
1231 const std::vector<T>* b, const std::vector<lqn::detail::RawLinConRow<T>>* rows,
1232 const std::vector<T>* lld, bool has_cd, bool has_jd,
1233 const std::vector<lqn::detail::RawServerPool<T>>* pools) {
1234 if (A && A->rows() > 0 && A->cols() > 0) os << v << ".setConstraint(" << m_matrix(*A) << ", " << m_row(*b) << ");\n";
1235 if (rows)
1236 for (const lqn::detail::RawLinConRow<T>& r : *rows)
1237 os << v << ".addConstraint(" << m_cellstr(r.names) << ", " << m_row(r.coeffs) << ", " << m_scalar_t(r.cap)
1238 << ");\n";
1239 if (lld && !lld->empty()) os << v << ".setLoadDependence(" << m_row(*lld) << ");\n";
1240 if (has_cd || has_jd)
1241 throw UnsupportedError("LQN2MATLAB: " + owner + " declares a " + (has_cd ? "class" : "joint") +
1242 "-dependent rate as a compiled function, which has no MATLAB source to write");
1243 if (pools)
1244 for (const lqn::detail::RawServerPool<T>& sp : *pools)
1245 os << v << ".addServerType(ServerType(" << m_quote(sp.name) << ", " << m_scalar(sp.count) << ", "
1246 << m_cellstr(sp.compatible) << ", " << m_scalar_t(sp.rate) << "));\n";
1247}
1248
1249} // namespace code_gen_detail
1250
1251/**
1252 * Port of `LQN2MATLAB(model, modelName, fid)`, the generator form: a MATLAB
1253 * script that rebuilds the layered network by the constructors a user writes,
1254 * with hosts, tasks, entries and activities in model order so the regenerated
1255 * model has the same LayeredNetworkStruct indexing, and numbers at the shortest
1256 * spelling that reads back bit for bit.
1257 *
1258 * It reads the intermediate `LqnModel`, which is the C++ counterpart of the
1259 * MATLAB objects. What that model does not carry is written at its MATLAB
1260 * default: a task priority, an entry type other than PH1PH2, an activity call
1261 * order, phase, a processor class other than Processor, and the numeric-versus-
1262 * object choice MATLAB makes for an Immediate or Exp time (this port writes the
1263 * object form, and cannot tell an explicit `Immediate()` think time from the
1264 * default one). A class- or joint-dependent rate is a compiled function here
1265 * and is refused.
1266 */
1267template <class T>
1268void lqn2matlab(const lqn::LqnModel<T>& m, const std::string& model_name, std::ostream& os) {
1269 using namespace code_gen_detail;
1270 std::map<std::string, std::size_t> entry_idx, act_idx;
1271 for (std::size_t e = 0; e < m.entries.size(); ++e) entry_idx.emplace(m.entries[e].name, e + 1);
1272 for (std::size_t a = 0; a < m.acts.size(); ++a) act_idx.emplace(m.acts[a].name, a + 1);
1273 auto entry_ref = [&](const std::string& n) {
1274 const auto it = entry_idx.find(n);
1275 return it == entry_idx.end() ? m_quote(n) : "E{" + std::to_string(it->second) + "}";
1276 };
1277 auto sched = [](lang::SchedStrategy s) { return "SchedStrategy." + sched_property(s); };
1278
1279 os << "% LayeredNetwork generated by LQN2MATLAB\n";
1280 os << "model = LayeredNetwork(" << m_quote(model_name) << ");\n";
1281 os << "P = {}; T = {}; E = {}; A = {};\n";
1282
1283 os << "\n%% Block 1: processors\n";
1284 for (std::size_t p = 0; p < m.procs.size(); ++p) {
1285 const lqn::detail::RawProc& h = m.procs[p];
1286 const std::string hv = "P{" + std::to_string(p + 1) + "}";
1287 // A quantum of 0 is this port's "not declared" (the .lqnx reader's default); MATLAB's default is 0.001.
1288 const double quantum = h.quantum == 0.0 ? 0.001 : h.quantum;
1289 const char* ctor = h.is_host_class ? " = Host(model, " : " = Processor(model, ";
1290 if (quantum != 0.001 || h.speed_factor != 1.0)
1291 os << hv << ctor << m_quote(h.name) << ", " << m_scalar(h.mult) << ", " << sched(h.sched)
1292 << ", " << m_scalar(quantum) << ", " << m_scalar(h.speed_factor) << ");\n";
1293 else
1294 os << hv << ctor << m_quote(h.name) << ", " << m_scalar(h.mult) << ", " << sched(h.sched)
1295 << ");\n";
1296 if (h.repl != 1.0) os << hv << ".setReplication(" << m_scalar(h.repl) << ");\n";
1297 auto lc = m.proc_lincon.find(p);
1298 auto rows = m.proc_linconrows.find(p);
1299 auto lld = m.proc_lldscaling.find(p);
1300 auto pools = m.proc_pools.find(p);
1301 auto cd = m.proc_cdscaling.find(p);
1302 auto jd = m.proc_jdscaling.find(p);
1303 m_server_extras<T>(os, hv, h.name, lc == m.proc_lincon.end() ? nullptr : &lc->second.first,
1304 lc == m.proc_lincon.end() ? nullptr : &lc->second.second,
1305 rows == m.proc_linconrows.end() ? nullptr : &rows->second,
1306 lld == m.proc_lldscaling.end() ? nullptr : &lld->second,
1307 cd != m.proc_cdscaling.end() && static_cast<bool>(cd->second),
1308 jd != m.proc_jdscaling.end() && static_cast<bool>(jd->second),
1309 pools == m.proc_pools.end() ? nullptr : &pools->second);
1310 }
1311
1312 os << "\n%% Block 2: tasks\n";
1313 for (std::size_t t = 0; t < m.tasks.size(); ++t) {
1314 const lqn::detail::RawTask<T>& tk = m.tasks[t];
1315 const std::string tv = "T{" + std::to_string(t + 1) + "}";
1316 const std::string on = ".on(P{" + std::to_string(tk.proc_slot + 1) + "});\n";
1317 const bool setup = !m_default_time(tk.setuptime) || !m_default_time(tk.delayofftime);
1318 if (tk.nitems > 0) {
1319 std::vector<double> cap(tk.itemcap.begin(), tk.itemcap.end());
1320 os << tv << " = CacheTask(model, " << m_quote(tk.name) << ", " << m_scalar(double(tk.nitems)) << ", "
1321 << m_row(cap) << ", " << m_repl(tk.replacestrat) << ", " << m_scalar(tk.mult) << ", " << sched(tk.sched)
1322 << ")" << on;
1323 if (tk.retrieval) os << tv << ".setRetrieval(true);\n";
1324 } else {
1325 os << tv << " = " << (setup ? "SetupTask" : "Task") << "(model, " << m_quote(tk.name) << ", "
1326 << m_scalar(tk.mult) << ", " << sched(tk.sched) << ")" << on;
1327 }
1328 if (!m_default_time(tk.thinktime)) os << tv << ".setThinkTime(" << m_dist(tk.thinktime) << ");\n";
1329 if (!m_default_time(tk.setuptime)) os << tv << ".setSetupTime(" << m_dist(tk.setuptime) << ");\n";
1330 if (!m_default_time(tk.delayofftime)) os << tv << ".setDelayOffTime(" << m_dist(tk.delayofftime) << ");\n";
1331 if (tk.repl != 1.0) os << tv << ".setReplication(" << m_scalar(tk.repl) << ");\n";
1332 if (tk.priority != 0) os << tv << ".setPriority(" << tk.priority << ");\n";
1333 for (const auto& fi : tk.fanin) os << tv << ".setFanIn(" << m_quote(fi.first) << ", " << m_scalar(fi.second) << ");\n";
1334 for (const auto& fo : tk.fanout)
1335 os << tv << ".setFanOut(" << m_quote(fo.first) << ", " << m_scalar(fo.second) << ");\n";
1336 m_server_extras<T>(os, tv, tk.name, &tk.lincon_A, &tk.lincon_b, &tk.linconrows, &tk.lldscaling,
1337 static_cast<bool>(tk.cdscaling), static_cast<bool>(tk.jdscaling), &tk.pools);
1338 }
1339
1340 os << "\n%% Block 3: entries\n";
1341 for (std::size_t e = 0; e < m.entries.size(); ++e) {
1342 const lqn::detail::RawEntry<T>& en = m.entries[e];
1343 const std::string ev = "E{" + std::to_string(e + 1) + "}";
1344 const std::string on = ".on(T{" + std::to_string(en.task_slot + 1) + "});\n";
1345 if (en.cardinality > 0) {
1346 std::vector<T> x;
1347 for (std::size_t k = 0; k < en.popularity.size(); ++k) x.push_back(num_traits<T>::from_int(static_cast<long>(k + 1)));
1348 os << ev << " = ItemEntry(model, " << m_quote(en.name) << ", " << m_scalar(double(en.cardinality))
1349 << ", DiscreteSampler(" << m_row(en.popularity) << ", " << m_row(x) << "))" << on;
1350 } else {
1351 os << ev << " = Entry(model, " << m_quote(en.name) << ")" << on;
1352 }
1353 if (en.type != "PH1PH2") os << ev << ".setType(" << m_quote(en.type) << ");\n";
1354 if (en.has_arrival) os << ev << ".setArrival(" << m_dist(en.arrival) << ");\n";
1355 }
1356 for (std::size_t e = 0; e < m.entries.size(); ++e)
1357 for (std::size_t f = 0; f < m.entries[e].fwd_dest.size(); ++f)
1358 os << "E{" << e + 1 << "}.forward(" << entry_ref(m.entries[e].fwd_dest[f]) << ", "
1359 << m_scalar_t(m.entries[e].fwd_prob[f]) << ");\n";
1360
1361 os << "\n%% Block 4: activities\n";
1362 for (std::size_t a = 0; a < m.acts.size(); ++a) {
1363 const lqn::detail::RawActivity<T>& ac = m.acts[a];
1364 const std::string av = "A{" + std::to_string(a + 1) + "}";
1365 os << av << " = Activity(model, " << m_quote(ac.name) << ", " << m_dist(ac.hostdem) << ").on(T{"
1366 << ac.task_slot + 1 << "})";
1367 if (!ac.bound_to_entry.empty()) os << ".boundTo(" << entry_ref(ac.bound_to_entry) << ")";
1368 os << ";\n";
1369 if (ac.call_order != "STOCHASTIC") os << av << ".setCallOrder(" << m_quote(ac.call_order) << ");\n";
1370 if (!m_default_time(ac.thinktime)) os << av << ".setThinkTime(" << m_dist(ac.thinktime) << ");\n";
1371 if (ac.phase != 1) os << av << ".setPhase(" << ac.phase << ");\n";
1372 for (const lqn::detail::RawCall<T>& c : ac.sync_calls)
1373 os << av << ".synchCall(" << entry_ref(c.dest) << ", " << m_scalar_t(c.mean) << ");\n";
1374 for (const auto& g : ac.call_groups) {
1375 std::string rs;
1376 if (g.first == lang::RoutingStrategy::RROBIN) rs = "RoutingStrategy.RROBIN";
1377 else if (g.first == lang::RoutingStrategy::JSQ) rs = "RoutingStrategy.JSQ";
1378 else
1379 throw UnsupportedError(std::string("LQN2MATLAB: call groups carry RROBIN or JSQ; routing strategy ") +
1380 lang::routing_to_text(g.first) + " cannot be written out");
1381 os << av << ".recordCallGroup(" << rs << ", " << m_cellstr(g.second) << ");\n";
1382 }
1383 for (const lqn::detail::RawCall<T>& c : ac.async_calls)
1384 os << av << ".asynchCall(" << entry_ref(c.dest) << ", " << m_scalar_t(c.mean) << ");\n";
1385 }
1386
1387 os << "\n%% Block 5: replies\n";
1388 for (std::size_t e = 0; e < m.entries.size(); ++e)
1389 for (const std::string& rn : m.entries[e].reply_activities) {
1390 const auto it = act_idx.find(rn);
1391 const bool is_ref = it != act_idx.end() &&
1392 m.tasks[m.acts[it->second - 1].task_slot].sched == lang::SchedStrategy::REF;
1393 if (it == act_idx.end() || is_ref)
1394 os << "E{" << e + 1 << "}.replyActivity{end+1} = " << m_quote(rn) << ";\n";
1395 else
1396 os << "A{" << it->second << "}.repliesTo(E{" << e + 1 << "});\n";
1397 }
1398
1399 os << "\n%% Block 6: precedences\n";
1400 for (std::size_t t = 0; t < m.tasks.size(); ++t)
1401 for (const lqn::detail::RawPrecedence<T>& ap : m.tasks[t].precedences)
1402 os << "T{" << t + 1 << "}.addPrecedence(" << m_prec(ap) << ");\n";
1403}
1404
1405/** LQN2MATLAB under the model's own name (`myLayeredModel` when it has none). */
1406template <class T>
1407void lqn2matlab(const lqn::LqnModel<T>& model, std::ostream& os) {
1408 lqn2matlab(model, model.name.empty() ? std::string("myLayeredModel") : model.name, os);
1409}
1410
1411/** LQN2MATLAB on a model under construction. */
1412template <class T>
1413void lqn2matlab(const lqn::LqnBuilder<T>& b, const std::string& model_name, std::ostream& os) {
1414 lqn2matlab(b.model(), model_name, os);
1415}
1416
1417/** LQN2MATLAB into a file. */
1418template <class T>
1419void lqn2matlab(const lqn::LqnModel<T>& model, const std::string& model_name, const std::string& path) {
1420 std::ofstream f = code_gen_detail::open_out(path);
1421 lqn2matlab(model, model_name, f);
1422}
1423
1424/** LQN2MATLAB returned as a string. */
1425template <class T>
1426std::string lqn2matlab_string(const lqn::LqnModel<T>& model, const std::string& model_name) {
1427 std::ostringstream os;
1428 lqn2matlab(model, model_name, os);
1429 return os.str();
1430}
1431
1432// ---------------------------------------------------------------------------
1433// LINE2MATLAB / LINE2JAVA
1434// ---------------------------------------------------------------------------
1435
1436/** Port of `LINE2MATLAB(model)` for a Network: QN2MATLAB under the model's own name. */
1437template <class T>
1438void line2matlab(qn::Network<T>& model, std::ostream& os) {
1439 const qn::NetworkStruct<T>& sn = model.get_struct();
1440 qn2matlab(sn, sn.name, os);
1441}
1442
1443/** Port of `LINE2MATLAB(model, filename)` for a Network. */
1444template <class T>
1445void line2matlab(qn::Network<T>& model, const std::string& path) {
1446 std::ofstream f = code_gen_detail::open_out(path);
1447 line2matlab(model, f);
1448}
1449
1450/** Port of `LINE2JAVA(model)` for a Network: QN2JAVA under the model's own name. */
1451template <class T>
1452void line2java(qn::Network<T>& model, std::ostream& os) {
1453 const qn::NetworkStruct<T>& sn = model.get_struct();
1454 qn2java(sn, sn.name, os, true);
1455}
1456
1457/** Port of `LINE2JAVA(model, filename)` for a Network. */
1458template <class T>
1459void line2java(qn::Network<T>& model, const std::string& path) {
1460 std::ofstream f = code_gen_detail::open_out(path);
1461 line2java(model, f);
1462}
1463
1464/**
1465 * Port of `LINE2JAVA(model)` for a LayeredNetwork. The intermediate model does
1466 * not carry the model name, so it is passed explicitly.
1467 */
1468template <class T>
1469void line2java(const lqn::LqnModel<T>& model, const std::string& model_name, std::ostream& os) {
1470 lqn2java(model, model_name, os);
1471}
1472
1473/** Port of `LINE2JAVA(model, filename)` for a LayeredNetwork. */
1474template <class T>
1475void line2java(const lqn::LqnModel<T>& model, const std::string& model_name, const std::string& path) {
1476 lqn2java(model, model_name, path);
1477}
1478
1479/** Port of `LINE2MATLAB(model)` for a LayeredNetwork: LQN2MATLAB under the model's own name. */
1480template <class T>
1481void line2matlab(const lqn::LqnModel<T>& model, std::ostream& os) {
1482 lqn2matlab(model, os);
1483}
1484
1485/** Port of `LINE2MATLAB(model, filename)` for a LayeredNetwork. */
1486template <class T>
1487void line2matlab(const lqn::LqnModel<T>& model, const std::string& path) {
1488 std::ofstream f = code_gen_detail::open_out(path);
1489 lqn2matlab(model, f);
1490}
1491
1492/** Port of `LINE2JAVA(model)` for a LayeredNetwork under its own name (`myLayeredModel` when it has none). */
1493template <class T>
1494void line2java(const lqn::LqnModel<T>& model, std::ostream& os) {
1495 lqn2java(model, model.name.empty() ? std::string("myLayeredModel") : model.name, os);
1496}
1497
1498namespace code_gen_detail {
1499
1500/** The `name` of the model in a model.json envelope, `fallback` when it has none. */
1501inline std::string json_model_name(const std::string& path, const std::string& fallback) {
1502 std::ifstream in(path.c_str());
1503 if (!in) throw InputError("code_gen: cannot open " + path);
1504 detail::json root;
1505 try {
1506 in >> root;
1507 } catch (const detail::json::parse_error& e) {
1508 throw InputError("code_gen: malformed JSON in " + path + ": " + e.what());
1509 }
1510 const detail::json& model = root.contains("model") ? root.at("model") : root;
1511 return model.contains("name") && model.at("name").is_string() ? model.at("name").get<std::string>()
1512 : fallback;
1513}
1514
1515} // namespace code_gen_detail
1516
1517/**
1518 * LINE2JAVA on a model.json file, dispatching on the model type as MATLAB
1519 * dispatches on the class of the object: a LayeredNetwork goes to LQN2JAVA and
1520 * any other network to QN2JAVA, each under the name the document declares.
1521 */
1522template <class T = double>
1523void line2java_json(const std::string& json_path, std::ostream& os) {
1524 if (is_layered_json(json_path)) {
1525 lqn2java(read_lqn_json_model<T>(json_path), code_gen_detail::json_model_name(json_path, "myLayeredModel"),
1526 os);
1527 } else {
1528 qn::Network<T> model = read_network_json<T>(json_path);
1529 line2java(model, os);
1530 }
1531}
1532
1533/** LINE2MATLAB on a model.json file: a LayeredNetwork goes to LQN2MATLAB, any other network to QN2MATLAB. */
1534template <class T = double>
1535void line2matlab_json(const std::string& json_path, std::ostream& os) {
1536 if (is_layered_json(json_path)) {
1537 lqn2matlab(read_lqn_json_model<T>(json_path), os);
1538 return;
1539 }
1540 qn::Network<T> model = read_network_json<T>(json_path);
1541 line2matlab(model, os);
1542}
1543
1544} // namespace io
1545} // namespace line
1546
1547#endif // LINE_IO_CODE_GEN_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:51
const LqnModel< T > & model() const
A network plus its refreshed NetworkStruct.
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Build a layered queueing network in code, as the MATLAB constructors do.
model.json with type: "LayeredNetwork" -> LqnStruct, via LqnBuilder.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
LqnModel -> .lqnx, a port of matlab/src/lang/layered/@LayeredNetwork/writeXML.m.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
std::string j_round(double v)
Definition code_gen.h:609
std::string json_model_name(const std::string &path, const std::string &fallback)
The name of the model in a model.json envelope, fallback when it has none.
Definition code_gen.h:1501
std::string j_mat_row(const std::vector< T > &v)
jmat of a row vector: new Matrix(new double[][]{{a, b}}).
Definition code_gen.h:591
std::size_t fork_of(const qn::NetworkStruct< T > &sn, std::size_t j)
find(sn.fj(:,j)): the 1-based Fork node the 1-based Join node j closes.
Definition code_gen.h:168
std::string m_scalar_t(const T &v)
Definition code_gen.h:1047
bool m_default_time(const lang::Distrib< T > &d)
True for a think / setup / delay-off time left at its constructor default.
Definition code_gen.h:1174
std::ofstream open_out(const std::string &path)
Opens path for writing or throws, naming it.
Definition code_gen.h:264
std::string m_row(const std::vector< T > &v)
num2code of a row vector: [a, b], a scalar when it has one element, [] when empty.
Definition code_gen.h:1053
std::string fmt_f(double x)
Definition code_gen.h:81
std::string m_dist(const lang::Distrib< T > &d)
dist2code: the constructor call that rebuilds d from its parameters, which this port keeps in MATLAB ...
Definition code_gen.h:1111
std::string fmt_d(double x)
MATLAB d of a double: the integer when it is one, else MATLAB's e override.
Definition code_gen.h:85
std::string m_prec(const lqn::detail::RawPrecedence< T > &ap)
The ActivityPrecedence expression of one declared precedence (precCode).
Definition code_gen.h:1180
void m_server_extras(std::ostream &os, const std::string &v, const std::string &owner, const Matrix< T > *A, const std::vector< T > *b, const std::vector< lqn::detail::RawLinConRow< T > > *rows, const std::vector< T > *lld, bool has_cd, bool has_jd, const std::vector< lqn::detail::RawServerPool< T > > *pools)
emitServerExtras: admission constraints, load dependence and server pools of a host or task.
Definition code_gen.h:1230
std::size_t empty_class_ref(const qn::NetworkStruct< T > &sn, std::size_t k)
zeroPopRefNode(sn, k) of QN2MATLAB / QN2JAVA: the NODE index (1-based) of the reference station of ze...
Definition code_gen.h:183
std::vector< Route > routes(const qn::NetworkStruct< T > &sn)
Definition code_gen.h:246
std::string m_scalar(double v)
scalar2code: a double at the shortest of 15..17 significant digits that reads back as itself.
Definition code_gen.h:1030
std::string m_matrix(const Matrix< T > &M)
num2code of a matrix: [a, b; c, d].
Definition code_gen.h:1063
std::size_t and_join_quorum(const lqn::LqnModel< T > &m, std::size_t tslot, const std::string &post_name)
The quorum of the AND-join into post_name on task slot tslot, 0 when it declares none.
Definition code_gen.h:713
std::string j_num(double v)
jnum: a Java double literal at the shortest spelling that reads back as v.
Definition code_gen.h:561
std::string j_mat(const Matrix< T > &m)
jmat of a matrix, row by row.
Definition code_gen.h:599
std::string sched_property(lang::SchedStrategy s)
SchedStrategy.toProperty(SchedStrategy.toText(s)): the enum constant name.
Definition code_gen.h:99
std::string java_dist(const lang::Distrib< T > &d)
javaDist: the Java constructor call rebuilding d with the same parameters.
Definition code_gen.h:622
std::vector< std::vector< bool > > reply_graph(const lqn::LqnModel< T > &m, const lqn::LqnStruct< T > &sn)
sn.replygraph: (nacts+1) x (nentries+1), 1-based, true where the activity replies to the entry.
Definition code_gen.h:694
ProcSpec proc_spec(const qn::NetworkStruct< T > &sn, std::size_t i, std::size_t k)
The branch of QN2MATLAB's process block for station i, class k (0-based).
Definition code_gen.h:130
std::string mfmt(const char *spec, double x)
f, e or g as MATLAB's fprintf prints it: C's text, with Inf / NaN spelt MATLAB's way.
Definition code_gen.h:73
std::string upper_nospace(const std::string &s)
Definition code_gen.h:725
std::string m_quote(const std::string &s)
q(str): a MATLAB single-quoted literal.
Definition code_gen.h:1075
std::string m_cellstr(const std::vector< std::string > &c)
cellstr2code: {'a', 'b'}.
Definition code_gen.h:1085
std::string fmt_g(double x)
Definition code_gen.h:82
std::string j_arr(const std::vector< T > &v)
jarr: a new double[]{...} literal.
Definition code_gen.h:583
std::string sched_feature(lang::SchedStrategy s)
strrep(SchedStrategy.toFeature(s),'_','.
Definition code_gen.h:110
std::string m_repl(lang::ReplacementStrategy r)
Definition code_gen.h:1091
void check_node_types(const qn::NetworkStruct< T > &sn, const char *who)
Refuses a node type neither MATLAB generator has a statement for.
Definition code_gen.h:218
std::string j_num_t(const T &v)
Definition code_gen.h:577
bool is_layered_json(const std::string &path)
True when a file is a LayeredNetwork model.json rather than an .lqnx.
void line2matlab(qn::Network< T > &model, std::ostream &os)
Port of LINE2MATLAB(model) for a Network: QN2MATLAB under the model's own name.
Definition code_gen.h:1438
std::string lqn2java_string(const lqn::LqnModel< T > &model, const std::string &model_name="myLayeredModel")
LQN2JAVA returned as a string.
Definition code_gen.h:1017
lqn::LqnModel< T > read_lqn_json_model(const std::string &path)
Parse a LayeredNetwork model.json file into its intermediate LqnModel<T>.
qn::Network< T > read_network_json(const std::string &path)
Parse a model.json file into a qn::Network<T>.
void line2java_json(const std::string &json_path, std::ostream &os)
LINE2JAVA on a model.json file, dispatching on the model type as MATLAB dispatches on the class of th...
Definition code_gen.h:1523
void lqn2java(const lqn::LqnModel< T > &model, const std::string &model_name, std::ostream &os)
Port of LQN2JAVA(model, modelName, fid): a JLINE program that rebuilds the layered network and solves...
Definition code_gen.h:743
void qn2matlab(const qn::NetworkStruct< T > &sn, const std::string &model_name, std::ostream &os)
Port of QN2MATLAB(model, modelName, fid): a MATLAB script that rebuilds the network from its refreshe...
Definition code_gen.h:281
void line2java(qn::Network< T > &model, std::ostream &os)
Port of LINE2JAVA(model) for a Network: QN2JAVA under the model's own name.
Definition code_gen.h:1452
std::string qn2matlab_string(qn::Network< T > &model, const std::string &model_name="myModel")
QN2MATLAB returned as a string.
Definition code_gen.h:394
std::string lqn2matlab_string(const lqn::LqnModel< T > &model, const std::string &model_name)
LQN2MATLAB returned as a string.
Definition code_gen.h:1426
void line2matlab_json(const std::string &json_path, std::ostream &os)
LINE2MATLAB on a model.json file: a LayeredNetwork goes to LQN2MATLAB, any other network to QN2MATLAB...
Definition code_gen.h:1535
void lqn2matlab(const lqn::LqnModel< T > &m, const std::string &model_name, std::ostream &os)
Port of LQN2MATLAB(model, modelName, fid), the generator form: a MATLAB script that rebuilds the laye...
Definition code_gen.h:1268
void qn2java(const qn::NetworkStruct< T > &sn, const std::string &model_name, std::ostream &os, bool headers=true)
Port of QN2JAVA(model, modelName, fid, headers): the body of a JLINE method public static Network ex(...
Definition code_gen.h:410
std::string qn2java_string(qn::Network< T > &model, const std::string &model_name="myModel", bool headers=true)
QN2JAVA returned as a string.
Definition code_gen.h:547
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
PrecedenceType
Activity precedence kinds, with the values of MATLAB ActivityPrecedenceType.
Definition lang_types.h:472
const char * node_type_to_text(NodeType t)
Name of a node kind, for diagnostics.
Definition lang_types.h:343
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:485
const char * sched_to_text(SchedStrategy s)
Definition lang_types.h:230
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
Definition lang_types.h:596
const char * routing_to_text(RoutingStrategy r)
Definition lang_types.h:404
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:326
ReplacementStrategy
Cache replacement policies, with the values of MATLAB ReplacementStrategy.
Definition lang_types.h:380
@ HLRU
h-LRU / LRU(m): h lists, promote i -> i+1 on a hit
Definition lang_types.h:385
@ CLIMB
move up one position on a hit (transposition rule)
Definition lang_types.h:386
@ QLRU
q-LRU: LRU with probabilistic admission on a miss
Definition lang_types.h:387
@ LRU
least recently used
Definition lang_types.h:384
@ FIFO
first in, first out
Definition lang_types.h:382
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Definition lqn_reader.h:444
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
T map_scv(const Map< T > &m)
Squared coefficient of variation.
Definition map_moment.h:140
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The Network constructor API: Queue, Delay, Source, Sink, Router, ClassSwitch, Cache,...
Reader for the LINE model.json interchange (a Network model) into a qn::Network<T> built through the ...
A queueing network and its refreshed NetworkStruct.
One (station, class) process, reduced to what QN2MATLAB / QN2JAVA decide on.
Definition code_gen.h:113
enum line::io::code_gen_detail::ProcSpec::Kind kind
bool arrival
the station is EXT scheduled: setArrival, not setService
Definition code_gen.h:115
The (k, c, i, m, p) of every positive sn.rtnodes entry, in QN2MATLAB's loop order,...
Definition code_gen.h:240
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
Definition lang_types.h:853
std::vector< T > params
Constructor arguments, in MATLAB getParam order.
Definition lang_types.h:826
std::vector< T > trace
Replayer / Trace samples; empty for every other type.
Definition lang_types.h:828
bool cox_scalar_form
Built by cox2(mu1, mu2, phi1), MATLAB's 3-argument Coxian(mu1, mu2, phi1); read only by the code gene...
Definition lang_types.h:842
std::string trace_file
The trace FILE a Replayer was read from, when there was one.
Definition lang_types.h:840
static constexpr double CoarseTol
Definition lang_types.h:761
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:409
std::vector< detail::RawTask< T > > tasks
Definition lqn_reader.h:413
std::vector< detail::RawActivity< T > > acts
Definition lqn_reader.h:415
std::map< std::size_t, std::vector< detail::RawServerPool< T > > > proc_pools
Definition lqn_reader.h:439
std::vector< detail::RawProc > procs
Definition lqn_reader.h:412
std::map< std::size_t, CdScaling< T > > proc_jdscaling
Definition lqn_reader.h:437
std::map< std::size_t, std::pair< Matrix< T >, std::vector< T > > > proc_lincon
Definition lqn_reader.h:425
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:424
std::map< std::size_t, CdScaling< T > > proc_cdscaling
Definition lqn_reader.h:435
std::string name
LayeredNetwork.getName(); empty when unnamed.
Definition lqn_reader.h:411
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:434
std::vector< detail::RawEntry< T > > entries
Definition lqn_reader.h:414
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D0
Definition map_moment.h:54
One job class of the network.
std::size_t refstat
1-based reference station
double population
infinite for an open class
A node of the network.
One station of the network.
SchedStrategy sched
double nservers
may be infinite (a Delay, or an inf-scheduled task)