LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_codes_matrices.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_FJ_FJ_CODES_MATRICES_H
6#define LINE_API_FJ_FJ_CODES_MATRICES_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * The state-space construction of FJ_codes, the fork-join response-time-tail
12 * approximation of Z. Qiu, J. F. Perez and P. Harrison, "Beyond the Mean in
13 * Fork-Join Queues: Efficient Approximation for Response-Time Tails" (IFIP
14 * Performance 2015). Third-party, BSD-3-Clause, Copyright 2015 Imperial College
15 * London; see THIRD-PARTY-NOTICES.md and
16 * `python/line_solver/lib/thirdparty/fj/LICENSE.txt`.
17 *
18 * Port of `build_index.m`, `vectmatch.m`, `build_Service_h.m`, `build_SA.m`,
19 * `generateService.m`, `constructSRK.m` and `constructNotAllBusy.m` from
20 * `matlab/lib/thirdparty/FJ_codes`. The solve layer is `fj_codes.h`.
21 *
22 * THE STATE SPACE. The algorithm analyses the TWO-node fork-join queue exactly
23 * (Section 4 of the paper) and interpolates to K nodes (Section 6). Its phase
24 * process tracks, for the job at the head of the two branches, the service
25 * phase of the subtask in the LONGER queue and of the subtask in the SHORTER
26 * one, together with c, the difference in queue length between the two
27 * branches, truncated at C. A phase is therefore a pair of unit count vectors
28 * `[e_long, e_short]` of length m each, and the level is c in 0..C; `S` is the
29 * generator of phase changes at constant c, `A_jump` collects the transitions
30 * on which the head job leaves and the next one enters.
31 *
32 * WHY COUNT VECTORS AND NOT PHASE INDICES. The reference indexes every phase by
33 * a length-2m vector of counts summing to two (one per branch) and finds the
34 * target of a transition with a linear search, `vectmatch`. For the two-node
35 * queue this is a bijection with the pair (i, j), and a direct index would be
36 * faster. It is reproduced because the transition builders multiply by
37 * `countvect(i)`, the MULTIPLICITY of the source phase, and that factor is
38 * where a general-K generalisation of the same code would differ from a pair
39 * encoding; dropping the representation would silently drop the factor.
40 *
41 * ARITHMETIC. `double` only, in step with the solve layer: the T matrix needs
42 * an ordered real Schur factorization and the two Sylvester equations need
43 * Bartels-Stewart, all LAPACK. The construction itself is exact rational
44 * arithmetic on the descriptors, but a `Matrix<T>` construction feeding a
45 * double-only solve would only move the conversion one call inwards.
46 */
47
48#include <cstddef>
49#include <vector>
50
53#include "line/util/error.h"
54#include "line/util/linalg.h"
55#include "line/util/matrix.h"
56
57namespace line {
58namespace fj {
59
60/** `build_Service_h.m`: the two-subtask phase process of one fork-join job. */
62 Matrix<double> service_phases; ///< (m^2) x (2m) count vectors, [long, short]
63 std::vector<double> beta; ///< kron(tau_st, tau_st), length m^2
64 Matrix<double> S; ///< kronsum(ST, ST), (m^2) x (m^2)
65};
66
67/** `build_SA.m`: the level-constant generator and the head-of-line jump. */
68struct FjCodesSA {
69 Matrix<double> S; ///< no job completes, ((C+1) m^2) square
70 Matrix<double> A_jump; ///< a job completes and the next enters service
71};
72
73/** `generateService.m`: the service process seen by a tagged job. */
75 Matrix<double> T; ///< (newdim + dim_notbusy) square
76 std::size_t newdim = 0; ///< the all-busy part
77 std::size_t dim_notbusy = 0; ///< the not-all-busy part
78};
79
80/** `constructSRK.m`: the extended generator and the busy/idle projectors. */
81struct FjCodesSRK {
82 Matrix<double> Se; ///< busy and not-busy phases together
83 Matrix<double> Sestar; ///< the busy-to-not-busy block of Se, in place
84 Matrix<double> R0; ///< not-busy to busy, on an arrival
85 Matrix<double> Ke; ///< newdim x (newdim + dim_notbusy), the busy rows
86 Matrix<double> Kc; ///< (newdim + dim_notbusy) x newdim, the busy columns
87};
88
89/**
90 * Port of `build_index.m`: the compositions of `cr` into `m` non-negative
91 * parts, one per row, in the reference's own order.
92 *
93 * FJ_codes calls this with cr = 1 only, where it is the identity matrix and the
94 * rows are the m single-subtask phases. The general case is ported because it
95 * is what the reference computes, and because the row ORDER is what
96 * `vectmatch` resolves against: any other enumeration of the same set would
97 * permute every block of every matrix below.
98 */
99inline Matrix<double> fj_build_index(std::size_t m, std::size_t cr) {
100 if (m == 0) throw InputError("fj_build_index: m must be positive");
101 // nchoosek(cr + m - 1, cr), in exact integer arithmetic
102 std::size_t total = 1;
103 for (std::size_t k = 1; k <= cr; ++k) total = total * (cr + m - k) / k;
104 Matrix<double> idx(total, m, 0.0);
105 idx(0, 0) = static_cast<double>(cr);
106 for (std::size_t row = 1; row < total; ++row) {
107 std::size_t k = m; // MATLAB find(..., 1) on an all-zero row yields empty
108 for (std::size_t j = 0; j < m; ++j)
109 if (idx(row - 1, j) > 0.0) {
110 k = j;
111 break;
112 }
113 if (k + 1 < m) { // the reference's `k < m`, with k 1-based there
114 for (std::size_t j = 0; j < m; ++j) idx(row, j) = idx(row - 1, j);
115 idx(row, k + 1) += 1.0;
116 idx(row, 0) = idx(row, k) - 1.0;
117 for (std::size_t j = 1; j <= k; ++j) idx(row, j) = 0.0;
118 }
119 }
120 return idx;
121}
122
123/**
124 * Port of `vectmatch.m`: the row of `matrix` equal to `row`.
125 *
126 * Returns a 0-based index. The reference leaves its output unassigned when
127 * there is no match, which MATLAB reports as an error one frame up; the same
128 * condition is a construction bug here and is named as one.
129 */
130inline std::size_t fj_vectmatch(const std::vector<double>& row, const Matrix<double>& matrix) {
131 if (row.size() != matrix.cols())
132 throw InputError("fj_vectmatch: the probe and the table have different widths");
133 for (std::size_t i = 0; i < matrix.rows(); ++i) {
134 bool hit = true;
135 for (std::size_t j = 0; j < matrix.cols(); ++j)
136 if (matrix(i, j) != row[j]) {
137 hit = false;
138 break;
139 }
140 if (hit) return i;
141 }
142 throw NumericError("fj_vectmatch: the target phase is not in the phase table, so the "
143 "fork-join state space is not closed under its own transitions");
144}
145
146/** The reference's `A = -sum(ST, 2) * tau_st`: complete, then restart. */
148 const std::size_t m = service.tau_st.size();
149 Matrix<double> A(m, m, 0.0);
150 for (std::size_t i = 0; i < m; ++i) {
151 double s = 0.0;
152 for (std::size_t j = 0; j < m; ++j) s += service.ST(i, j);
153 for (std::size_t j = 0; j < m; ++j) A(i, j) = -s * service.tau_st[j];
154 }
155 return A;
156}
157
158/** Port of `build_Service_h.m`. */
160 const std::size_t m = service.tau_st.size();
161 if (m == 0) throw InputError("fj_build_service_h: the service process has no phases");
162 const Matrix<double> single = fj_build_index(m, 1);
163
165 h.service_phases = Matrix<double>(m * m, 2 * m, 0.0);
166 std::size_t k = 0;
167 for (std::size_t i = 0; i < m; ++i)
168 for (std::size_t j = 0; j < m; ++j) {
169 for (std::size_t c = 0; c < m; ++c) {
170 h.service_phases(k, c) = single(i, c); // longest queue
171 h.service_phases(k, m + c) = single(j, c); // shortest queue
172 }
173 ++k;
174 }
175 h.beta.assign(m * m, 0.0);
176 for (std::size_t i = 0; i < m; ++i)
177 for (std::size_t j = 0; j < m; ++j)
178 h.beta[i * m + j] = service.tau_st[i] * service.tau_st[j];
179 h.S = mam::krons(service.ST, service.ST);
180 return h;
181}
182
183/**
184 * Port of `build_SA.m`.
185 *
186 * `C` is the truncation of the queue-length difference and must be at least
187 * one: the reference indexes `A_jump((C-1)*dim+1 : C*dim)` unconditionally, so
188 * C = 0 is an out-of-range write there rather than a degenerate model here.
189 */
190inline FjCodesSA fj_build_sa(const FjDist<double>& service, const FjCodesServiceH& h,
191 std::size_t C) {
192 if (C < 1)
193 throw InputError("fj_build_sa: the FJ_codes truncation C must be at least 1 (the "
194 "reference indexes the c = C - 1 block unconditionally)");
195 const std::size_t dim = h.beta.size();
196 const std::size_t m = service.tau_st.size();
197 const std::size_t dim_C = C + 1;
198 const std::size_t newdim = dim_C * dim;
199
200 FjCodesSA out;
201 out.S = Matrix<double>(newdim, newdim, 0.0);
202 out.A_jump = Matrix<double>(newdim, newdim, 0.0);
203
204 for (std::size_t b = 0; b < dim_C; ++b)
205 for (std::size_t i = 0; i < dim; ++i)
206 for (std::size_t j = 0; j < dim; ++j) out.S(b * dim + i, b * dim + j) = h.S(i, j);
207
208 const Matrix<double> A = fj_restart_matrix(service);
209
210 // The job in the LONGER queue completes: c drops by one.
211 Matrix<double> S_Cminus1(dim, dim, 0.0);
212 for (std::size_t row = 0; row < dim; ++row)
213 for (std::size_t i = 0; i < m; ++i) {
214 const double cnt = h.service_phases(row, i);
215 if (cnt <= 0.0) continue;
216 for (std::size_t j = 0; j < m; ++j) {
217 std::vector<double> to(2 * m);
218 for (std::size_t c = 0; c < 2 * m; ++c) to[c] = h.service_phases(row, c);
219 to[i] -= 1.0;
220 to[j] += 1.0;
221 S_Cminus1(row, fj_vectmatch(to, h.service_phases)) += cnt * A(i, j);
222 }
223 }
224 for (std::size_t b = 0; b + 1 < dim_C; ++b)
225 for (std::size_t i = 0; i < dim; ++i)
226 for (std::size_t j = 0; j < dim; ++j)
227 out.S(b * dim + i, (b + 1) * dim + j) = S_Cminus1(i, j);
228
229 // The job in the SHORTER queue completes: c grows by one. The reference
230 // does NOT weight this one by countvect(i), unlike the two around it.
231 Matrix<double> A_Cplus1(dim, dim, 0.0);
232 for (std::size_t row = 0; row < dim; ++row)
233 for (std::size_t i = m; i < 2 * m; ++i) {
234 if (h.service_phases(row, i) <= 0.0) continue;
235 for (std::size_t j = m; j < 2 * m; ++j) {
236 std::vector<double> to(2 * m);
237 for (std::size_t c = 0; c < 2 * m; ++c) to[c] = h.service_phases(row, c);
238 to[i] -= 1.0;
239 to[j] += 1.0;
240 A_Cplus1(row, fj_vectmatch(to, h.service_phases)) += A(i - m, j - m);
241 }
242 }
243 for (std::size_t b = 1; b + 1 < dim_C; ++b)
244 for (std::size_t i = 0; i < dim; ++i)
245 for (std::size_t j = 0; j < dim; ++j)
246 out.A_jump(b * dim + i, (b - 1) * dim + j) = A_Cplus1(i, j);
247 for (std::size_t i = 0; i < dim; ++i)
248 for (std::size_t j = 0; j < dim; ++j) out.A_jump(i, j) = A_Cplus1(i, j);
249
250 // Both queues have the same length: whichever subtask does not finish
251 // becomes the longer one.
252 Matrix<double> A_last(dim, dim, 0.0);
253 for (std::size_t row = 0; row < dim; ++row)
254 for (std::size_t k = 0; k < 2; ++k)
255 for (std::size_t i = 0; i < m; ++i) {
256 const double cnt = h.service_phases(row, k * m + i);
257 if (cnt <= 0.0) continue;
258 std::vector<double> other(m);
259 for (std::size_t c = 0; c < m; ++c)
260 other[c] = h.service_phases(row, (1 - k) * m + c);
261 for (std::size_t j = 0; j < m; ++j) {
262 std::vector<double> to(2 * m, 0.0);
263 for (std::size_t c = 0; c < m; ++c) to[c] = other[c];
264 to[m + j] = 1.0;
265 A_last(row, fj_vectmatch(to, h.service_phases)) += cnt * A(i, j);
266 }
267 }
268 for (std::size_t i = 0; i < dim; ++i)
269 for (std::size_t j = 0; j < dim; ++j)
270 out.A_jump(C * dim + i, (C - 1) * dim + j) = A_last(i, j);
271
272 return out;
273}
274
275/**
276 * The busy-to-not-busy blocks `S_long` and `S_last`, shared verbatim by
277 * `generateService.m` and `constructSRK.m`.
278 *
279 * S_long: the subtask in the SHORTER queue completes while c > 0, so the
280 * remaining subtask is the one that was in the longer queue.
281 * S_last: the two queues have equal length, so either subtask may be the one
282 * that completes and the other one is what remains.
283 */
284inline void fj_busy_to_idle(const FjDist<double>& service, const FjCodesServiceH& h,
285 const Matrix<double>& idle_phases, Matrix<double>& S_long,
286 Matrix<double>& S_last) {
287 const std::size_t dim = h.beta.size();
288 const std::size_t m = service.tau_st.size();
289 const std::size_t dim_NB = idle_phases.rows();
290 S_long = Matrix<double>(dim, dim_NB, 0.0);
291 S_last = Matrix<double>(dim, dim_NB, 0.0);
292
293 for (std::size_t row = 0; row < dim; ++row)
294 for (std::size_t i = m; i < 2 * m; ++i) {
295 const double cnt = h.service_phases(row, i);
296 if (cnt <= 0.0) continue;
297 std::vector<double> to(m);
298 for (std::size_t c = 0; c < m; ++c) to[c] = h.service_phases(row, c);
299 S_long(row, fj_vectmatch(to, idle_phases)) += cnt * service.St[i - m];
300 }
301
302 for (std::size_t row = 0; row < dim; ++row)
303 for (std::size_t k = 0; k < 2; ++k)
304 for (std::size_t i = k * m; i < (k + 1) * m; ++i) {
305 const double cnt = h.service_phases(row, i);
306 if (cnt <= 0.0) continue;
307 std::vector<double> to(m);
308 for (std::size_t c = 0; c < m; ++c)
309 to[c] = h.service_phases(row, (1 - k) * m + c);
310 S_last(row, fj_vectmatch(to, idle_phases)) += cnt * service.St[i - k * m];
311 }
312}
313
314/** Port of `generateService.m`. */
316 const FjCodesServiceH& h, std::size_t C,
317 const Matrix<double>& S) {
318 const std::size_t dim = h.beta.size();
319 const std::size_t m = service.tau_st.size();
320 const Matrix<double> idle = fj_build_index(m, 1);
321 const std::size_t dim_NB = idle.rows();
322 const std::size_t dim_C = C + 1;
323
325 out.newdim = dim_C * dim;
326 out.dim_notbusy = dim_C * dim_NB;
327 const std::size_t n = out.newdim + out.dim_notbusy;
328 out.T = Matrix<double>(n, n, 0.0);
329
330 std::vector<double> t(n, 0.0);
331 for (std::size_t i = 0; i < dim_NB; ++i) t[n - dim_NB + i] = service.St[i];
332
333 for (std::size_t i = 0; i < out.newdim; ++i)
334 for (std::size_t j = 0; j < out.newdim; ++j) out.T(i, j) = S(i, j);
335
336 Matrix<double> S_long, S_last;
337 fj_busy_to_idle(service, h, idle, S_long, S_last);
338
339 for (std::size_t b = 0; b + 1 < dim_C; ++b)
340 for (std::size_t i = 0; i < dim; ++i)
341 for (std::size_t j = 0; j < dim_NB; ++j)
342 out.T(b * dim + i, out.newdim + b * dim_NB + j) = S_long(i, j);
343 for (std::size_t i = 0; i < dim; ++i)
344 for (std::size_t j = 0; j < dim_NB; ++j)
345 out.T((dim_C - 1) * dim + i, out.newdim + (dim_C - 1) * dim_NB + j) = S_last(i, j);
346
347 for (std::size_t b = 0; b < dim_C; ++b)
348 for (std::size_t i = 0; i < dim_NB; ++i)
349 for (std::size_t j = 0; j < dim_NB; ++j)
350 out.T(out.newdim + b * dim_NB + i, out.newdim + b * dim_NB + j) = service.ST(i, j);
351
352 const Matrix<double> A = fj_restart_matrix(service);
353 for (std::size_t b = 0; b + 1 < dim_C; ++b)
354 for (std::size_t i = 0; i < dim_NB; ++i)
355 for (std::size_t j = 0; j < dim_NB; ++j)
356 out.T(out.newdim + b * dim_NB + i, out.newdim + (b + 1) * dim_NB + j) = A(i, j);
357
358 for (std::size_t row = out.newdim; row < n; ++row) {
359 out.T(row, row) = 0.0;
360 double s = 0.0;
361 for (std::size_t j = 0; j < n; ++j) s += out.T(row, j);
362 out.T(row, row) = -s - t[row];
363 }
364 return out;
365}
366
367/** Port of `constructNotAllBusy.m`. */
368inline Matrix<double> fj_construct_not_all_busy(std::size_t C, const FjDist<double>& service,
369 const FjCodesServiceH& h) {
370 (void)h;
371 if (C < 1)
372 throw InputError("fj_construct_not_all_busy: the FJ_codes truncation C must be at least 1");
373 const std::size_t m = service.tau_st.size();
374 const std::size_t dim_NB = fj_build_index(m, 1).rows();
375 const std::size_t dim_C = C + 1;
376 const std::size_t n = (dim_C - 1) * dim_NB + 1;
377 Matrix<double> out(n, n, 0.0);
378
379 for (std::size_t b = 0; b + 1 < dim_C; ++b)
380 for (std::size_t i = 0; i < dim_NB; ++i)
381 for (std::size_t j = 0; j < dim_NB; ++j)
382 out(b * dim_NB + i, b * dim_NB + j) = service.ST(i, j);
383
384 const Matrix<double> A = fj_restart_matrix(service);
385 for (std::size_t b = 0; b + 2 < dim_C; ++b)
386 for (std::size_t i = 0; i < dim_NB; ++i)
387 for (std::size_t j = 0; j < dim_NB; ++j)
388 out(b * dim_NB + i, (b + 1) * dim_NB + j) = A(i, j);
389
390 // The last column is the empty state: the one remaining subtask completes.
391 for (std::size_t i = 0; i < dim_NB; ++i)
392 out((dim_C - 2) * dim_NB + i, n - 1) = service.St[i];
393
394 for (std::size_t row = 0; row < n; ++row) {
395 out(row, row) = 0.0;
396 double s = 0.0;
397 for (std::size_t j = 0; j < n; ++j) s += out(row, j);
398 out(row, row) = -s;
399 }
400 return out;
401}
402
403/** Port of `constructSRK.m`. */
404inline FjCodesSRK fj_construct_srk(std::size_t C, const FjDist<double>& service,
405 const FjCodesServiceH& h, const Matrix<double>& S) {
406 if (C < 1) throw InputError("fj_construct_srk: the FJ_codes truncation C must be at least 1");
407 const std::size_t dim = h.beta.size();
408 const std::size_t m = service.tau_st.size();
409 const Matrix<double> idle = fj_build_index(m, 1);
410 const std::size_t dim_NB = idle.rows();
411 const std::size_t dim_C = C + 1;
412 const std::size_t newdim = dim_C * dim;
413 const std::size_t dim_notbusy = (dim_C - 1) * dim_NB + 1;
414 const std::size_t n = newdim + dim_notbusy;
415
416 FjCodesSRK out;
417 out.Se = Matrix<double>(n, n, 0.0);
418 out.Sestar = Matrix<double>(n, n, 0.0);
419 for (std::size_t i = 0; i < newdim; ++i)
420 for (std::size_t j = 0; j < newdim; ++j) out.Se(i, j) = S(i, j);
421
422 Matrix<double> S_long, S_last;
423 fj_busy_to_idle(service, h, idle, S_long, S_last);
424
425 // Block row 0 lands on idle block 0; block row b >= 1 lands on idle block
426 // b - 1. The offset by one is the reference's, and is what distinguishes
427 // constructSRK's idle space (which carries the single empty state at the
428 // end) from generateService's.
429 for (std::size_t i = 0; i < dim; ++i)
430 for (std::size_t j = 0; j < dim_NB; ++j) out.Se(i, newdim + j) = S_long(i, j);
431 for (std::size_t b = 1; b + 1 < dim_C; ++b)
432 for (std::size_t i = 0; i < dim; ++i)
433 for (std::size_t j = 0; j < dim_NB; ++j)
434 out.Se(b * dim + i, newdim + (b - 1) * dim_NB + j) = S_long(i, j);
435 for (std::size_t i = 0; i < dim; ++i)
436 for (std::size_t j = 0; j < dim_NB; ++j)
437 out.Se((dim_C - 1) * dim + i, newdim + (dim_C - 2) * dim_NB + j) = S_last(i, j);
438
439 for (std::size_t i = 0; i < newdim; ++i)
440 for (std::size_t j = newdim; j < n; ++j) out.Sestar(i, j) = out.Se(i, j);
441
442 for (std::size_t b = 0; b + 1 < dim_C; ++b)
443 for (std::size_t i = 0; i < dim_NB; ++i)
444 for (std::size_t j = 0; j < dim_NB; ++j)
445 out.Se(newdim + b * dim_NB + i, newdim + b * dim_NB + j) = service.ST(i, j);
446
447 const Matrix<double> A = fj_restart_matrix(service);
448 for (std::size_t b = 0; b + 2 < dim_C; ++b)
449 for (std::size_t i = 0; i < dim_NB; ++i)
450 for (std::size_t j = 0; j < dim_NB; ++j)
451 out.Se(newdim + b * dim_NB + i, newdim + (b + 1) * dim_NB + j) = A(i, j);
452
453 for (std::size_t i = 0; i < dim_NB; ++i)
454 out.Se(newdim + (dim_C - 2) * dim_NB + i, n - 1) = service.St[i];
455
456 for (std::size_t row = newdim; row < n; ++row) {
457 out.Se(row, row) = 0.0;
458 double s = 0.0;
459 for (std::size_t j = 0; j < n; ++j) s += out.Se(row, j);
460 out.Se(row, row) = -s;
461 }
462
463 // R0: an arrival during a not-all-busy period restarts the second branch,
464 // so the system re-enters the all-busy phase with the same long-queue
465 // phase and a fresh short-queue phase drawn from tau_st.
466 out.R0 = Matrix<double>(n, n, 0.0);
467 Matrix<double> R_NB(dim_NB, dim, 0.0);
468 for (std::size_t row = 0; row < dim_NB; ++row)
469 for (std::size_t i = 0; i < m; ++i) {
470 std::vector<double> to(2 * m, 0.0);
471 for (std::size_t c = 0; c < m; ++c) to[c] = idle(row, c);
472 to[m + i] = 1.0;
473 R_NB(row, fj_vectmatch(to, h.service_phases)) += service.tau_st[i];
474 }
475 for (std::size_t b = 0; b + 1 < dim_C; ++b)
476 for (std::size_t i = 0; i < dim_NB; ++i)
477 for (std::size_t j = 0; j < dim; ++j)
478 out.R0(newdim + b * dim_NB + i, b * dim + j) = R_NB(i, j);
479 for (std::size_t j = 0; j < dim; ++j) out.R0(n - 1, newdim - dim + j) = h.beta[j];
480
481 out.Ke = Matrix<double>(newdim, n, 0.0);
482 for (std::size_t i = 0; i < newdim; ++i) out.Ke(i, i) = 1.0;
483 out.Kc = Matrix<double>(n, newdim, 0.0);
484 for (std::size_t i = 0; i < newdim; ++i) out.Kc(i, i) = 1.0;
485 return out;
486}
487
488} // namespace fj
489} // namespace line
490
491#endif // LINE_API_FJ_FJ_CODES_MATRICES_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Conversion of a LINE MAP into the arrival or service descriptor of the fork-join response-time-tail a...
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
std::size_t fj_vectmatch(const std::vector< double > &row, const Matrix< double > &matrix)
Port of vectmatch.m: the row of matrix equal to row.
Matrix< double > fj_restart_matrix(const FjDist< double > &service)
The reference's A = -sum(ST, 2) * tau_st: complete, then restart.
FjCodesSRK fj_construct_srk(std::size_t C, const FjDist< double > &service, const FjCodesServiceH &h, const Matrix< double > &S)
Port of constructSRK.m.
FjCodesServiceH fj_build_service_h(const FjDist< double > &service)
Port of build_Service_h.m.
FjCodesGenService fj_generate_service(const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C, const Matrix< double > &S)
Port of generateService.m.
void fj_busy_to_idle(const FjDist< double > &service, const FjCodesServiceH &h, const Matrix< double > &idle_phases, Matrix< double > &S_long, Matrix< double > &S_last)
The busy-to-not-busy blocks S_long and S_last, shared verbatim by generateService....
Matrix< double > fj_construct_not_all_busy(std::size_t C, const FjDist< double > &service, const FjCodesServiceH &h)
Port of constructNotAllBusy.m.
FjCodesSA fj_build_sa(const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C)
Port of build_SA.m.
Matrix< double > fj_build_index(std::size_t m, std::size_t cr)
Port of build_index.m: the compositions of cr into m non-negative parts, one per row,...
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
Definition mmap_lambda.h:71
generateService.m: the service process seen by a tagged job.
Matrix< double > T
(newdim + dim_notbusy) square
std::size_t newdim
the all-busy part
std::size_t dim_notbusy
the not-all-busy part
build_SA.m: the level-constant generator and the head-of-line jump.
Matrix< double > A_jump
a job completes and the next enters service
Matrix< double > S
no job completes, ((C+1) m^2) square
constructSRK.m: the extended generator and the busy/idle projectors.
Matrix< double > R0
not-busy to busy, on an arrival
Matrix< double > Se
busy and not-busy phases together
Matrix< double > Sestar
the busy-to-not-busy block of Se, in place
Matrix< double > Ke
newdim x (newdim + dim_notbusy), the busy rows
Matrix< double > Kc
(newdim + dim_notbusy) x newdim, the busy columns
build_Service_h.m: the two-subtask phase process of one fork-join job.
Matrix< double > service_phases
(m^2) x (2m) count vectors, [long, short]
std::vector< double > beta
kron(tau_st, tau_st), length m^2
Matrix< double > S
kronsum(ST, ST), (m^2) x (m^2)
Descriptor of an arrival or a service process.
Definition fj_dist2fj.h:76
std::vector< T > tau_st
Definition fj_dist2fj.h:87
std::vector< T > St
Definition fj_dist2fj.h:86
Matrix< T > ST
Definition fj_dist2fj.h:85