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.
4% [QN,UN,RN,TN,CN,XN] = SOLVER_CTMC_AVG_FROM_PI(SN, PIVEC, STATESPACE,
5% STATESPACEAGGR, ARVRATES, DEPRATES)
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.
16% Copyright (c) 2012-2026, Imperial College London
26probSysState = pivec(:)';
27probSysState(probSysState<GlobalConstants.Zero) = 0;
28if sum(probSysState) > 0
29 probSysState = probSysState/sum(probSysState);
31wset = 1:size(StateSpace,1);
40istSpaceShift = zeros(1,M);
43 istSpaceShift(ist) = 0;
45 istSpaceShift(ist) = istSpaceShift(ist-1) + size(sn.space{ist-1},2);
50 refsf = sn.stationToStateful(sn.refstat(k));
51 XN(k) = probSysState*arvRates(wset,refsf,k);
55 isf = sn.stationToStateful(ist);
56 ind = sn.stationToNode(ist);
58 TN(ist,k) = probSysState*depRates(wset,isf,k);
59 QN(ist,k) = probSysState*StateSpaceAggr(wset,(ist-1)*K+k);
61 if sn.nodetype(ind) ~= NodeType.Source
62 % see _kb/06-solver-catalog.md (G-network signals)
for rationale
63 signalLoss = ctmc_signal_lossy(sn, arvRates, probSysState, wset, isf);
65 case SchedStrategy.INF
67 UN(ist,k) = QN(ist,k);
69 case {SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.LPS}
70 if isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
72 if ~isempty(PH{ist}{k})
73 % see _kb/06-solver-catalog.md (Utilization conventions)
for rationale
74 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
75 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); %
this is valid because CS in LINE
is in a separate node
76 UN(ist,k) = signalLoss(k)*UNdep_ik + (1-signalLoss(k))*max(UNarv_ik,UNdep_ik);
79 else % lld/cd/ljd cases
80 % see _kb/06-solver-catalog.md (Utilization conventions)
for rationale
81 ind = sn.stationToNode(ist);
83 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
84 ceff = max(ceff, max(sn.lldscaling(ist,:)));
88 [ni,nir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
91 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
92 lldnow = sn.lldscaling(ist, min(max(sum(ni),1), size(sn.lldscaling,2)));
95 UN(ist,k) = UN(ist,k) + probSysState(st)*nir(k)*sn.schedparam(ist,k)/(nir*sn.schedparam(ist,:)')*lldnow/ceff;
100 case SchedStrategy.PAS
101 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
102 ind = sn.stationToNode(ist);
105 [~,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
107 UN(ist,k) = UN(ist,k) + probSysState(st)*sir(k)/S(ist);
111 if isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
113 if ~isempty(PH{ist}{k})
114 % see _kb/06-solver-catalog.md (Utilization conventions)
for rationale
115 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
116 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); %
this is valid because CS in LINE
is in a separate node
117 UN(ist,k) = signalLoss(k)*UNdep_ik + (1-signalLoss(k))*max(UNarv_ik,UNdep_ik);
120 else % lld/cd/ljd cases
121 ind = sn.stationToNode(ist);
124 [ni,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
127 UN(ist,k) = UN(ist,k) + probSysState(st)*sir(k)/S(ist);
133 % see _kb/06-solver-catalog.md (G-network signals)
for rationale
134 if any(signalLoss) && sched(ist) ~= SchedStrategy.INF && ...
135 isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
136 UNb = ctmc_signal_busy(sn, ind, ist, sched(ist), S(ist), StateSpace, istSpaceShift, wset, probSysState);
137 UN(ist,signalLoss) = UNb(signalLoss);
142% see _kb/06-solver-catalog.md (Utilization conventions) for rationale
143if ~isempty(sn.cdscaling) && ~any(isinf(sn.njobs))
145 if length(sn.cdscaling) >= ist && ~isempty(sn.cdscaling{ist})
147 bmax = sn.cdscalingpeak(ist,k);
148 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
149 UN(ist,k) = TN(ist,k) / sn.rates(ist,k) / bmax;
158% Joint-dependence (non-product-
form) utilization normalization: Util=T*S/peak
159%
using the declared sn.jdscalingpeak, mirroring the
class-dependence block.
160if ~isempty(sn.jdscaling) && ~any(isinf(sn.njobs))
162 if length(sn.jdscaling) >= ist && ~isempty(sn.jdscaling{ist})
164 bmax = sn.jdscalingpeak(ist,k);
165 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
166 UN(ist,k) = TN(ist,k) / sn.rates(ist,k) / bmax;
179 RN(ist,k) = QN(ist,k)./TN(ist,k);
184 CN(k) = NK(k)./XN(k);