LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pas_swap2order.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_PFQN_PAS_SWAP2ORDER_H
6#define LINE_API_PFQN_PAS_SWAP2ORDER_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Global placement-order DAG of a closed two-station pass-and-swap tandem.
12 *
13 * Templated port of matlab/src/api/pfqn/pas_swap2order.m. With a non-empty
14 * swap graph the ordered-state chain is reducible (Comte and Dorsman, 2021,
15 * arXiv:2009.12299) and its recurrent class consists of the splits of the
16 * orderings that are the linear extensions of a single placement partial
17 * order. The port enumerates that recurrent class from the all-in-queue-1
18 * state of the minimal single-job-per-class instance, reads the full ordering
19 * c = [l1, reverse(l2)] off every reachable state, and sets H(i, j) = 1 iff i
20 * precedes j in EVERY such ordering. The placement order is a class-level
21 * property, so the result is valid for any population.
22 *
23 * PARTNER FUNCTION. pas_placement takes the H produced here and returns its
24 * transitive closure together with the placeable-next test; the two are meant
25 * to be used together and are tested against each other.
26 *
27 * SERVICE RATES. listRate[m](c) is the total service rate of queue m on the
28 * ordered prefix c, and the marginal rate of the p-th customer is the forward
29 * difference. The reference guards p == 1 explicitly because some rate handles
30 * return a nonzero constant on the empty prefix, and that guard is reproduced:
31 * the empty-prefix rate is zero by definition.
32 *
33 * ARITHMETIC. Only the rate comparison touches the number type, against the
34 * reference's fixed 1e-12 threshold, which is a representable rational. No
35 * transcendental function is used and the header is instantiated at Rational.
36 * The threshold is NOT relaxed to an exact "> 0" test at exact arithmetic:
37 * that would be a different algorithm, admitting transitions the reference
38 * prunes.
39 *
40 * INDEXING. Classes are 1-based, as in MATLAB, everywhere a class appears in
41 * an ordering or in N0. H is (R x R) and 0-based in the C++ sense, so
42 * H(a - 1, b - 1) is MATLAB's H(a, b).
43 */
44
45#include <algorithm>
46#include <cstddef>
47#include <functional>
48#include <set>
49#include <utility>
50#include <vector>
51
52#include "line/num/number.h"
53#include "line/util/error.h"
54#include "line/util/matrix.h"
55
56namespace line {
57namespace pfqn {
58
59/** Total service rate of a queue on an ordered prefix of classes (1-based). */
60template <class T>
61using PasRateFun = std::function<T(const std::vector<int>&)>;
62
63namespace detail {
64
65/**
66 * One pass-and-swap completion at position p of the ordered queue c
67 * (pas_swap_local in the reference).
68 *
69 * The job at p chases the first later job it may swap with, that one chases
70 * the next, and so on; the last job of the chain departs, and every earlier
71 * job of the chain moves up into the slot of the next one, position p being
72 * removed.
73 *
74 * @return the new order and the departing class
75 */
76template <class T>
77std::pair<std::vector<int>, int> pas_swap_local(const std::vector<int>& c, std::size_t p,
78 const Matrix<T>& G) {
79 const T zero = num_traits<T>::from_int(0);
80 const std::size_t n = c.size();
81 std::vector<std::size_t> chain;
82 chain.push_back(p);
83 int moving = c[p - 1];
84 std::size_t cur = p;
85 while (true) {
86 std::size_t q = 0;
87 for (std::size_t j = cur + 1; j <= n; ++j)
88 if (G.rows() != 0 && G(static_cast<std::size_t>(moving) - 1,
89 static_cast<std::size_t>(c[j - 1]) - 1) != zero) {
90 q = j;
91 break;
92 }
93 if (q == 0) break;
94 chain.push_back(q);
95 moving = c[q - 1];
96 cur = q;
97 }
98 const int dep = c[chain.back() - 1];
99 std::vector<int> tmp(c);
100 for (std::size_t i = 0; i + 1 < chain.size(); ++i) tmp[chain[i + 1] - 1] = c[chain[i] - 1];
101 tmp.erase(tmp.begin() + static_cast<std::ptrdiff_t>(chain[0] - 1));
102 return std::make_pair(tmp, dep);
103}
104
105} // namespace detail
106
107/**
108 * Placement-order DAG of a two-station pass-and-swap tandem.
109 *
110 * @param swap one graph, applied to both queues, or one graph per queue;
111 * G(a, b) nonzero means class a chases class b
112 * @param listRate the two ordered service-rate functions, queue 1 then queue 2
113 * @param N0 (R) minimal probing population; empty means one job per class
114 * @return (R x R) H, with H(i, j) = 1 iff class i+1 must precede class j+1
115 */
116template <class T>
117Matrix<T> pas_swap2order(const std::vector<Matrix<T>>& swap,
118 const std::vector<PasRateFun<T>>& listRate,
119 const std::vector<int>& N0 = std::vector<int>()) {
120 const std::size_t M = 2;
121 if (swap.empty()) throw InputError("pas_swap2order: no swap graph");
122 if (listRate.size() != M) throw InputError("pas_swap2order: two rate functions are required");
123 std::vector<Matrix<T>> G(M);
124 if (swap.size() == 1) {
125 G[0] = swap[0];
126 G[1] = swap[0];
127 } else if (swap.size() == M) {
128 G[0] = swap[0];
129 G[1] = swap[1];
130 } else {
131 throw InputError("pas_swap2order: expected one swap graph or one per queue");
132 }
133
134 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
135 std::vector<int> pop(N0);
136 if (pop.empty()) {
137 if (G[0].rows() == 0) throw InputError("pas_swap2order: cannot infer the class count");
138 pop.assign(G[0].rows(), 1);
139 }
140 const std::size_t R = pop.size();
141
142 bool anySwap = false;
143 for (std::size_t m = 0; m < M; ++m)
144 for (std::size_t i = 0; i < G[m].rows(); ++i)
145 for (std::size_t j = 0; j < G[m].cols(); ++j)
146 if (G[m](i, j) != zero) anySwap = true;
147 if (!anySwap) return Matrix<T>(R, R, zero); // pure OI: no placement constraint
148
149 // depth-first enumeration of the reachable (communicating) class
150 typedef std::pair<std::vector<int>, std::vector<int>> State;
151 State init;
152 for (std::size_t r = 0; r < R; ++r)
153 for (int k = 0; k < pop[r]; ++k) init.first.push_back(static_cast<int>(r) + 1);
154
155 std::set<State> seen;
156 std::vector<State> frontier, classStates;
157 seen.insert(init);
158 frontier.push_back(init);
159 const T rateTol = num_traits<T>::from_double(1e-12);
160 while (!frontier.empty()) {
161 const State st = frontier.back();
162 frontier.pop_back();
163 classStates.push_back(st);
164 for (std::size_t m = 0; m < M; ++m) {
165 const std::vector<int>& c = m == 0 ? st.first : st.second;
166 for (std::size_t p = 1; p <= c.size(); ++p) {
167 std::vector<int> prefix(c.begin(), c.begin() + static_cast<std::ptrdiff_t>(p));
168 T prevRate = zero;
169 if (p > 1) {
170 std::vector<int> shorter(c.begin(),
171 c.begin() + static_cast<std::ptrdiff_t>(p - 1));
172 prevRate = listRate[m](shorter);
173 }
174 const T rate = T(listRate[m](prefix) - prevRate);
175 if (!(rate > rateTol)) continue;
176 const std::pair<std::vector<int>, int> mv = detail::pas_swap_local(c, p, G[m]);
177 State stn = st;
178 (m == 0 ? stn.first : stn.second) = mv.first;
179 (m == 0 ? stn.second : stn.first).push_back(mv.second);
180 if (seen.insert(stn).second) frontier.push_back(stn);
181 }
182 }
183 }
184
185 // the orderings exposed by the reachable states
186 std::set<std::vector<int>> D;
187 for (std::size_t s = 0; s < classStates.size(); ++s) {
188 std::vector<int> c(classStates[s].first);
189 for (std::size_t k = classStates[s].second.size(); k > 0; --k)
190 c.push_back(classStates[s].second[k - 1]);
191 D.insert(c);
192 }
193
194 Matrix<T> H(R, R, zero);
195 for (std::size_t a = 1; a <= R; ++a)
196 for (std::size_t b = 1; b <= R; ++b) {
197 if (a == b) continue;
198 bool both = false, forced = true;
199 for (std::set<std::vector<int>>::const_iterator it = D.begin(); it != D.end(); ++it) {
200 const std::vector<int>& c = *it;
201 std::size_t maxa = 0, minb = 0;
202 for (std::size_t k = 0; k < c.size(); ++k) {
203 if (c[k] == static_cast<int>(a)) maxa = k + 1;
204 if (c[k] == static_cast<int>(b) && minb == 0) minb = k + 1;
205 }
206 if (maxa == 0 || minb == 0) continue;
207 both = true;
208 if (!(maxa < minb)) {
209 forced = false;
210 break;
211 }
212 }
213 if (both && forced) H(a - 1, b - 1) = one;
214 }
215 return H;
216}
217
218} // namespace pfqn
219} // namespace line
220
221#endif // LINE_API_PFQN_PAS_SWAP2ORDER_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
Matrix< T > pas_swap2order(const std::vector< Matrix< T > > &swap, const std::vector< PasRateFun< T > > &listRate, const std::vector< int > &N0=std::vector< int >())
Placement-order DAG of a two-station pass-and-swap tandem.
std::function< T(const std::vector< int > &)> PasRateFun
Total service rate of a queue on an ordered prefix of classes (1-based).
Number-type abstraction for the templated API port.