1function Pstate = getProb(self, node, state)
2% PSTATE = GETPROB(NODE, STATE)
4% Returns state probability
for the specified node and state
5% For QBD models, state
is a 2-element vector [level, phase]
8% node - node/station index
9% state - state vector [level, phase] or structure with .level and .phase fields
10% If state
is omitted or empty, returns full probability matrix
13% Pstate - probability of the state, or full probability matrix
if state not specified
16 line_error(mfilename,
'getProb requires a node parameter.');
24% Convert node to station index
if needed
26 line_error(mfilename,
'Node number exceeds the number of nodes in the model.');
28ist = sn.nodeToStation(node);
30 line_error(mfilename,
'Specified node is not a station.');
33% Check
if this is a network (more than one queue station)
36 if sn.nodetype(sn.stationToNode(i)) == NodeType.Queue
37 queueStations = queueStations + 1;
42 line_error(mfilename,
'getProb is not supported for networks with multiple queues in SolverMAM. The MAM solver uses QBD (quasi-birth-death) analysis, which is fundamentally a single-queue method. Use SolverCTMC or SolverSSA for state probabilities in networks with multiple queues.');
45% Check
if the model has only one queue
47 line_error(mfilename,
'Model does not contain any queue stations.');
50% Ensure results are available
51if isempty(self.result)
55% Get model parameters needed for QBD solution
59% Check if this
is a closed model
61 % Closed model - compute probability distribution using QBD
62 maxLevel = sum(N(isfinite(N))) + 1;
64 % Build the arrival and service processes
69 % Extract service process parameters
71 PH{ist}{k} = map_scale(PH{ist}{k}, 1./sn.rates(ist,k)/sn.nservers(ist));
72 pie{k} = map_pie(PH{ist}{k});
73 D0{k} = PH{ist}{k}{1};
75 D0{k} = -GlobalConstants.Immediate;
80 % Build aggregate arrival process (approximation
using throughput)
81 T = self.result.Avg.T;
82 lambda_total = sum(T(ist,:));
84 if lambda_total < GlobalConstants.FineTol
85 % No traffic at
this station
87 % Return full probability matrix - all probability at state (0,1)
88 Pstate = zeros(maxLevel, 1);
95 elseif length(state) >= 2
99 line_error(mfilename,'State must be a 2-element vector [level, phase] or structure with .level and .phase fields.');
102 if level == 0 && phase == 1
111 % Build a simple
MMAP approximation based on class throughputs
112 D_approx = cell(1, K+1);
113 D_approx{1} = -lambda_total * eye(1); % D0
115 D_approx{k+1} = T(ist,k) * ones(1,1); % Dk - arrivals of
class k
119 % Approximate joint (level,phase) from level marginal + uniform phase;
120 % see _kb/06-solver-catalog.md
for rationale
122 [pdistr] = MMAPPH1FCFS(D_approx, {pie{:}}, {D0{:}},
'ncDistr', maxLevel);
123 pdistr = abs(pdistr);
124 pdistr = pdistr / sum(pdistr);
126 % Build phase distribution -
for simplicity, use the steady-state
127 % phase distribution from the service process
128 % This
is an approximation
129 nPhases = size(D0{1}, 1);
131 nPhases = max(nPhases, size(D0{k}, 1));
134 % Construct joint probability matrix: rows = levels, cols = phases
135 % For now, approximate by assuming phases are independent of level
136 % and use the service process steady-state distribution
137 avgPie = zeros(1, nPhases);
139 if size(pie{k}, 2) == nPhases
140 avgPie = avgPie + pie{k} * T(ist,k) / lambda_total;
144 if sum(avgPie) == 0 || any(isnan(avgPie))
145 avgPie = ones(1, nPhases) / nPhases;
147 avgPie = avgPie / sum(avgPie);
150 % Joint distribution:
P(level, phase) ≈
P(level) *
P(phase)
151 Pstate = zeros(min(maxLevel, length(pdistr)), nPhases);
152 for level=1:min(maxLevel, length(pdistr))
153 Pstate(level, :) = pdistr(level) * avgPie;
156 % If specific state requested, extract it
161 elseif length(state) >= 2
165 line_error(mfilename,
'State must be a 2-element vector [level, phase] or structure with .level and .phase fields.');
168 % Check bounds (MATLAB indexing: level+1, phase)
169 if level+1 > size(Pstate, 1) || phase > size(Pstate, 2) || level < 0 || phase < 1
172 Pstate = Pstate(level+1, phase);
177 line_error(mfilename,
'Failed to compute state probabilities: %s', ME.message);
181 % Open model - compute joint probability distribution
using MAM (MMAPPH1FCFS)
183 % Get model parameters
188 % Extract service process parameters
for the queue station
190 PH{ist}{k} = map_scale(PH{ist}{k}, 1./sn.rates(ist,k)/sn.nservers(ist));
191 pie{k} = map_pie(PH{ist}{k});
192 D0{k} = PH{ist}{k}{1};
194 D0{k} = -GlobalConstants.Immediate;
199 % Build the arrival process from source
200 refstat = sn.refstat(1); % Source station
201 PH_src = sn.proc{refstat};
203 % Build arrival process cell array: {D0, D1, D2, ...}
for K classes
204 D_arr = cell(1, K + 1);
206 % Check total arrival rate
209 if ~isnan(PH_src{k}{1})
210 totalLambda = totalLambda + map_lambda(PH_src{k});
214 % Compute queue length distribution
using MMAPPH1FCFS
215 maxLevel = 100; % Maximum queue length to compute
216 if ~isempty(self.options.cutoff) && isfinite(self.options.cutoff) && self.options.cutoff > 0
217 maxLevel = self.options.cutoff;
220 if totalLambda < GlobalConstants.FineTol
221 % No arrivals at
this station
223 Pstate = zeros(maxLevel, 1);
229 elseif length(state) >= 2
233 line_error(mfilename,
'State must be a 2-element vector [level, phase] or structure.');
235 if level == 0 && phase == 1
242 % Build the aggregate arrival
MMAP
243 arrMaps = cell(1, K);
245 if ~isnan(PH_src{k}{1})
246 arrMaps{k} = PH_src{k};
248 arrMaps{k} = map_exponential(Inf); % No arrivals
252 % Superpose all arrival processes
254 % Single
class - use the arrival process directly
255 arrProcess = arrMaps{1};
256 D_arr{1} = arrProcess{1}; % D0
257 D_arr{2} = arrProcess{2}; % D1
259 % Multiple classes - superpose MAPs
260 superMAP = arrMaps{1};
262 superMAP = map_super({superMAP, arrMaps{k}});
264 % Build
MMAP with
class marking based on arrival rates
265 D_arr{1} = superMAP{1}; % D0
267 lambdaK = map_lambda(arrMaps{k});
268 D_arr{k + 1} = (lambdaK / totalLambda) * superMAP{2};
273 [pdistr] = MMAPPH1FCFS(
D_arr, pie, D0,
'ncDistr', maxLevel);
274 pdistr = abs(pdistr);
275 pdistr = pdistr / sum(pdistr);
277 % Build phase distribution - use the steady-state phase distribution
278 nPhases = size(D0{1}, 1);
280 nPhases = max(nPhases, size(D0{k}, 1));
283 % Compute weighted average phase distribution
284 V = cellsum(sn.visits);
285 avgPie = zeros(1, nPhases);
287 if size(pie{k}, 2) <= nPhases
288 pieK = [pie{k}, zeros(1, nPhases - size(pie{k}, 2))];
289 lambdaK = map_lambda(arrMaps{k});
290 avgPie = avgPie + pieK * lambdaK / totalLambda;
294 if sum(avgPie) == 0 || any(isnan(avgPie))
295 avgPie = ones(1, nPhases) / nPhases;
297 avgPie = avgPie / sum(avgPie);
300 % Joint distribution:
P(level, phase) ≈
P(level) *
P(phase)
301 Pstate = zeros(min(maxLevel, length(pdistr)), nPhases);
302 for level = 1:min(maxLevel, length(pdistr))
303 Pstate(level, :) = pdistr(level) * avgPie;
306 % If specific state requested, extract it
311 elseif length(state) >= 2
315 line_error(mfilename,
'State must be a 2-element vector [level, phase] or structure.');
318 if level + 1 > size(Pstate, 1) || phase > size(Pstate, 2) || level < 0 || phase < 1
321 Pstate = Pstate(level + 1, phase);
325 line_error(mfilename, sprintf(
'Failed to compute state probabilities: %s', ME.message));