LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ctmc_avg_from_pi.m
1function [QN,UN,RN,TN,CN,XN] = solver_ctmc_avg_from_pi(sn, pivec, StateSpace, StateSpaceAggr, arvRates, depRates, options)
2% SOLVER_CTMC_AVG_FROM_PI Map a state distribution to mean performance metrics.
3%
4% [QN,UN,RN,TN,CN,XN] = SOLVER_CTMC_AVG_FROM_PI(SN, PIVEC, STATESPACE,
5% STATESPACEAGGR, ARVRATES, DEPRATES)
6%
7% Given an arbitrary probability vector PIVEC over the enumerated CTMC state
8% space of SN (rows of STATESPACE / STATESPACEAGGR), returns the per-(station,
9% class) mean queue length QN, utilization UN, response time RN, throughput TN,
10% system response time CN and system throughput XN. The discipline-aware mapping
11% is identical to the steady-state reduction performed by solver_ctmc_analyzer;
12% it is factored here so that callers holding their own distribution (e.g. the
13% SolverENV state-vector analyzer, which time-averages a transient distribution)
14% can reuse it without re-solving for the stationary vector.
15%
16% Copyright (c) 2012-2026, Imperial College London
17% All rights reserved.
18
19M = sn.nstations;
20K = sn.nclasses;
21S = sn.nservers;
22NK = sn.njobs';
23sched = sn.sched;
24PH = sn.proc;
25
26probSysState = pivec(:)';
27probSysState(probSysState<GlobalConstants.Zero) = 0;
28if sum(probSysState) > 0
29 probSysState = probSysState/sum(probSysState);
30end
31wset = 1:size(StateSpace,1);
32
33XN = NaN*zeros(1,K);
34UN = NaN*zeros(M,K);
35QN = NaN*zeros(M,K);
36RN = NaN*zeros(M,K);
37TN = NaN*zeros(M,K);
38CN = NaN*zeros(1,K);
39
40istSpaceShift = zeros(1,M);
41for ist=1:M
42 if ist==1
43 istSpaceShift(ist) = 0;
44 else
45 istSpaceShift(ist) = istSpaceShift(ist-1) + size(sn.space{ist-1},2);
46 end
47end
48
49for k=1:K
50 refsf = sn.stationToStateful(sn.refstat(k));
51 XN(k) = probSysState*arvRates(wset,refsf,k);
52end
53
54for ist=1:M
55 isf = sn.stationToStateful(ist);
56 ind = sn.stationToNode(ist);
57 for k=1:K
58 TN(ist,k) = probSysState*depRates(wset,isf,k);
59 QN(ist,k) = probSysState*StateSpaceAggr(wset,(ist-1)*K+k);
60 end
61 if sn.nodetype(ind) ~= NodeType.Source
62 % A class annihilated in place by a G-network signal leaves without a
63 % service completion, so its arrival-based estimator counts offered
64 % rather than carried load; use the departure-based one there.
65 signalLoss = ctmc_signal_lossy(sn, arvRates, probSysState, wset, isf);
66 switch sched(ist)
67 case SchedStrategy.INF
68 for k=1:K
69 UN(ist,k) = QN(ist,k);
70 end
71 case {SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.LPS}
72 if isempty(sn.lldscaling) && isempty(sn.cdscaling)
73 for k=1:K
74 if ~isempty(PH{ist}{k})
75 % There are cases where due to remove of
76 % immediate transitions or due to cutoff the
77 % utilization estimator based on arrivals or
78 % departures can be under-estimated. E.g.:
79 % UNarv_ik doesn't work well with example_stateDependentRouting_3
80 % UNdep_ik doesn't work well with test_OQN_JMT_6
81 % Therefore, we take the maximum of the two
82 % Note: the two estimators are normally
83 % identical.
84 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
85 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); % this is valid because CS in LINE is in a separate node
86 UN(ist,k) = signalLoss(k)*UNdep_ik + (1-signalLoss(k))*max(UNarv_ik,UNdep_ik);
87 end
88 end
89 else % lld/cd/ljd cases
90 % Weight the per-class capacity share by the current scaling
91 % and normalize by the peak (effective capacity), as
92 % SOLVER_CTMC_ANALYZER does: the unweighted share is a
93 % P(busy)-style value, not a busy-server fraction, and would
94 % overstate the utilization of a load-dependent station.
95 ind = sn.stationToNode(ist);
96 ceff = S(ist);
97 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
98 ceff = max(ceff, max(sn.lldscaling(ist,:)));
99 end
100 UN(ist,1:K) = 0;
101 for st = wset
102 [ni,nir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
103 if ni>0
104 lldnow = 1;
105 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
106 lldnow = sn.lldscaling(ist, min(max(sum(ni),1), size(sn.lldscaling,2)));
107 end
108 for k=1:K
109 UN(ist,k) = UN(ist,k) + probSysState(st)*nir(k)*sn.schedparam(ist,k)/(nir*sn.schedparam(ist,:)')*lldnow/ceff;
110 end
111 end
112 end
113 end
114 case SchedStrategy.PAS
115 % Pass-and-swap (order-independent): utilization is the
116 % time-average number of in-service jobs per class divided by
117 % the number of servers. sir already counts the positions whose
118 % marginal rate increment is positive (toMarginal PAS branch),
119 % so a single job served by multiple server types still counts
120 % as one in-service job (not 1/rate).
121 ind = sn.stationToNode(ist);
122 UN(ist,1:K) = 0;
123 for st = wset
124 [~,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
125 for k=1:K
126 UN(ist,k) = UN(ist,k) + probSysState(st)*sir(k)/S(ist);
127 end
128 end
129 otherwise
130 if isempty(sn.lldscaling) && isempty(sn.cdscaling)
131 for k=1:K
132 if ~isempty(PH{ist}{k})
133 % There are cases where due to remove of
134 % immediate transitions or due to cutoff the
135 % utilization estimator based on arrivals or
136 % departures can be under-estimated. E.g.:
137 % UNarv_ik doesn't work well with example_stateDependentRouting_3
138 % UNdep_ik doesn't work well with test_OQN_JMT_6
139 % Therefore, we take the maximum of the two
140 % Note: the two estimators are normally
141 % identical.
142 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
143 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); % this is valid because CS in LINE is in a separate node
144 UN(ist,k) = signalLoss(k)*UNdep_ik + (1-signalLoss(k))*max(UNarv_ik,UNdep_ik);
145 end
146 end
147 else % lld/cd/ljd cases
148 ind = sn.stationToNode(ist);
149 UN(ist,1:K) = 0;
150 for st = wset
151 [ni,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
152 if ni>0
153 for k=1:K
154 UN(ist,k) = UN(ist,k) + probSysState(st)*sir(k)/S(ist);
155 end
156 end
157 end
158 end
159 end
160 % A signal that destroys a partially served job leaves busy time behind
161 % with no completion, so T*E[S]/c is exact only for exponential service.
162 % Read the busy-server occupancy off the state space instead; the two
163 % agree in the exponential case. Mirrors SOLVER_CTMC_ANALYZER.
164 if any(signalLoss) && sched(ist) ~= SchedStrategy.INF && ...
165 isempty(sn.lldscaling) && isempty(sn.cdscaling)
166 UNb = ctmc_signal_busy(sn, ind, ist, sched(ist), S(ist), StateSpace, istSpaceShift, wset, probSysState);
167 UN(ist,signalLoss) = UNb(signalLoss);
168 end
169 end
170end
171
172% Stations with limited class dependence report utilization as T*S/peak,
173% where peak is the user-declared peak rate scaling per class
174% (sn.cdscalingpeak, from setClassDependence(beta,peakRatePerClass)). This
175% matches the T*S/c convention of ordinary multiserver stations; the
176% in-service accumulation above would instead report the busy probability
177% P(n>=1), which is a different quantity.
178if ~isempty(sn.cdscaling) && ~any(isinf(sn.njobs))
179 for ist=1:M
180 if length(sn.cdscaling) >= ist && ~isempty(sn.cdscaling{ist})
181 for k=1:K
182 bmax = sn.cdscalingpeak(ist,k);
183 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
184 UN(ist,k) = TN(ist,k) / sn.rates(ist,k) / bmax;
185 else
186 UN(ist,k) = 0;
187 end
188 end
189 end
190 end
191end
192
193for k=1:K
194 for ist=1:M
195 if TN(ist,k)>0
196 RN(ist,k) = QN(ist,k)./TN(ist,k);
197 else
198 RN(ist,k)=0;
199 end
200 end
201 CN(k) = NK(k)./XN(k);
202end
203
204QN(isnan(QN))=0;
205CN(isnan(CN))=0;
206RN(isnan(RN))=0;
207UN(isnan(UN))=0;
208XN(isnan(XN))=0;
209TN(isnan(TN))=0;
210end
Definition Station.m:245