1% Pass-and-swap (PAS) saturation handling
for large / unbounded buffers.
3% An order-independent service rate mu(c) usually SATURATES: beyond some
4% per-
class count threshold tau_r, adding more class-r jobs no longer changes
5% mu (e.g. a compatibility / rank function saturates once each present
class
6% has one job; an M/M/K rate saturates at K jobs). A large finite buffer can
7% therefore approximate an unbounded queue cheaply.
9% This uses the Comte (thesis Sect. 4.1) compatibility queue: I=2
classes,
10% S=3 servers, S_1={1,2}, S_2={2,3}, rank function
11% mu(A) = sum_{ s : exists present
class in A compatible with s } cap_s,
12% so mu({1})=mu({2})=cap_1+cap_2 = 3, mu({1,2})=cap_1+cap_2+cap_3 = 5, and mu
13%
is constant once both
classes are present (cutoffs tau=[1,1]).
15% It checks that (i) SolverLDES matches SolverCTMC on a finite buffer and
16% (ii) an unset/infinite buffer raises a clean, actionable error.
19% compatibility comp(s,i) = 1 iff server s serves
class i; capacities cap_s
20comp = [1 0; % server 1 ->
class 1
21 1 1; % server 2 ->
classes 1, 2 (shared)
22 0 1]; % server 3 ->
class 2
28% Rank function: total capacity of servers compatible with a present
class.
29muRank = @(c) sum(cap_s(any(comp(:, unique(c(c>0))), 2)));
31% (1) a large buffer approximates the unbounded queue; LDES matches CTMC ------
33Qc = qlen(CTMC(build(muRank, CAP, I, S, lambda),
'cutoff', CAP), I);
34Ql = qlen(LDES(build(muRank, CAP, I, S, lambda),
'samples', 3e5,
'seed', 23000), I);
35fprintf(
'open compatibility PAS, buffer=%d:\n', CAP);
36fprintf(
' CTMC QLen = %s\n', mat2str(round(Qc, 5)));
37fprintf(
' LDES QLen = %s\n', mat2str(round(Ql, 5)));
38rel = max(abs(Qc - Ql) ./ max(Qc, 1e-12));
39fprintf(
' LDES vs CTMC max rel|dQ| = %.2f%%\n', 100 * rel);
40assert(rel <= 0.03,
'LDES does not match CTMC within simulation noise');
42% (2) clean error
for an unset / infinite buffer -----------------------------
43fprintf(
'\ninfinite-buffer PAS raises a clean, actionable error:\n');
45 LDES(build(muRank, Inf, I, S, lambda),
'samples', 1000,
'seed', 1).getAvgTable;
46 error(
'expected a finite-buffer error');
48 fprintf(
' %s\n', e.message);
51fprintf([
'\nPASS: PAS saturation handling matches CTMC on a large buffer and ' ...
52 'errors cleanly on infinite buffers.\n']);
54function model = build(mu, cap, I, S, lambda)
55model = Network(
'PASsaturation');
56source = Source(model,
'Source');
57queue = Queue(model,
'PASQueue', SchedStrategy.PAS);
58sink = Sink(model,
'Sink');
61 jobclass{r} = OpenClass(model, sprintf(
'Class%d', r));
62 source.setArrival(
jobclass{r}, Exp(lambda(r)));
65queue.setSwapGraph(zeros(I)); % empty graph: plain OI queue
66queue.setNumberOfServers(S);
70P = model.initRoutingMatrix;
72 P{
jobclass{r}} = Network.serialRouting(source, queue, sink);
77function q = qlen(solver, I)
78QN = solver.getAvgQLen; % nstations x nclasses mean queue length
79sn = solver.model.getStruct;
80sidx = sn.nodeToStation(find(strcmp(sn.nodenames,
'PASQueue'), 1));