1function [ph, phases] = refreshProcessRepresentations(self)
2% [PH, PHASES] = REFRESHPROCESSREPRESENTATIONS()
4% Copyright (c) 2012-2026, Imperial College London
8M = getNumberOfStations(self);
9K = getNumberOfClasses(self);
12 ph{ist,1} = cell(1,K);
15stations = self.stations;
17 if ist == self.getIndexSourceStation
18 ph_i = stations{ist}.getSourceRates();
20 switch class(stations{ist})
28 ph_i = Coxian(mu_i,phi_i).getProcess;
35 ph_i{r} = Coxian(mu_i{r},phi_i{r}).getProcess;
38 ph_i = stations{ist}.getServiceRates();
43 % NHPP carries a rate schedule, not D0/D1: always 1 active phase
44 isSchedule_ir =
false;
46 if isa(stations{ist},
'Source') && any(ist == self.getIndexSourceStation) ...
47 && length(stations{ist}.input.sourceClasses) >= r ...
48 && ~isempty(stations{ist}.input.sourceClasses{r})
49 dist_ir = stations{ist}.input.sourceClasses{r}{end};
50 isSchedule_ir = ismethod(dist_ir,
'getRateSchedule');
51 elseif isa(stations{ist},
'ServiceStation') ...
52 && length(stations{ist}.server.serviceProcess) >= r ...
53 && ~isempty(stations{ist}.server.serviceProcess{r})
54 dist_ir = stations{ist}.server.serviceProcess{r}{end};
55 isSchedule_ir = ismethod(dist_ir,
'getRateSchedule');
57 % Gamma/Weibull/Lognormal/Pareto/Uniform
return the raw distribution
58 % PARAMETERS from getProcess, not a (D0,D1) pair. Two 1x1 parameters
59 % satisfy isMAP
's "square matrices of equal size" shape test, so they
60 % were classified as a valid single-phase MAP and convertToMAP was never
61 % reached: sn.proc then held {D0=shape, D1=scale}, phases was 1, and
62 % map_pie ran on parameters. The shape test cannot tell a 1x1 (D0,D1)
63 % from two scalars, so the process type has to be asked directly. Det
64 % returns a single parameter and already fails the length>=2 test.
65 isRawParam_ir = ~isempty(dist_ir) && (isa(dist_ir,'Gamma
') ...
66 || isa(dist_ir,'Weibull
') || isa(dist_ir,'Lognormal
') ...
67 || isa(dist_ir,'Pareto
') || isa(dist_ir,'Uniform
'));
68 if isempty(ph{ist}{r}) % fluid fails otherwise
72 elseif isRawParam_ir || ~isMAP(ph{ist}{r})
73 % Non-Markovian distribution: convert to MAP representation
74 ph{ist}{r} = convertToMAP(stations{ist}, r, ph{ist}{r});
75 phases(ist,r) = length(ph{ist}{r}{1});
76 elseif any(isnan(ph{ist}{r}{1}(:))) || any(isnan(ph{ist}{r}{2}(:))) % disabled
79 phases(ist,r) = length(ph{ist}{r}{1});
83if ~isempty(self.sn) %&& isprop(self.sn,'mu
')
89 % NHPP carries a rate schedule, not D0/D1: skip map_pie
90 isSchedule_ir = false;
91 if isa(stations{ist}, 'Source
') && any(ist == self.getIndexSourceStation) ...
92 && length(stations{ist}.input.sourceClasses) >= r ...
93 && ~isempty(stations{ist}.input.sourceClasses{r})
94 isSchedule_ir = ismethod(stations{ist}.input.sourceClasses{r}{end}, 'getRateSchedule
');
95 elseif isa(stations{ist}, 'ServiceStation
') ...
96 && length(stations{ist}.server.serviceProcess) >= r ...
97 && ~isempty(stations{ist}.server.serviceProcess{r})
98 isSchedule_ir = ismethod(stations{ist}.server.serviceProcess{r}{end}, 'getRateSchedule
');
101 proc{ist}{r} = map_ir;
105 pie{ist}{r} = map_pie(map_ir);
114 self.sn.phases = phases;
115 self.sn.phasessz = max(self.sn.phases,ones(size(self.sn.phases)));
116 self.sn.phasessz(self.sn.nodeToStation(self.sn.nodetype == NodeType.Join),:)=phases(self.sn.nodeToStation(self.sn.nodetype == NodeType.Join),:);
117 % Marked (MMAP) source classes share the carrier's modulating chain: the
118 % non-carrier classes (mark index > 1) contribute a single always-zero
119 % state
column rather than their own phase block.
120 if isfield(self.sn,'markidx') && ~isempty(self.sn.markidx)
121 self.sn.phasessz(self.sn.markidx > 1) = 1;
123 self.sn.phaseshift = [zeros(size(phases,1),1),cumsum(self.sn.phasessz,2)];
127function result = isMAP(proc)
128% ISMAP Check if a process representation
is a valid MAP {D0, D1}
130% A valid representation has at least 2 cell elements (D0, D1, plus
131% optional marked/batch matrices D_k as in BMAP/MarkedMAP), all square
132% matrices of the same size.
134if ~iscell(proc) || length(proc) < 2
137n0 = size(proc{1}, 1);
138for e = 1:length(proc)
140 if ~isnumeric(De) || ~ismatrix(De) || size(De,1) ~= size(De,2) || size(De,1) ~= n0
147function MAP = convertToMAP(station, classIdx, proc)
148% CONVERTTOMAP Convert non-Markovian distribution parameters to MAP
150% For non-Markovian distributions, the process representation contains
151% distribution parameters rather than {D0, D1} matrices. This function
152% converts them to an Erlang approximation.
154% Get the distribution
object from the station
155if isa(station,
'Source')
156 dist = station.input.sourceClasses{classIdx}{end};
158 dist = station.server.serviceProcess{classIdx}{end};
161% Get mean
for Erlang approximation
162targetMean = dist.getMean();
164% Determine number of phases based on SCV
165% For Det (SCV=0), use high number of phases;
for others, match SCV
167if scv < GlobalConstants.CoarseTol
168 % Deterministic or near-deterministic: use 20 phases
171 % Match SCV:
for Erlang, SCV = 1/n, so n = 1/SCV
172 nPhases = max(1, ceil(1/scv));
173 nPhases = min(nPhases, 100); % Cap at 100 phases
176% Create Erlang MAP approximation
177MAP = map_erlang(targetMean, nPhases);