LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_nc_pas_is_analyzer.m
1function [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_nc_pas_is_analyzer(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME,ITER,METHOD] = SOLVER_NC_PAS_IS_ANALYZER(SN, OPTIONS)
3%
4% Importance-sampling (IS) normalizing-constant analysis of a closed two-station
5% pass-and-swap (P&S) tandem with a non-empty swap graph (Casale, Comte &
6% Dorsman, 2026). With a genuine swap graph the ordered-state chain is reducible
7% (Comte & Dorsman, 2021, arXiv:2009.12299); the recurrent communicating class
8% carries a per-class product form pi(c) = Phi_1(c_1) Phi_2(c_2)/G_C, and its
9% constant G_C is estimated by the auto-normalized IS routine PFQN_PAS_IS. This
10% is the Monte-Carlo counterpart of SOLVER_NC_OI_ANALYZER for the case that the
11% exact OI convolution does not apply (non-empty swap graph).
12%
13% Station 1 is the upstream P&S queue (prefix of the ordering), station 2 the
14% downstream queue (reversed suffix); the swap graph is a class-level property
15% read from the upstream station. Both stations must be OI/P&S with unit per-
16% class visits (the balanced-fairness framework). Mean per-class queue lengths
17% come directly from PFQN_PAS_IS (auto-normalized IS, same samples for numerator
18% and denominator). Per-class throughput uses the balanced-fairness ratio
19% X_r = G(N - e_r)/G(N) with common random numbers across the N and N-e_r runs
20% for variance reduction; utilization, response time, and system time follow the
21% same conventions as SOLVER_NC_OI_ANALYZER.
22%
23% Copyright (c) 2012-2026, Imperial College London
24% All rights reserved.
25Tstart = tic;
26iter = 1;
27method = 'is'; % importance sampling, specialized to OI/P&S stations
28
29M = sn.nstations;
30K = sn.nclasses;
31
32% ---- reject class switching (P&S rank rates are per raw class) -------------
33for c = 1:sn.nchains
34 if numel(sn.inchain{c}) > 1
35 line_error(mfilename, 'solver_nc_pas_is requires one class per chain (no class switching).');
36 end
37end
38if any(isinf(sn.njobs))
39 line_error(mfilename, 'solver_nc_pas_is requires a closed queueing network.');
40end
41if M ~= 2
42 line_error(mfilename, 'solver_nc_pas_is models a two-station pass-and-swap tandem (got %d stations).', M);
43end
44N = round(sn.njobs(:)');
45
46% ---- classify stations and read swap graph ---------------------------------
47svc = cell(M, 1);
48swapG = cell(M, 1);
49for ist = 1:M
50 if sn.sched(ist) ~= SchedStrategy.PAS && sn.sched(ist) ~= SchedStrategy.OI
51 line_error(mfilename, 'solver_nc_pas_is requires both stations to be OI/PAS (station %d is not).', ist);
52 end
53 ind = sn.stationToNode(ist);
54 if ind < 1 || ind > numel(sn.nodeparam) || ~isstruct(sn.nodeparam{ind})
55 line_error(mfilename, 'station %d has no OI/PAS node parameters.', ist);
56 end
57 svc{ist} = sn.nodeparam{ind}.svcRateFun;
58 if isempty(svc{ist})
59 line_error(mfilename, 'OI/PAS station %d has no service rate function; set it via setService(@(c) ...).', ist);
60 end
61 if isfield(sn.nodeparam{ind}, 'swapGraph')
62 swapG{ist} = sn.nodeparam{ind}.swapGraph;
63 else
64 swapG{ist} = [];
65 end
66end
67
68% derive the global placement-order DAG H (recurrent communicating class) from
69% the P&S dynamics via pas_swap2order; see _kb/06-solver-catalog.md (NC section)
70G1 = swapG{1}; if isempty(G1), G1 = zeros(K, K); end
71G2 = swapG{2}; if isempty(G2), G2 = zeros(K, K); end
72H = pas_swap2order({G1, G2}, {svc{1}, svc{2}}, ones(1, K));
73
74% ---- per-class visits (chain == class); require unit visits ----------------
75V = zeros(M, K);
76for r = 1:K
77 c = find(sn.chains(:, r));
78 vis = sn.visits{c};
79 for ist = 1:M
80 isf = sn.stationToStateful(ist);
81 V(ist, r) = vis(isf, r);
82 end
83 vref = V(sn.refstat(r), r);
84 if vref > 0
85 V(:, r) = V(:, r) / vref;
86 end
87end
88for ist = 1:M
89 for r = 1:K
90 if N(r) > 0 && abs(V(ist, r) - 1) > 1e-9
91 line_error(mfilename, 'solver_nc_pas_is requires unit per-class visits (station %d, class %d, V=%g).', ist, r, V(ist, r));
92 end
93 end
94end
95
96% ---- OI rank-rate handles on a per-class count vector ----------------------
97% svcRateFun(c) takes an ordered microstate list; for an OI station it is
98% permutation-invariant, so evaluate it on a canonical microstate for count n.
99rate1 = @(n) svc{1}(pas_is_microstate(n));
100rate2 = @(n) svc{2}(pas_is_microstate(n));
101mu = {rate1, rate2};
102
103% ---- IS options; fix a base seed so the N and N-e_r runs share randoms -----
104isopt = options;
105if ~isfield(isopt, 'samples') || isempty(isopt.samples)
106 if isfield(options, 'iter_max') && ~isempty(options.iter_max) && options.iter_max > 1
107 isopt.samples = options.iter_max;
108 else
109 isopt.samples = 1e4;
110 end
111end
112if ~isfield(isopt, 'seed') || isempty(isopt.seed)
113 if isfield(options, 'seed') && ~isempty(options.seed)
114 isopt.seed = options.seed;
115 else
116 isopt.seed = 23456;
117 end
118end
119
120% ---- normalizing constant and mean queue lengths at population N -----------
121[G, lG, Qpas] = pfqn_pas_is(N, mu, H, isopt);
122
123Q = zeros(M, K);
124Q(1, :) = Qpas(1, :);
125Q(2, :) = Qpas(2, :);
126
127% ---- per-class throughput X_r = G(N - e_r)/G(N) (common random numbers) ----
128X = zeros(1, K);
129for r = 1:K
130 if N(r) > 0
131 er = zeros(1, K); er(r) = 1;
132 Gr = pfqn_pas_is(N - er, mu, H, isopt);
133 if G > 0
134 X(r) = Gr / G;
135 end
136 end
137end
138
139% ---- throughput, utilization, response time --------------------------------
140T = zeros(M, K);
141U = zeros(M, K);
142R = zeros(M, K);
143for ist = 1:M
144 for r = 1:K
145 T(ist, r) = X(r) * V(ist, r);
146 end
147end
148for ist = 1:M
149 S = sn.nservers(ist);
150 if ~isfinite(S) || S <= 0, S = 1; end
151 for r = 1:K
152 if N(r) > 0
153 er = zeros(1, K); er(r) = 1;
154 muR = mu{ist}(er); % rank rate with only class r present
155 if muR > 0
156 U(ist, r) = T(ist, r) / muR / S;
157 end
158 end
159 end
160end
161for ist = 1:M
162 for r = 1:K
163 if T(ist, r) > 0
164 R(ist, r) = Q(ist, r) / T(ist, r);
165 end
166 end
167end
168
169C = zeros(1, K);
170for r = 1:K
171 if X(r) > 0
172 C(r) = N(r) / X(r);
173 end
174end
175
176runtime = toc(Tstart);
177end
178
179% ==========================================================================
180function c = pas_is_microstate(n)
181% Canonical ordered microstate holding n_r copies of class r (n a count
182% vector). For an order-independent station the rank rate is invariant to the
183% ordering, so this representative suffices to evaluate svcRateFun(c).
184c = repelem(1:numel(n), round(n));
185end