69 const T one = nt::from_int(1);
70 const std::size_t I =
sn.nodes.size();
71 const std::size_t K =
sn.nclasses;
72 const std::size_t C =
sn.nchains;
74 if (I == 0 || K == 0 || C == 0)
75 throw InputError(
"QN2LQN: the queueing network has no closed chain to convert");
76 if (
sn.nodevisits.size() != C ||
sn.inchain.size() != C)
77 throw InputError(
"QN2LQN: the NetworkStruct visits or chains are not refreshed");
78 for (std::size_t r = 0; r < K; ++r)
79 if (!std::isfinite(
sn.classes[r].population))
81 "QN2LQN: open classes are not supported; SolverLQNS sends open Networks to "
84 std::vector<std::size_t> chain_of(K, C);
85 for (std::size_t c = 0; c < C; ++c)
86 for (std::size_t x = 0; x <
sn.inchain[c].size(); ++x) {
87 const std::size_t r1 =
sn.inchain[c][x];
88 if (r1 == 0 || r1 > K)
89 throw InputError(
"QN2LQN: chain " + std::to_string(c + 1) +
90 " contains an out-of-range class index");
91 if (chain_of[r1 - 1] != C)
92 throw InputError(
"QN2LQN: class " + std::to_string(r1) +
93 " belongs to more than one chain");
96 for (std::size_t r = 0; r < K; ++r)
98 throw InputError(
"QN2LQN: class " + std::to_string(r + 1) +
99 " belongs to no chain");
102 const std::string pseudo_host =
sn.name.empty() ? std::string(
"QN") :
sn.name;
105 std::vector<std::string> ref_task(C), ref_entry(C);
106 for (std::size_t c = 0; c < C; ++c) {
108 for (std::size_t x = 0; x <
sn.inchain[c].size(); ++x)
109 jobs +=
sn.classes[
sn.inchain[c][x] - 1].population;
110 ref_task[c] =
"RefTask_" + std::to_string(c + 1);
111 ref_entry[c] =
"Chain_" + std::to_string(c + 1);
113 b.
entry(ref_entry[c], ref_task[c]);
119 std::vector<std::string> entries(I * K), service_acts(I * K);
120 const auto ir = [K](std::size_t i, std::size_t r) {
return i * K + r; };
121 const auto cir = [I, K](std::size_t c, std::size_t i, std::size_t r) {
122 return (c * I + i) * K + r;
125 for (std::size_t i = 0; i < I; ++i) {
127 if (nd.
nodetype != qn::NodeType::Queue && nd.
nodetype != qn::NodeType::Delay)
130 throw InputError(
"QN2LQN: service node '" + nd.
name +
"' has no station row");
131 const std::size_t ist = nd.
station - 1;
133 const std::string task =
"T_" + nd.
name;
135 for (std::size_t r = 0; r < K; ++r) {
136 const std::size_t c = chain_of[r];
137 if (
sn.nodevisits[c].rows() != I ||
sn.nodevisits[c].cols() != K)
138 throw InputError(
"QN2LQN: nodevisits has the wrong dimensions for chain " +
139 std::to_string(c + 1));
140 if (!(nt::to_double(
sn.nodevisits[c](i, r)) > 0.0))
continue;
141 if (ist >=
sn.service.size() || r >=
sn.service[ist].size() ||
142 sn.service[ist][r].disabled)
143 throw InputError(
"QN2LQN: visited station '" + nd.
name +
"', class '" +
144 sn.classes[r].name +
"' has no service distribution");
145 entries[ir(i, r)] = detail::qn2lqn_indexed(
"E", i, r);
146 service_acts[ir(i, r)] = detail::qn2lqn_indexed(
"Q", i, r);
147 b.
entry(entries[ir(i, r)], task);
148 b.
activity(service_acts[ir(i, r)],
sn.service[ist][r], task);
149 b.
bound_to(service_acts[ir(i, r)], entries[ir(i, r)]);
150 b.
replies_to(service_acts[ir(i, r)], entries[ir(i, r)]);
154 std::vector<std::string> pseudo(C * I * K);
155 std::vector<bool> bound(C,
false);
156 std::vector<std::size_t> bound_i(C, 0), bound_r(C, 0);
157 const auto incoming = [&](std::size_t i, std::size_t r) {
158 const std::size_t col = i * K + r;
159 if (
sn.rtnodes.cols() <= col)
return false;
160 for (std::size_t row = 0; row <
sn.rtnodes.rows(); ++row)
161 if (nt::to_double(
sn.rtnodes(row, col)) > 0.0)
return true;
165 for (std::size_t i = 0; i < I; ++i) {
167 if (type == qn::NodeType::ClassSwitch || type == qn::NodeType::Router ||
168 type == qn::NodeType::Logger || type == qn::NodeType::Fork ||
169 type == qn::NodeType::Join) {
170 const char* prefix = (type == qn::NodeType::Fork || type == qn::NodeType::Join)
173 for (std::size_t r = 0; r < K; ++r) {
174 if (!incoming(i, r))
continue;
175 const std::size_t c = chain_of[r];
176 pseudo[cir(c, i, r)] = detail::qn2lqn_pseudo(prefix, c, i, r);
179 }
else if (type == qn::NodeType::Queue || type == qn::NodeType::Delay) {
180 for (std::size_t r = 0; r < K; ++r) {
181 const std::size_t c = chain_of[r];
182 if (service_acts[ir(i, r)].empty())
continue;
183 pseudo[cir(c, i, r)] = detail::qn2lqn_indexed(
"A", i, r);
185 const std::size_t first_r =
sn.inchain[c].front() - 1;
186 const std::size_t refstat =
sn.classes[first_r].refstat;
187 if (refstat == 0 || refstat >
sn.station_to_node.size())
188 throw InputError(
"QN2LQN: chain " + std::to_string(c + 1) +
189 " has an invalid reference station");
190 const std::size_t refnode =
sn.station_to_node[refstat - 1] - 1;
191 if (i == refnode && r == first_r) {
192 b.
bound_to(pseudo[cir(c, i, r)], ref_entry[c]);
197 b.
sync_call(pseudo[cir(c, i, r)], entries[ir(i, r)], one);
199 }
else if (type != qn::NodeType::Source && type != qn::NodeType::Sink) {
202 ", which the MATLAB conversion does not map");
205 for (std::size_t c = 0; c < C; ++c)
207 throw InputError(
"QN2LQN: chain " + std::to_string(c + 1) +
208 " has no visited reference station to bind to its entry");
215 std::vector<std::string> posts;
216 std::vector<T> probs;
219 for (std::size_t c = 0; c < C; ++c) {
220 std::vector<ForkRec> forks;
221 for (std::size_t i = 0; i < I; ++i) {
222 if (!detail::qn2lqn_routing_node(
sn.nodes[i].nodetype))
continue;
223 for (std::size_t rx = 0; rx <
sn.inchain[c].size(); ++rx) {
224 const std::size_t r =
sn.inchain[c][rx] - 1;
225 const std::string& pre = pseudo[cir(c, i, r)];
226 if (pre.empty() || !incoming(i, r))
continue;
227 std::vector<std::string> posts;
228 std::vector<T> probs;
229 for (std::size_t j = 0; j < I; ++j) {
230 if (!detail::qn2lqn_routing_node(
sn.nodes[j].nodetype) ||
231 sn.nodes[j].nodetype == qn::NodeType::Join)
233 for (std::size_t sx = 0; sx <
sn.inchain[c].size(); ++sx) {
234 const std::size_t s =
sn.inchain[c][sx] - 1;
235 const T pr =
sn.rtnodes(i * K + r, j * K + s);
236 if (!(nt::to_double(pr) > 0.0))
continue;
237 if (j == bound_i[c] && s == bound_r[c]) {
238 const std::string end = detail::qn2lqn_pseudo(
"End", c, i, r);
240 posts.push_back(end);
242 const std::string& post = pseudo[cir(c, j, s)];
244 throw InputError(
"QN2LQN: route from node '" +
sn.nodes[i].name +
245 "' reaches a node/class with no pseudo-activity");
246 posts.push_back(post);
251 if (posts.empty())
continue;
252 forks.push_back(ForkRec{pre, posts, probs,
sn.nodes[i].nodetype == qn::NodeType::Fork});
255 std::map<std::string, std::size_t> nin;
256 for (
const ForkRec& f : forks)
257 for (
const std::string& t : f.posts) ++nin[t];
258 std::map<std::string, std::vector<std::string>> stubs;
259 for (std::size_t k = 0; k < forks.size(); ++k) {
260 ForkRec& f = forks[k];
261 for (std::string& t : f.posts) {
262 if (nin[t] < 2)
continue;
263 const std::string stub =
"In_" + t +
"_" + std::to_string(k + 1);
265 stubs[t].push_back(stub);
271 b.
or_fork(f.pre, f.posts, f.probs);
273 for (
const auto& kv : stubs) b.
or_join(kv.second, kv.first);
278 for (std::size_t c = 0; c < C; ++c)
279 for (std::size_t j = 0; j < I; ++j) {
280 if (
sn.nodes[j].nodetype != qn::NodeType::Join)
continue;
281 for (std::size_t sx = 0; sx <
sn.inchain[c].size(); ++sx) {
282 const std::size_t s =
sn.inchain[c][sx] - 1;
283 const std::string& post = pseudo[cir(c, j, s)];
284 if (post.empty())
continue;
285 std::vector<std::string> pres;
286 for (std::size_t i = 0; i < I; ++i) {
287 if (!detail::qn2lqn_routing_node(
sn.nodes[i].nodetype))
continue;
288 for (std::size_t rx = 0; rx <
sn.inchain[c].size(); ++rx) {
289 const std::size_t r =
sn.inchain[c][rx] - 1;
290 if (nt::to_double(
sn.rtnodes(i * K + r, j * K + s)) <= 0.0)
continue;
291 const std::string& pre = pseudo[cir(c, i, r)];
292 if (!pre.empty()) pres.push_back(pre);
297 else if (pres.size() == 1)
298 b.
serial(pres.front(), post);