LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fj_tsm_capacity.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_TSM_CAPACITY_H
6#define LINE_API_FJ_TSM_CAPACITY_H
7
8/**
9 * @file
10 * @ingroup api_fj
11 * Saturation throughput of the team service model.
12 *
13 * Templated port of matlab/src/api/fj/fj_tsm_capacity.m.
14 *
15 * A class-k job seizes r(k) of the s servers at once, holds them for a mean
16 * x(k), and releases them all together. The apparent saturation rate is
17 *
18 * Lambda_max = s / sum_k f(k) r(k) x(k),
19 *
20 * attainable only when the scheduler can pack jobs into execution states that
21 * leave no server idle. The attainable capacity is the largest arrival rate for
22 * which some mixture p over the feasible execution states balances every class,
23 *
24 * maximise Lambda s.t. sum_j p_j n(j,k)/x(k) = Lambda f(k),
25 * sum_j p_j = 1, p >= 0,
26 *
27 * over the multisets of jobs whose total server demand is at most s. The
28 * optimum equals Lambda_max exactly when every state carrying positive
29 * probability is full capacity.
30 *
31 * For the two-server two-class case with r = (1,2), strict first come first
32 * served cannot pack at all and reaches only
33 *
34 * lambda_FCFS = 2 mu1 mu2 / (f1^2 mu2 + 2 f2^2 mu1 + 2 f1 f2 (mu1+mu2)).
35 */
36
37#include <cstddef>
38#include <vector>
39
41#include "line/num/number.h"
42#include "line/util/error.h"
43#include "line/util/simplex.h"
44
45namespace line {
46namespace fj {
47
48/** [Lmax, Llp, Lfcfs, states, prob] of fj_tsm_capacity. */
49template <class T>
52 T Llp;
55 std::vector<std::vector<unsigned> > states;
56 std::vector<T> prob;
57};
58
59namespace detail {
60
61/** Every multiset of jobs whose total server demand is at most s, minus the empty one. */
62inline void tsm_states(const std::vector<unsigned>& r, unsigned left, std::size_t k,
63 std::vector<unsigned>& stack,
64 std::vector<std::vector<unsigned> >& out) {
65 if (k == r.size()) {
66 for (std::size_t i = 0; i < stack.size(); ++i)
67 if (stack[i] > 0) { out.push_back(stack); return; }
68 return;
69 }
70 const unsigned nmax = left / r[k];
71 for (unsigned n = 0; n <= nmax; ++n) {
72 stack[k] = n;
73 tsm_states(r, left - n * r[k], k + 1, stack, out);
74 }
75 stack[k] = 0;
76}
77
78} // namespace detail
79
80/**
81 * @brief Saturation throughput of the team service model.
82 *
83 * @param s number of servers
84 * @param f class frequencies in the arrival stream, summing to one
85 * @param r per-class server requirements, integers in 1..s
86 * @param x per-class mean service times, all positive
87 * @return the apparent and attainable capacities with the optimal state mixture
88 */
89template <class T>
90FJTsmCapacityResult<T> fj_tsm_capacity(unsigned s, const std::vector<T>& f,
91 const std::vector<unsigned>& r, const std::vector<T>& x) {
92 const std::size_t K = f.size();
93 if (s < 1) throw InputError("fj_tsm_capacity: s must be a positive integer");
94 if (r.size() != K || x.size() != K)
95 throw InputError("fj_tsm_capacity: f, r and x must have the same length");
96 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
97 T tot = zero;
98 for (std::size_t k = 0; k < K; ++k) {
99 if (f[k] < zero) throw InputError("fj_tsm_capacity: the class frequencies must be non-negative");
100 if (r[k] < 1 || r[k] > s)
101 throw InputError("fj_tsm_capacity: the server requirements must lie in 1..s");
102 if (!(x[k] > zero)) throw InputError("fj_tsm_capacity: the mean service times must be positive");
103 tot += f[k];
104 }
105 if (!(tot - one < num_traits<T>::from_double(1e-9)) ||
106 !(one - tot < num_traits<T>::from_double(1e-9)))
107 throw InputError("fj_tsm_capacity: the class frequencies must sum to one");
108
110 T den = zero;
111 for (std::size_t k = 0; k < K; ++k)
112 den += f[k] * num_traits<T>::from_int(static_cast<long>(r[k])) * x[k];
113 out.Lmax = num_traits<T>::from_int(static_cast<long>(s)) / den;
114
115 std::vector<unsigned> stack(K, 0);
116 detail::tsm_states(r, s, 0, stack, out.states);
117 const std::size_t ns = out.states.size();
118 if (ns == 0) throw NumericError("fj_tsm_capacity: no feasible execution state");
119
120 // Variables [p_1..p_ns, Lambda], maximise Lambda
121 lp::LpModel<T> lpm(ns + 1);
122 lpm.set_maximize(true);
123 lpm.set_cost(ns, one);
124 for (std::size_t k = 0; k < K; ++k) {
125 if (!(f[k] > zero)) continue;
126 lpm.row_clear();
127 for (std::size_t j = 0; j < ns; ++j)
128 lpm.row_add(j, num_traits<T>::from_int(static_cast<long>(out.states[j][k])) / x[k]);
129 lpm.row_add(ns, -f[k]);
130 lpm.emit(lp::LpSense::EQ, zero);
131 }
132 lpm.row_clear();
133 for (std::size_t j = 0; j < ns; ++j) lpm.row_add(j, one);
134 lpm.emit(lp::LpSense::EQ, one);
135
136 const lp::LpSolution<T> sol = lp::simplex_solve(lpm);
137 if (!sol.ok())
138 throw NumericError(std::string("fj_tsm_capacity: the capacity linear program returned ") +
140 out.Llp = sol.x[ns];
141 out.prob.assign(sol.x.begin(), sol.x.begin() + static_cast<long>(ns));
142
143 // Strict first come first served capacity of the two-server two-class case
144 out.fcfs_available = false;
145 out.Lfcfs = zero;
146 if (s == 2 && K == 2 && ((r[0] == 1 && r[1] == 2) || (r[0] == 2 && r[1] == 1))) {
147 const std::size_t a = (r[0] == 1) ? 0 : 1, b = 1 - a;
148 const T f1 = f[a], f2 = f[b], mu1 = one / x[a], mu2 = one / x[b];
149 const T two = num_traits<T>::from_int(2);
150 out.Lfcfs = two * mu1 * mu2 /
151 (f1 * f1 * mu2 + two * f2 * f2 * mu1 + two * f1 * f2 * (mu1 + mu2));
152 out.fcfs_available = true;
153 }
154 return out;
155}
156
157} // namespace fj
158} // namespace line
159
160#endif // LINE_API_FJ_TSM_CAPACITY_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
void emit(LpSense sense, const T &rhs)
Emit the accumulated row with the given relation and right-hand side.
Definition simplex.h:200
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
void row_clear()
Discard whatever the row accumulator holds.
Definition simplex.h:179
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
The exception types the port throws.
Shared return types and arithmetic helpers for the templated fork-join port.
FJTsmCapacityResult< T > fj_tsm_capacity(unsigned s, const std::vector< T > &f, const std::vector< unsigned > &r, const std::vector< T > &x)
Saturation throughput of the team service model.
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
Number-type abstraction for the templated API port.
Templated primal simplex with Bland's rule.
[Lmax, Llp, Lfcfs, states, prob] of fj_tsm_capacity.
std::vector< std::vector< unsigned > > states
bool ok() const
Definition simplex.h:99
std::vector< T > x
primal solution in the ORIGINAL variable space
Definition simplex.h:96