LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mapqn_p1_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_P1_COMMON_H
6#define LINE_API_MAPQN_MAPQN_P1_COMMON_H
7
8/**
9 * @file
10 * @ingroup api_mapqn
11 * The variable layout and the constraint families shared by the two reductions
12 * that carry singly-indexed probabilities: the linear reduction
13 * (mapqn_bnd_lr.m) and the general quadratic reduction (mapqn_bnd_qr.m).
14 *
15 * The two references duplicate these fifteen families verbatim -- ZER1..ZER4,
16 * CEQU, ONE1, UTLB, UTLC, QLEN, CLEN, ONE, POPC, SRVB, UUB1, QUB1 are the same
17 * loops with the same coefficients in both files -- and the QR file then adds
18 * the p2 level on top. Emitting each family from ONE function here is the same
19 * discipline mapqn_qr_common.h applies to the load-dependent pair, and for the
20 * same reason: a correction to a family must not be able to land in one entry
21 * point and miss the other.
22 *
23 * INDEX LAYOUT. Blocks in the order the references register them, minus the
24 * three inert ones (see below). Populations are 0..N as written; queues and
25 * phases are 0-based, so every reference loop `for i = 1:M` becomes
26 * `for i = 0; i < M; ++i` and every subscript except a population drops by one.
27 * `half(i,ni,h)` is shared by the inner half of p1 and p1c and by both halves
28 * of p2, which is what makes the QR projection families PI21/PI22 plain index
29 * arithmetic and PI23 a swap.
30 *
31 * INERT VARIABLES OMITTED. Both references register UP(j,k,i,h), QP(j,k,i,h)
32 * and I_var(j,k,i), give them upper bounds, and then reference them in no
33 * constraint and in no objective (verified in both files: the three index
34 * arrays appear only at their own registration and bounding). A variable with
35 * no row and no cost cannot move the optimum, so they are not allocated.
36 */
37
38#include <cstddef>
39#include <vector>
40
42#include "line/num/number.h"
43#include "line/util/simplex.h"
44
45namespace line {
46namespace mapqn {
47
48/**
49 * Variable layout of the p1-level models.
50 *
51 * `with_p2` selects the quadratic reduction's extra block; the linear reduction
52 * carries no joint variables at all, and allocating them would square the model
53 * for nothing.
54 */
56 int M = 0, N = 0;
57 std::vector<int> K;
58 std::vector<int> cumK; ///< cumK[i] = sum_{i' < i} K(i')
59 std::size_t sumK = 0; ///< sum_i K(i)
60 std::size_t block = 0; ///< (N+1) * sumK, the pairwise half-index range
61 bool has_p2 = false;
62 std::size_t off_U = 0, off_IT = 0, off_Q = 0, off_C = 0;
63 std::size_t off_p1 = 0, off_p1c = 0, off_p2 = 0, total = 0;
64
66 MapqnP1Index(int m, int n, const std::vector<int>& k, bool with_p2)
67 : M(m), N(n), K(k), has_p2(with_p2) {
68 cumK.assign(static_cast<std::size_t>(M) + 1, 0);
69 for (int i = 0; i < M; ++i) cumK[i + 1] = cumK[i] + K[i];
70 sumK = static_cast<std::size_t>(cumK[M]);
71 block = static_cast<std::size_t>(N + 1) * sumK;
72 off_U = 0;
73 off_IT = off_U + sumK;
74 off_Q = off_IT + sumK;
75 off_C = off_Q + sumK;
76 off_p1 = off_C + sumK * static_cast<std::size_t>(M);
80 }
81
82 /** Flat (queue, phase) index, range sumK. */
83 std::size_t pk(int i, int k) const {
84 return static_cast<std::size_t>(cumK[i]) + static_cast<std::size_t>(k);
85 }
86 /** Flat (queue, population, phase) half-index, range block. */
87 std::size_t half(int i, int ni, int h) const {
88 return static_cast<std::size_t>(N + 1) * static_cast<std::size_t>(cumK[i]) +
89 static_cast<std::size_t>(ni) * static_cast<std::size_t>(K[i]) +
90 static_cast<std::size_t>(h);
91 }
92
93 std::size_t U(int i, int k) const { return off_U + pk(i, k); }
94 std::size_t IT(int i, int k) const { return off_IT + pk(i, k); }
95 std::size_t Q(int i, int k) const { return off_Q + pk(i, k); }
96 std::size_t C(int j, int k, int i) const {
97 return off_C + pk(j, k) * static_cast<std::size_t>(M) + static_cast<std::size_t>(i);
98 }
99 std::size_t p1(int j, int k, int i, int ni, int h) const {
100 return off_p1 + pk(j, k) * block + half(i, ni, h);
101 }
102 std::size_t p1c(int j, int k, int i, int ni, int h) const {
103 return off_p1c + pk(j, k) * block + half(i, ni, h);
104 }
105 std::size_t p2(int j, int nj, int k, int i, int ni, int h) const {
106 return off_p2 + half(j, nj, k) * block + half(i, ni, h);
107 }
108
109 std::size_t num_vars() const { return total; }
110};
111
112namespace detail {
113
114/**
115 * q(i,j,k,h): rate at which queue i in phase k moves to phase h while routing a
116 * job to queue j.
117 *
118 * Port of the q{i,j}(k,h) cell built at the head of both references. It is
119 * deliberately NOT mapqn_q from mapqn_params.h: neither model has load
120 * dependence, so there is no population argument and no alpha factor, and
121 * mapqn_q additionally returns 0 at n == 0, which has no counterpart here. The
122 * i == j branch adds the background rate v, because a self-routing completion
123 * and a phase change without completion are indistinguishable in the marginal.
124 */
125template <class T>
126T qr_rate(const MapqnParams<T>& p, int i, int j, int k, int h) {
127 const std::size_t ki = static_cast<std::size_t>(k), hi = static_cast<std::size_t>(h);
128 const std::size_t ii = static_cast<std::size_t>(i), ji = static_cast<std::size_t>(j);
129 if (j != i) return T(p.r(ii, ji) * p.mu[i](ki, hi));
130 return T(p.v[i](ki, hi) + p.r(ii, ii) * p.mu[i](ki, hi));
131}
132
133/** Default bounds on the p1-level variables, before ZER pins the zeros. */
134template <class T>
135void p1_bounds(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
136 const T one = num_traits<T>::from_int(1);
137 const T nn = num_traits<T>::from_int(p.N);
138 for (int i = 0; i < p.M; ++i) {
139 for (int k = 0; k < p.K[i]; ++k) {
140 m.set_bounds(x.U(i, k), T(), one);
141 m.set_bounds(x.IT(i, k), T(), one);
142 m.set_bounds(x.Q(i, k), T(), nn);
143 for (int t = 0; t < p.M; ++t) m.set_bounds(x.C(i, k, t), T(), nn);
144 for (int t = 0; t < p.M; ++t) {
145 for (int nt = 0; nt <= p.N; ++nt) {
146 for (int h = 0; h < p.K[t]; ++h) {
147 m.set_bounds(x.p1(i, k, t, nt, h), T(), one);
148 m.set_bounds(x.p1c(i, k, t, nt, h), T(), one);
149 }
150 }
151 }
152 }
153 }
154}
155
156/** ZER1: p1(j,k,j,0,k) = 0. An occupied station cannot hold zero jobs. */
157template <class T>
158void p1_zer1(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
159 for (int j = 0; j < p.M; ++j)
160 for (int k = 0; k < p.K[j]; ++k) m.fix(x.p1(j, k, j, 0, k), T());
161}
162
163/** ZER2: p1(j,k,j,nj,h) = 0 for h != k. One station, one phase. */
164template <class T>
165void p1_zer2(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
166 for (int j = 0; j < p.M; ++j)
167 for (int k = 0; k < p.K[j]; ++k)
168 for (int nj = 0; nj <= p.N; ++nj)
169 for (int h = 0; h < p.K[j]; ++h)
170 if (h != k) m.fix(x.p1(j, k, j, nj, h), T());
171}
172
173/** ZER3: p1(j,k,i,N,h) = 0 for i != j. A busy j leaves i short of N. */
174template <class T>
175void p1_zer3(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
176 for (int j = 0; j < p.M; ++j)
177 for (int k = 0; k < p.K[j]; ++k)
178 for (int i = 0; i < p.M; ++i)
179 if (i != j)
180 for (int h = 0; h < p.K[i]; ++h) m.fix(x.p1(j, k, i, p.N, h), T());
181}
182
183/** ZER4: p1c(j,k,j,nj,h) = 0 for nj >= 1. The complement is the idle branch. */
184template <class T>
185void p1_zer4(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
186 for (int j = 0; j < p.M; ++j)
187 for (int k = 0; k < p.K[j]; ++k)
188 for (int nj = 1; nj <= p.N; ++nj)
189 for (int h = 0; h < p.K[j]; ++h) m.fix(x.p1c(j, k, j, nj, h), T());
190}
191
192/** CEQU: C(j,k,j) = Q(j,k). The self-conditioned length is the mean length. */
193template <class T>
194void p1_cequ(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
195 for (int j = 0; j < p.M; ++j) {
196 for (int k = 0; k < p.K[j]; ++k) {
197 m.row_add_int(x.C(j, k, j), 1);
198 m.row_add_int(x.Q(j, k), -1);
199 m.emit_eq_int(0);
200 }
201 }
202}
203
204/** ONE1: sum over k,h,ni of (p1 + p1c) = 1 for each ordered pair (j,i). */
205template <class T>
206void p1_one1(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
207 for (int j = 0; j < p.M; ++j) {
208 for (int i = 0; i < p.M; ++i) {
209 for (int k = 0; k < p.K[j]; ++k) {
210 for (int h = 0; h < p.K[i]; ++h) {
211 for (int ni = 0; ni <= p.N; ++ni) {
212 m.row_add_int(x.p1(j, k, i, ni, h), 1);
213 m.row_add_int(x.p1c(j, k, i, ni, h), 1);
214 }
215 }
216 }
217 m.emit_eq_int(1);
218 }
219 }
220}
221
222/** UTLB: U(i,k) = sum over nt,h of p1(i,k,t,nt,h), one row per witness t. */
223template <class T>
224void p1_utlb(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
225 for (int i = 0; i < p.M; ++i) {
226 for (int k = 0; k < p.K[i]; ++k) {
227 for (int t = 0; t < p.M; ++t) {
228 m.row_add_int(x.U(i, k), 1);
229 for (int nt = 0; nt <= p.N; ++nt)
230 for (int h = 0; h < p.K[t]; ++h) m.row_add_int(x.p1(i, k, t, nt, h), -1);
231 m.emit_eq_int(0);
232 }
233 }
234 }
235}
236
237/** UTLC: IT(i,k) = sum over nt,h of p1c(i,k,t,nt,h), one row per witness t. */
238template <class T>
239void p1_utlc(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
240 for (int i = 0; i < p.M; ++i) {
241 for (int k = 0; k < p.K[i]; ++k) {
242 for (int t = 0; t < p.M; ++t) {
243 m.row_add_int(x.IT(i, k), 1);
244 for (int nt = 0; nt <= p.N; ++nt)
245 for (int h = 0; h < p.K[t]; ++h) m.row_add_int(x.p1c(i, k, t, nt, h), -1);
246 m.emit_eq_int(0);
247 }
248 }
249 }
250}
251
252/** QLEN: Q(i,k) = sum over ni of ni * p1(i,k,i,ni,k). */
253template <class T>
254void p1_qlen(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
255 for (int i = 0; i < p.M; ++i) {
256 for (int k = 0; k < p.K[i]; ++k) {
257 m.row_add_int(x.Q(i, k), 1);
258 for (int ni = 0; ni <= p.N; ++ni) m.row_add_int(x.p1(i, k, i, ni, k), -ni);
259 m.emit_eq_int(0);
260 }
261 }
262}
263
264/**
265 * CLEN: C(j,k,i) = sum over ni,h of ni * p1(j,k,i,ni,h).
266 *
267 * Without it CEQU defines C only on the diagonal and every upper bound on C for
268 * i != j is vacuous.
269 */
270template <class T>
271void p1_clen(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
272 for (int j = 0; j < p.M; ++j) {
273 for (int k = 0; k < p.K[j]; ++k) {
274 for (int i = 0; i < p.M; ++i) {
275 m.row_add_int(x.C(j, k, i), 1);
276 for (int ni = 0; ni <= p.N; ++ni)
277 for (int h = 0; h < p.K[i]; ++h) m.row_add_int(x.p1(j, k, i, ni, h), -ni);
278 m.emit_eq_int(0);
279 }
280 }
281 }
282}
283
284/** ONE: each station is busy in some phase or idle, with probability one. */
285template <class T>
286void p1_one(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
287 for (int j = 0; j < p.M; ++j) {
288 for (int k = 0; k < p.K[j]; ++k) {
289 m.row_add_int(x.U(j, k), 1);
290 m.row_add_int(x.IT(j, k), 1);
291 }
292 m.emit_eq_int(1);
293 }
294}
295
296/** POPC: the mean lengths carry the whole closed population. */
297template <class T>
298void p1_popc(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
299 for (int i = 0; i < p.M; ++i)
300 for (int k = 0; k < p.K[i]; ++k) m.row_add_int(x.Q(i, k), 1);
301 m.emit_eq_int(p.N);
302}
303
304/**
305 * SRVB: phase balance of the marginal, sum{j,h} q(i,j,k,h) U(i,k) equals
306 * sum{j,h} q(i,j,h,k) U(i,h) (AMPL THM1).
307 *
308 * This is the only family that reads the transition rates into the U variables;
309 * without it the phase split of each utilization is free. A single-phase
310 * station gives an identically zero row and the references skip it rather than
311 * emitting one, so keep the skip and the row counts match.
312 */
313template <class T>
314void p1_srvb(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
315 for (int i = 0; i < p.M; ++i) {
316 if (p.K[i] < 2) continue;
317 for (int k = 0; k < p.K[i]; ++k) {
318 for (int j = 0; j < p.M; ++j) {
319 for (int h = 0; h < p.K[i]; ++h) {
320 m.row_add(x.U(i, k), qr_rate(p, i, j, k, h));
321 m.row_add(x.U(i, h), T(-qr_rate(p, i, j, h, k)));
322 }
323 }
324 m.emit_eq_int(0);
325 }
326 }
327}
328
329/** UUB1: a station is busy in at most one phase at a time. */
330template <class T>
331void p1_uub1(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
332 for (int i = 0; i < p.M; ++i) {
333 for (int k = 0; k < p.K[i]; ++k) m.row_add_int(x.U(i, k), 1);
334 m.emit_le_int(1);
335 }
336}
337
338/** QUB1: Q(j,k) <= N U(j,k). An idle phase holds no jobs. */
339template <class T>
340void p1_qub1(const MapqnParams<T>& p, const MapqnP1Index& x, lp::LpModel<T>& m) {
341 for (int j = 0; j < p.M; ++j) {
342 for (int k = 0; k < p.K[j]; ++k) {
343 m.row_add_int(x.Q(j, k), 1);
344 m.row_add_int(x.U(j, k), -p.N);
345 m.emit_le_int(0);
346 }
347 }
348}
349
350/** Shared argument validation for both p1-level entry points. */
351template <class T>
352void p1_check_objective(const MapqnParams<T>& p, int objective_queue, int objective_phase) {
353 if (p.N < 1) throw InputError("mapqn: N must be at least 1");
354 if (objective_queue < 0 || objective_queue >= p.M)
355 throw InputError("mapqn: objective_queue out of range");
356 if (objective_phase < 0 || objective_phase >= p.K[objective_queue])
357 throw InputError("mapqn: objective_phase out of range");
358}
359
360} // namespace detail
361
362} // namespace mapqn
363} // namespace line
364
365#endif // LINE_API_MAPQN_MAPQN_P1_COMMON_H
InputError(const std::string &what)
Definition error.h:39
Model parameters and variable indexing shared by the mapqn QR bounds.
Number-type abstraction for the templated API port.
Templated primal simplex with Bland's rule.
std::size_t Q(int i, int k) const
std::size_t p1c(int j, int k, int i, int ni, int h) const
std::size_t half(int i, int ni, int h) const
Flat (queue, population, phase) half-index, range block.
std::size_t block
(N+1) * sumK, the pairwise half-index range
std::size_t IT(int i, int k) const
MapqnP1Index(int m, int n, const std::vector< int > &k, bool with_p2)
std::size_t C(int j, int k, int i) const
std::size_t sumK
sum_i K(i)
std::vector< int > cumK
cumK[i] = sum_{i' < i} K(i')
std::size_t p1(int j, int k, int i, int ni, int h) const
std::size_t U(int i, int k) const
std::size_t num_vars() const
std::size_t pk(int i, int k) const
Flat (queue, phase) index, range sumK.
std::size_t p2(int j, int nj, int k, int i, int ni, int h) const