LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mapqn_qr_common.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_MAPQN_MAPQN_QR_COMMON_H
6#define LINE_API_MAPQN_MAPQN_QR_COMMON_H
7
8/**
9 * @file
10 * @ingroup api_mapqn
11 * The constraint families of the quadratic-reduction (QR) polytope.
12 *
13 * mapqn_bnd_qr_ld and mapqn_bnd_qr_delay are the same polytope up to one
14 * family: the delay model adds XZ, the think-time balance between the delay
15 * station M and the throughput at queue 1. MATLAB keeps two nearly identical
16 * 800-line files (mapqn_bnd_qr_ld.m and mapqn_bnd_qr_delay.m differ only in
17 * XZ, the order in which PC2 is emitted, and the shape of the returned
18 * marginals); the JAR duplicates them again. Here each family is emitted by
19 * one function and each entry point calls the families it needs, so a fix to
20 * a family cannot land in one entry point and miss the other.
21 *
22 * Emitting the families BY NAME is deliberate. The recurring failure mode in
23 * this domain is an inventory gap: a file whose every subscript is internally
24 * consistent but which omits a whole family, producing a vacuous [0,1] bound
25 * that reads as "loose but valid" (see the mapqn notes in _kb/03-api-layer.md).
26 * Naming the families makes the inventory diffable against the reference.
27 *
28 * Sign convention: every family is assembled as a single accumulated row and
29 * emitted as `row = 0` or `row >= 0`, with the reference's right-hand terms
30 * carried across with a negative sign rather than dropped. QBAL in particular
31 * is LHS1 + LHS2 = RHS1 + RHS2; dropping the RHS terms would force-zero the
32 * variables they carry and silently tighten the polytope.
33 *
34 * Variable bounds. The reference sets lb = 0, ub = 1 on every variable and
35 * ub = 0 on the states the ZERO families exclude. Those are passed to the
36 * solver as bounds, not as rows: line::lp::LpModel takes explicit per-variable
37 * bounds, substitutes out any variable with lb == ub (which is every ZERO
38 * state, the bulk of the model) and materializes a row only for a finite upper
39 * bound that is not also a lower bound. The JAR has to add ~2 nVars explicit
40 * rows instead, because Apache Commons SimplexSolver does not box variables
41 * and the maximization is otherwise unbounded.
42 */
43
44#include <cstddef>
45#include <vector>
46
48#include "line/num/number.h"
49#include "line/util/simplex.h"
50
51namespace line {
52namespace mapqn {
53
54/**
55 * ZERO1/2/3: states that carry no probability mass, imposed as ub = 0.
56 * ZERO1 i == j, nj == ni, h != k (one queue cannot be in two phases)
57 * ZERO2 i == j, nj != ni (one queue cannot hold two populations)
58 * ZERO3 i != j, nj + ni > N (more jobs than the network holds)
59 * Returns the indicator so SYMMETRY can skip pairs that are both zeroed, as
60 * the reference does.
61 */
62template <class T>
63std::vector<char> qr_zero_bounds(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
64 const int M = p.M, N = p.N;
65 std::vector<char> is_zero(idx.num_vars(), 0);
66 for (int j = 0; j < M; ++j)
67 for (int nj = 0; nj <= N; ++nj)
68 for (int kj = 0; kj < p.K[j]; ++kj)
69 for (int i = 0; i < M; ++i)
70 for (int ni = 0; ni <= N; ++ni)
71 for (int hi = 0; hi < p.K[i]; ++hi) {
72 const bool z = (i == j && nj == ni && hi != kj) || (i == j && nj != ni) ||
73 (i != j && nj + ni > N);
74 if (z) {
75 const std::size_t v = idx(j, nj, kj, i, ni, hi);
76 is_zero[v] = 1;
77 m.set_upper(v, T());
78 }
79 }
80 return is_zero;
81}
82
83/** ONE: sum over (nj,k) of p2(j,nj,k,j,nj,k) = 1, per queue j. */
84template <class T>
85void qr_one(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
86 const T one = num_traits<T>::from_int(1);
87 for (int j = 0; j < p.M; ++j) {
88 for (int nj = 0; nj <= p.N; ++nj)
89 for (int kj = 0; kj < p.K[j]; ++kj) m.row_add(idx(j, nj, kj, j, nj, kj), one);
90 m.emit_eq(one);
91 }
92}
93
94/** SYMMETRY: p2(i,ni,h,j,nj,k) = p2(j,nj,k,i,ni,h), emitted once per pair. */
95template <class T>
96void qr_symmetry(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m,
97 const std::vector<char>& is_zero) {
98 const int M = p.M, N = p.N;
99 const T one = num_traits<T>::from_int(1);
100 const T mone = num_traits<T>::from_int(-1);
101 for (int j = 0; j < M; ++j)
102 for (int nj = 0; nj <= N; ++nj)
103 for (int kj = 0; kj < p.K[j]; ++kj)
104 for (int i = j + 1; i < M; ++i) // i > j only, one ordering
105 for (int ni = 0; ni <= N; ++ni) {
106 if (i != j && nj + ni > N) continue;
107 for (int hi = 0; hi < p.K[i]; ++hi) {
108 const std::size_t a = idx(j, nj, kj, i, ni, hi);
109 const std::size_t b = idx(i, ni, hi, j, nj, kj);
110 if (is_zero[a] && is_zero[b]) continue;
111 if (a == b) continue;
112 m.row_add(a, one);
113 m.row_add(b, mone);
114 m.emit_eq(T());
115 }
116 }
117}
118
119/**
120 * MARGINALS: p2(j,nj,k,j,nj,k) = sum over (ni <= N-nj, h) of p2(j,nj,k,i,ni,h)
121 * for every i != j. The diagonal entry is the marginal of queue j, so the
122 * joint over the pair (j,i) must sum back to it.
123 */
124template <class T>
125void qr_marginals(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
126 const int M = p.M, N = p.N;
127 const T one = num_traits<T>::from_int(1);
128 const T mone = num_traits<T>::from_int(-1);
129 for (int j = 0; j < M; ++j)
130 for (int kj = 0; kj < p.K[j]; ++kj)
131 for (int nj = 0; nj <= N; ++nj)
132 for (int i = 0; i < M; ++i) {
133 if (i == j) continue;
134 m.row_add(idx(j, nj, kj, j, nj, kj), one);
135 for (int ni = 0; ni <= N - nj; ++ni)
136 for (int hi = 0; hi < p.K[i]; ++hi) m.row_add(idx(j, nj, kj, i, ni, hi), mone);
137 m.emit_eq(T());
138 }
139}
140
141/**
142 * THM1 (Little's law in probability form): for each (j,k),
143 * sum_{i,nj>=1,ni>=1,h} ni p2(j,nj,k,i,ni,h) = N sum_{nj>=1} p2(j,nj,k,j,nj,k).
144 */
145template <class T>
146void qr_thm1(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
147 const int M = p.M, N = p.N;
148 for (int j = 0; j < M; ++j)
149 for (int kj = 0; kj < p.K[j]; ++kj) {
150 for (int i = 0; i < M; ++i)
151 for (int nj = 1; nj <= N; ++nj)
152 for (int ni = 1; ni <= N; ++ni)
153 for (int hi = 0; hi < p.K[i]; ++hi)
154 m.row_add_int(idx(j, nj, kj, i, ni, hi), ni);
155 for (int nj = 1; nj <= N; ++nj) m.row_add_int(idx(j, nj, kj, j, nj, kj), -N);
156 m.emit_eq(T());
157 }
158}
159
160/** THM1c: the nj = 0 companion of THM1, conditioning on queue j being empty. */
161template <class T>
162void qr_thm1c(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
163 const int M = p.M, N = p.N;
164 for (int j = 0; j < M; ++j)
165 for (int kj = 0; kj < p.K[j]; ++kj) {
166 for (int i = 0; i < M; ++i)
167 for (int ni = 1; ni <= N; ++ni)
168 for (int hi = 0; hi < p.K[i]; ++hi) m.row_add_int(idx(j, 0, kj, i, ni, hi), ni);
169 m.row_add_int(idx(j, 0, kj, j, 0, kj), -N);
170 m.emit_eq(T());
171 }
172}
173
174/** PC2 (second moment): sum_{i,j,ni>=1,nj>=1,h,k} nj ni p2(j,nj,k,i,ni,h) = N^2. */
175template <class T>
176void qr_pc2(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
177 const int M = p.M, N = p.N;
178 for (int i = 0; i < M; ++i)
179 for (int j = 0; j < M; ++j)
180 for (int ni = 1; ni <= N; ++ni)
181 for (int nj = 1; nj <= N; ++nj)
182 for (int hi = 0; hi < p.K[i]; ++hi)
183 for (int kj = 0; kj < p.K[j]; ++kj)
184 m.row_add_int(idx(j, nj, kj, i, ni, hi), static_cast<long>(nj) * ni);
185 m.emit_eq(num_traits<T>::from_int(static_cast<long>(N) * N));
186}
187
188/**
189 * XZ (delay model only): the think-time balance
190 * sum_{ni>=1,k} ni p2(M,ni,k,M,ni,k) = (Z/D1) sum_{k,nj>=1} p2(1,nj,k,1,nj,k),
191 * i.e. the mean population at the delay station equals Z times the throughput
192 * of queue 1, whose service demand is D1.
193 */
194template <class T>
195void qr_xz(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
196 const int M = p.M, N = p.N;
197 const int last = M - 1;
198 for (int ni = 1; ni <= N; ++ni)
199 for (int kM = 0; kM < p.K[last]; ++kM) m.row_add_int(idx(last, ni, kM, last, ni, kM), ni);
200 if (p.D1 == T()) throw InputError("mapqn_bnd_qr_delay: D1 must be nonzero");
201 const T ratio = p.Z / p.D1;
202 const T mratio = -ratio;
203 for (int kj = 0; kj < p.K[0]; ++kj)
204 for (int nj = 1; nj <= N; ++nj) m.row_add(idx(0, nj, kj, 0, nj, kj), mratio);
205 m.emit_eq(T());
206}
207
208/**
209 * THM2 (phase balance): for each (i,k) the total rate out of phase k at queue
210 * i equals the total rate into it, summed over populations ni >= 1.
211 */
212template <class T>
213void qr_thm2(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
214 const int M = p.M, N = p.N;
215 for (int i = 0; i < M; ++i)
216 for (int ki = 0; ki < p.K[i]; ++ki) {
217 for (int j = 0; j < M; ++j)
218 for (int hi = 0; hi < p.K[i]; ++hi) {
219 if (hi == ki && j == i) continue;
220 for (int ni = 1; ni <= N; ++ni) {
221 const T q_out = mapqn_q(p, i, j, ki, hi, ni);
222 const T q_in = mapqn_q(p, i, j, hi, ki, ni);
223 m.row_add(idx(i, ni, ki, i, ni, ki), q_out);
224 const T mq_in = -q_in;
225 m.row_add(idx(i, ni, hi, i, ni, hi), mq_in);
226 }
227 }
228 m.emit_eq(T());
229 }
230}
231
232/**
233 * THM3a (population flow balance, 1 <= ni <= N-1): the rate at which queue i
234 * is entered while holding ni jobs equals the rate at which it is left while
235 * holding ni+1.
236 */
237template <class T>
238void qr_thm3a(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
239 const int M = p.M, N = p.N;
240 for (int i = 0; i < M; ++i)
241 for (int ni = 1; ni <= N - 1; ++ni) {
242 for (int j = 0; j < M; ++j) {
243 if (j == i) continue;
244 for (int kj = 0; kj < p.K[j]; ++kj)
245 for (int hj = 0; hj < p.K[j]; ++hj)
246 for (int u = 0; u < p.K[i]; ++u)
247 for (int nj = 1; nj <= N - ni; ++nj)
248 m.row_add(idx(j, nj, kj, i, ni, u), mapqn_q(p, j, i, kj, hj, nj));
249 }
250 for (int j = 0; j < M; ++j) {
251 if (j == i) continue;
252 for (int ki = 0; ki < p.K[i]; ++ki)
253 for (int hi = 0; hi < p.K[i]; ++hi) {
254 const T qv = mapqn_q(p, i, j, ki, hi, ni + 1);
255 const T mqv = -qv;
256 m.row_add(idx(i, ni + 1, ki, i, ni + 1, ki), mqv);
257 }
258 }
259 m.emit_eq(T());
260 }
261}
262
263/** THM3b: the ni = 0 boundary case of THM3a, resolved per arrival phase u. */
264template <class T>
265void qr_thm3b(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
266 const int M = p.M, N = p.N;
267 for (int i = 0; i < M; ++i)
268 for (int u = 0; u < p.K[i]; ++u) {
269 for (int j = 0; j < M; ++j) {
270 if (j == i) continue;
271 for (int kj = 0; kj < p.K[j]; ++kj)
272 for (int hj = 0; hj < p.K[j]; ++hj)
273 for (int nj = 1; nj <= N; ++nj)
274 m.row_add(idx(j, nj, kj, i, 0, u), mapqn_q(p, j, i, kj, hj, nj));
275 }
276 for (int j = 0; j < M; ++j) {
277 if (j == i) continue;
278 for (int ki = 0; ki < p.K[i]; ++ki) {
279 const T qv = mapqn_q(p, i, j, ki, u, 1);
280 const T mqv = -qv;
281 m.row_add(idx(i, 1, ki, i, 1, ki), mqv);
282 }
283 }
284 m.emit_eq(T());
285 }
286}
287
288/**
289 * QBAL (queue balance): LHS1 + LHS2 = RHS1 + RHS2 for each (i,k).
290 *
291 * This is the family the reference warns about. The right-hand side carries
292 * two distinct blocks -- the arrival flow into queue i (RHS1, itself in two
293 * pieces, the ni = 0 term and the ni >= 1 term) and the population-weighted
294 * phase inflow (RHS2). Dropping either would leave the variables they touch
295 * with no other constraint mentioning them and force them to zero.
296 */
297template <class T>
298void qr_qbal(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
299 const int M = p.M, N = p.N;
300 for (int i = 0; i < M; ++i)
301 for (int ki = 0; ki < p.K[i]; ++ki) {
302 // LHS1: sum_{h!=k, j, ni>=1} q(i,j,k,h,ni) * ni * p2(i,ni,k,i,ni,k)
303 for (int hi = 0; hi < p.K[i]; ++hi) {
304 if (hi == ki) continue;
305 for (int j = 0; j < M; ++j)
306 for (int ni = 1; ni <= N; ++ni) {
307 const T qv = mapqn_q(p, i, j, ki, hi, ni);
308 const T w = qv * num_traits<T>::from_int(ni);
309 m.row_add(idx(i, ni, ki, i, ni, ki), w);
310 }
311 }
312 // LHS2: sum_{j!=i, h, ni>=1} q(i,j,h,k,ni) * p2(i,ni,h,i,ni,h)
313 for (int j = 0; j < M; ++j) {
314 if (j == i) continue;
315 for (int hi = 0; hi < p.K[i]; ++hi)
316 for (int ni = 1; ni <= N; ++ni)
317 m.row_add(idx(i, ni, hi, i, ni, hi), mapqn_q(p, i, j, hi, ki, ni));
318 }
319 // -RHS1a: arrivals finding queue i empty
320 for (int j = 0; j < M; ++j) {
321 if (j == i) continue;
322 for (int u = 0; u < p.K[j]; ++u)
323 for (int w = 0; w < p.K[j]; ++w)
324 for (int nj = 1; nj <= N; ++nj) {
325 const T qv = mapqn_q(p, j, i, u, w, nj);
326 const T mqv = -qv;
327 m.row_add(idx(j, nj, u, i, 0, ki), mqv);
328 }
329 }
330 // -RHS1b: arrivals finding queue i busy
331 for (int j = 0; j < M; ++j) {
332 if (j == i) continue;
333 for (int u = 0; u < p.K[j]; ++u)
334 for (int w = 0; w < p.K[j]; ++w)
335 for (int nj = 1; nj <= N; ++nj) {
336 const T qv = mapqn_q(p, j, i, u, w, nj);
337 const T mqv = -qv;
338 for (int ni = 1; ni <= N; ++ni) m.row_add(idx(i, ni, ki, j, nj, u), mqv);
339 }
340 }
341 // -RHS2: population-weighted phase inflow
342 for (int hi = 0; hi < p.K[i]; ++hi) {
343 if (hi == ki) continue;
344 for (int j = 0; j < M; ++j)
345 for (int ni = 1; ni <= N; ++ni) {
346 const T qv = mapqn_q(p, i, j, hi, ki, ni);
347 const T w = qv * num_traits<T>::from_int(ni);
348 const T mw = -w;
349 m.row_add(idx(i, ni, hi, i, ni, hi), mw);
350 }
351 }
352 m.emit_eq(T());
353 }
354}
355
356/**
357 * COR1a: the order-1 correlation cut, for each (i, kstar, ni = 0..N-2).
358 * Blocks A..H follow the reference letter for letter; A and B are the arrival
359 * terms, C..H the departure and phase-change terms at populations ni+1 and
360 * ni+2.
361 */
362template <class T>
363void qr_cor1a(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
364 const int M = p.M, N = p.N;
365 for (int i = 0; i < M; ++i)
366 for (int kstar = 0; kstar < p.K[i]; ++kstar)
367 for (int nic = 0; nic <= N - 2; ++nic) {
368 // A
369 for (int j = 0; j < M; ++j) {
370 if (j == i) continue;
371 for (int kj = 0; kj < p.K[j]; ++kj)
372 for (int hj = 0; hj < p.K[j]; ++hj)
373 for (int u = 0; u < p.K[i]; ++u) {
374 if (u == kstar) continue;
375 for (int nj = 1; nj <= N - nic; ++nj)
376 m.row_add(idx(j, nj, kj, i, nic, u), mapqn_q(p, j, i, kj, hj, nj));
377 }
378 }
379 // B
380 for (int j = 0; j < M; ++j) {
381 if (j == i) continue;
382 for (int kj = 0; kj < p.K[j]; ++kj)
383 for (int hj = 0; hj < p.K[j]; ++hj)
384 for (int nj = 1; nj <= N - nic; ++nj)
385 m.row_add(idx(j, nj, kj, i, nic + 1, kstar),
386 mapqn_q(p, j, i, kj, hj, nj));
387 }
388 // C
389 for (int k2 = 0; k2 < p.K[i]; ++k2) {
390 if (k2 == kstar) continue;
391 m.row_add(idx(i, nic + 1, kstar, i, nic + 1, kstar),
392 mapqn_q(p, i, i, kstar, k2, nic + 1));
393 }
394 // -D
395 for (int j = 0; j < M; ++j) {
396 if (j == i) continue;
397 for (int k2 = 0; k2 < p.K[i]; ++k2) {
398 if (k2 == kstar) continue;
399 const T qv = mapqn_q(p, i, j, k2, k2, nic + 1);
400 const T mqv = -qv;
401 m.row_add(idx(i, nic + 1, k2, i, nic + 1, k2), mqv);
402 }
403 }
404 // -E
405 for (int j = 0; j < M; ++j) {
406 if (j == i) continue;
407 for (int k2 = 0; k2 < p.K[i]; ++k2) {
408 if (k2 == kstar) continue;
409 for (int h2 = 0; h2 < p.K[i]; ++h2) {
410 if (h2 == k2) continue;
411 const T qv = mapqn_q(p, i, j, k2, h2, nic + 1);
412 const T mqv = -qv;
413 m.row_add(idx(i, nic + 1, k2, i, nic + 1, k2), mqv);
414 }
415 }
416 }
417 // -F
418 for (int j = 0; j < M; ++j) {
419 if (j == i) continue;
420 for (int k2 = 0; k2 < p.K[i]; ++k2) {
421 if (k2 == kstar) continue;
422 const T qv = mapqn_q(p, i, j, k2, kstar, nic + 2);
423 const T mqv = -qv;
424 m.row_add(idx(i, nic + 2, k2, i, nic + 2, k2), mqv);
425 }
426 }
427 // -G
428 for (int j = 0; j < M; ++j) {
429 if (j == i) continue;
430 const T qv = mapqn_q(p, i, j, kstar, kstar, nic + 2);
431 const T mqv = -qv;
432 m.row_add(idx(i, nic + 2, kstar, i, nic + 2, kstar), mqv);
433 }
434 // -H
435 for (int k2 = 0; k2 < p.K[i]; ++k2) {
436 if (k2 == kstar) continue;
437 const T qv = mapqn_q(p, i, i, k2, kstar, nic + 1);
438 const T mqv = -qv;
439 m.row_add(idx(i, nic + 1, k2, i, nic + 1, k2), mqv);
440 }
441 m.emit_eq(T());
442 }
443}
444
445/** COR1b: the ni = N-1 boundary of COR1a (blocks A', C', D', E', H'). */
446template <class T>
447void qr_cor1b(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
448 const int M = p.M, N = p.N;
449 for (int i = 0; i < M; ++i)
450 for (int kstar = 0; kstar < p.K[i]; ++kstar) {
451 // A'
452 for (int j = 0; j < M; ++j) {
453 if (j == i) continue;
454 for (int kj = 0; kj < p.K[j]; ++kj)
455 for (int hj = 0; hj < p.K[j]; ++hj)
456 for (int u = 0; u < p.K[i]; ++u) {
457 if (u == kstar) continue;
458 m.row_add(idx(j, 1, kj, i, N - 1, u), mapqn_q(p, j, i, kj, hj, 1));
459 }
460 }
461 // C'
462 for (int k2 = 0; k2 < p.K[i]; ++k2) {
463 if (k2 == kstar) continue;
464 m.row_add(idx(i, N, kstar, i, N, kstar), mapqn_q(p, i, i, kstar, k2, N));
465 }
466 // -D'
467 for (int j = 0; j < M; ++j) {
468 if (j == i) continue;
469 for (int k2 = 0; k2 < p.K[i]; ++k2) {
470 if (k2 == kstar) continue;
471 const T qv = mapqn_q(p, i, j, k2, k2, N);
472 const T mqv = -qv;
473 m.row_add(idx(i, N, k2, i, N, k2), mqv);
474 }
475 }
476 // -E'
477 for (int j = 0; j < M; ++j) {
478 if (j == i) continue;
479 for (int k2 = 0; k2 < p.K[i]; ++k2) {
480 if (k2 == kstar) continue;
481 for (int h2 = 0; h2 < p.K[i]; ++h2) {
482 if (h2 == k2) continue;
483 const T qv = mapqn_q(p, i, j, k2, h2, N);
484 const T mqv = -qv;
485 m.row_add(idx(i, N, k2, i, N, k2), mqv);
486 }
487 }
488 }
489 // -H'
490 for (int k2 = 0; k2 < p.K[i]; ++k2) {
491 if (k2 == kstar) continue;
492 const T qv = mapqn_q(p, i, i, k2, kstar, N);
493 const T mqv = -qv;
494 m.row_add(idx(i, N, k2, i, N, k2), mqv);
495 }
496 m.emit_eq(T());
497 }
498}
499
500/**
501 * THM4 (QMIN): for each (j,k,i),
502 * sum_{t,h,nj,nt} nt p2(j,nj,k,t,nt,h) >= N sum_{h,nj,ni} p2(j,nj,k,i,ni,h),
503 * the only inequality family. The reference passes it to linprog as
504 * -row * x <= 0; here it is emitted directly as row >= 0.
505 */
506template <class T>
507void qr_thm4(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m) {
508 const int M = p.M, N = p.N;
509 for (int j = 0; j < M; ++j)
510 for (int kj = 0; kj < p.K[j]; ++kj)
511 for (int i = 0; i < M; ++i) {
512 for (int t = 0; t < M; ++t)
513 for (int ht = 0; ht < p.K[t]; ++ht)
514 for (int nj = 0; nj <= N; ++nj)
515 for (int nt = 0; nt <= N; ++nt)
516 m.row_add_int(idx(j, nj, kj, t, nt, ht), nt);
517 for (int hi = 0; hi < p.K[i]; ++hi)
518 for (int nj = 0; nj <= N; ++nj)
519 for (int ni = 0; ni <= N; ++ni)
520 m.row_add_int(idx(j, nj, kj, i, ni, hi), -N);
521 m.emit_ge(T());
522 }
523}
524
525namespace detail {
526
527/** Common tail: solve, then unpack the objective and the diagonal marginals. */
528template <class T>
529MapqnQrResult<T> qr_finish(const MapqnParams<T>& p, const P2Index& idx, lp::LpModel<T>& m,
530 int objective_queue, int objective_phase, int objective_n,
531 MapqnSense sense) {
532 m.set_cost(idx(objective_queue, objective_n, objective_phase, objective_queue, objective_n,
533 objective_phase),
535 m.set_maximize(sense == MapqnSense::Max);
536
537 MapqnQrResult<T> res;
538 res.num_vars = m.num_vars();
539 res.num_rows = m.num_rows();
541 res.status = lp::lp_status_name(s.status);
542 res.iterations = s.iterations;
543 res.ok = s.ok();
544 if (!res.ok) return res;
545 res.objective = s.objective;
546 res.x = s.x;
547 res.p2marginals.resize(static_cast<std::size_t>(p.M));
548 for (int j = 0; j < p.M; ++j) {
549 res.p2marginals[j] = Matrix<T>(static_cast<std::size_t>(p.N + 1),
550 static_cast<std::size_t>(p.K[j]), T());
551 for (int nj = 0; nj <= p.N; ++nj)
552 for (int kj = 0; kj < p.K[j]; ++kj)
553 res.p2marginals[j](static_cast<std::size_t>(nj), static_cast<std::size_t>(kj)) =
554 s.x[idx(j, nj, kj, j, nj, kj)];
555 }
556 return res;
557}
558
559/** Shared argument validation for both entry points. */
560template <class T>
561void qr_check_objective(const MapqnParams<T>& p, int objective_queue, int objective_phase,
562 int objective_n) {
563 if (objective_queue < 0 || objective_queue >= p.M)
564 throw InputError("mapqn: objective_queue out of range");
565 if (objective_phase < 0 || objective_phase >= p.K[objective_queue])
566 throw InputError("mapqn: objective_phase out of range");
567 if (objective_n < 0 || objective_n > p.N) throw InputError("mapqn: objective_n out of range");
568}
569
570} // namespace detail
571
572} // namespace mapqn
573} // namespace line
574
575#endif // LINE_API_MAPQN_MAPQN_QR_COMMON_H
InputError(const std::string &what)
Definition error.h:39
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
void emit_eq(const T &rhs)
Definition simplex.h:217
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
std::size_t num_rows() const
Definition simplex.h:125
void emit_ge(const T &rhs)
Definition simplex.h:218
void row_add_int(std::size_t j, long v)
Definition simplex.h:197
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
std::size_t num_vars() const
Definition simplex.h:124
void set_upper(std::size_t j, const T &v)
Definition simplex.h:134
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
Model parameters and variable indexing shared by the mapqn QR bounds.
const char * lp_status_name(LpStatus s)
Definition simplex.h:84
LpSolution< T > simplex_solve(const LpModel< T > &model, std::size_t max_iterations=0)
Solve the model.
Definition simplex.h:286
void qr_pc2(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
PC2 (second moment): sum_{i,j,ni>=1,nj>=1,h,k} nj ni p2(j,nj,k,i,ni,h) = N^2.
void qr_qbal(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
QBAL (queue balance): LHS1 + LHS2 = RHS1 + RHS2 for each (i,k).
void qr_thm4(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM4 (QMIN): for each (j,k,i), sum_{t,h,nj,nt} nt p2(j,nj,k,t,nt,h) >= N sum_{h,nj,...
void qr_thm1(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM1 (Little's law in probability form): for each (j,k), sum_{i,nj>=1,ni>=1,h} ni p2(j,...
std::vector< char > qr_zero_bounds(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
ZERO1/2/3: states that carry no probability mass, imposed as ub = 0.
MapqnSense
Which direction the bound is taken in.
void qr_xz(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
XZ (delay model only): the think-time balance sum_{ni>=1,k} ni p2(M,ni,k,M,ni,k) = (Z/D1) sum_{k,...
void qr_symmetry(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m, const std::vector< char > &is_zero)
SYMMETRY: p2(i,ni,h,j,nj,k) = p2(j,nj,k,i,ni,h), emitted once per pair.
void qr_marginals(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
MARGINALS: p2(j,nj,k,j,nj,k) = sum over (ni <= N-nj, h) of p2(j,nj,k,i,ni,h) for every i !...
void qr_one(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
ONE: sum over (nj,k) of p2(j,nj,k,j,nj,k) = 1, per queue j.
void qr_thm1c(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM1c: the nj = 0 companion of THM1, conditioning on queue j being empty.
void qr_thm2(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM2 (phase balance): for each (i,k) the total rate out of phase k at queue i equals the total rate i...
void qr_thm3a(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM3a (population flow balance, 1 <= ni <= N-1): the rate at which queue i is entered while holding n...
void qr_cor1a(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
COR1a: the order-1 correlation cut, for each (i, kstar, ni = 0..N-2).
void qr_thm3b(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
THM3b: the ni = 0 boundary case of THM3a, resolved per arrival phase u.
void qr_cor1b(const MapqnParams< T > &p, const P2Index &idx, lp::LpModel< T > &m)
COR1b: the ni = N-1 boundary of COR1a (blocks A', C', D', E', H').
T mapqn_q(const MapqnParams< T > &p, int i, int j, int k, int h, int n)
q(i,j,k,h,n): rate at which queue i, holding n jobs and in phase k, moves to phase h while routing a ...
Number-type abstraction for the templated API port.
Templated primal simplex with Bland's rule.
std::size_t iterations
Definition simplex.h:98
T objective
c'x, in the sense requested (max or min)
Definition simplex.h:97
bool ok() const
Definition simplex.h:99
std::vector< T > x
primal solution in the ORIGINAL variable space
Definition simplex.h:96
Parameters of a MAP queueing network for the QR bounds.
int N
total population
std::vector< int > K
K[i] = number of phases at queue i.
T Z
think time (delay model only)
int M
number of queues
T D1
service demand at queue 1 (delay model only)
Flat index of the joint variable p2(j,nj,k,i,ni,h).
std::size_t num_vars() const