LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
SolverMAM.m
1classdef SolverMAM < NetworkSolver
2 % Matrix-Analytic and RCAT methods solver
3 %
4 % Implements matrix-analytic methods and RCAT for structured Markov chain analysis.
5 %
6 % Copyright (c) 2012-2026, Imperial College London
7 % All rights reserved.
8
9 methods
10 function self = SolverMAM(model,varargin)
11 % SOLVERMAM Create a Matrix-Analytic Methods solver instance
12 %
13 % @brief Creates a MAM solver for structured Markov chain analysis
14 % @param model Network model to be analyzed via matrix-analytic methods
15 % @param varargin Optional parameters (method, tolerance, etc.)
16 % @return self SolverMAM instance configured for MAM analysis
17
18 self@NetworkSolver(model, mfilename);
19 self.setOptions(Solver.parseOptions(varargin, self.defaultOptions));
20 self.setLang();
21 end
22
23 function sn = getStruct(self)
24 % QN = GETSTRUCT()
25
26 % Get data structure summarizing the model
27 sn = self.model.getStruct(true);
28 end
29
30 runtime = runAnalyzer(self, options);
31 RD = getCdfRespT(self, R);
32
33 function [allMethods] = listValidMethods(self)
34 % allMethods = LISTVALIDMETHODS()
35 % List valid methods for this solver
36 sn = self.model.getStruct();
37 % Note: Method order must match expected values in test files.
38 % New methods should be added at the end to preserve index alignment.
39 % 'exact' method removed - autocat moved to line-legacy.git
40 allMethods = {'default','dec.source','dec.mmap','dec.poisson','mna','inap','ldqbd','inapplus','dec.source.mmap','inapinf'};
41 end
42
43 function featSupported = getMethodFeatureSet(self, method) %#ok<INUSD>
44 % Every MAM method shares the solver-level feature envelope; the
45 % genuine per-method restrictions ('mna', 'ldqbd') are structural
46 % and are applied in supportsModelMethod below.
47 %
48 % Defining this is what lets NetworkSolver.supportsModelMethod name
49 % the offending features: with no method feature set it falls back
50 % to the coarse supports(model) and returns an empty reason, so the
51 % gate could only report "features not supported" without saying
52 % which ones.
53 featSupported = SolverMAM.getFeatureSet();
54 end
55
56 function [bool, reason] = supportsModelMethod(self, method)
57 % Method-aware gate for the genuine per-method restrictions of the
58 % MAM analyzer (mirrors the inline guards in solver_mam_analyzer):
59 % 'mna' does not support mixed open/closed models, and 'ldqbd'
60 % requires a single-class model. All other methods rely on the
61 % coarse MAM feature set and the analyzer's topology-based routing
62 % (e.g. a Fork-Join model on 'default'/'dec.source' is routed to the
63 % FJ solver, not rejected).
64 sn = self.model.getStruct();
65 switch method
66 case 'mna'
67 if ~sn_is_open_model(sn) && ~sn_is_closed_model(sn)
68 bool = false;
69 reason = 'The mna method does not support mixed open/closed models.';
70 return;
71 end
72 % mna decomposes INTER-station arrival flows: each class is
73 % assumed to flow between stations, generating an arrival
74 % process to fit at each queue. A self-looping class is
75 % confined to a single queueing station, so it has no
76 % inter-station flow and its "arrival" MMAP degenerates to
77 % an absorbing generator (ctmc_solve then errors with "no
78 % recurrent state" inside map_lambda). Reject rather than
79 % return the ~1/FineTol garbage the fit produced before.
80 Vmna = cellsum(sn.visits);
81 for kmna = 1:sn.nclasses
82 % Only a CLOSED class is self-looping: an open class
83 % confined to one queue has an external Poisson source
84 % that mna decomposes normally. njobs is Inf for open.
85 if ~isfinite(sn.njobs(kmna))
86 continue;
87 end
88 vis = find(Vmna(:, kmna) > GlobalConstants.FineTol);
89 if isscalar(vis) && sn.sched(vis) ~= SchedStrategy.INF ...
90 && sn.sched(vis) ~= SchedStrategy.EXT
91 bool = false;
92 reason = sprintf(['The mna method does not support self-looping ' ...
93 'classes (class %d is confined to station %d with no ' ...
94 'inter-station flow to decompose). Use the dec.source method.'], ...
95 kmna, vis);
96 return;
97 end
98 end
99 case 'ldqbd'
100 if sn.nclasses ~= 1
101 bool = false;
102 reason = 'The ldqbd method requires a single-class model.';
103 return;
104 end
105 case {'inap','inapplus','inapinf','exact'}
106 % RCAT builds one scalar birth-death chain per
107 % (station,class) from sn.rates alone (build_rcat in
108 % solver_mam_ag): the state is the queue length, with no
109 % service-phase dimension. Every process is therefore
110 % collapsed to its mean rate, so a non-exponential arrival
111 % or service process would be answered as if it were
112 % exponential. Measured vs SolverCTMC on an open M/PH/1,
113 % INAP returns the M/M/1 queue length for EVERY scv
114 % (1.000000 at rho=0.5, 4.000000 at rho=0.8): 14% error at
115 % scv=0.5 and 43% at scv=4.0. Reject rather than
116 % mis-answer; use dec.source for non-exponential models.
117 nonExp = (sn.procid ~= ProcessType.EXP) & isfinite(sn.rates) & (sn.rates > 0);
118 % A signal class has no queue process of its own: build_rcat
119 % skips it in processMap and reads only its Source arrival
120 % rate, so its SERVICE process at a queue station is never
121 % used. It is a trigger, and models commonly declare it
122 % Immediate, so exempt those entries (the signal's arrival
123 % process at the Source is still required to be exponential).
124 if isfield(sn, 'issignal') && ~isempty(sn.issignal) && any(sn.issignal)
125 issource = false(size(nonExp, 1), 1);
126 for ist = 1:numel(issource)
127 issource(ist) = sn.nodetype(sn.stationToNode(ist)) == NodeType.Source;
128 end
129 nonExp(~issource, logical(sn.issignal(:))') = false;
130 end
131 if any(nonExp(:))
132 [ist, r] = find(nonExp, 1);
133 bool = false;
134 reason = sprintf(['The %s method supports exponential processes only ' ...
135 '(RCAT models each station-class by its mean rate, with no service-phase ' ...
136 'dimension), but station %d class %d is %s. Use the dec.source method ' ...
137 'for non-exponential models.'], method, ist, r, ...
138 ProcessType.toText(sn.procid(ist, r)));
139 return;
140 end
141 % build_rcat never reads sn.nservers: every station is
142 % modelled as a SINGLE server, with departure rate mu rather
143 % than min(n,c)*mu. For c > 1 the isolated chain is then
144 % driven at rho = lambda/mu instead of lambda/(c*mu), so a
145 % perfectly stable station becomes unstable in isolation and
146 % pins against the maxStates truncation. Measured vs
147 % SolverCTMC: open M/M/2 rho=0.6 gives 94.000001 instead of
148 % 1.875000, M/M/3 gives 97.750000 instead of 2.332117 (~50x).
149 % Reject rather than mis-answer.
150 multi = isfinite(sn.nservers) & (sn.nservers > 1);
151 if any(multi(:))
152 ist = find(multi, 1);
153 bool = false;
154 reason = sprintf(['The %s method supports single-server stations only ' ...
155 '(RCAT does not model sn.nservers, so a multiserver station is driven ' ...
156 'at rho = lambda/mu instead of lambda/(c*mu)), but station %d has %d ' ...
157 'servers. Use the dec.source method for multiserver models.'], ...
158 method, ist, sn.nservers(ist));
159 return;
160 end
161 end
162 [bool, reason] = supportsModelMethod@NetworkSolver(self, method);
163 end
164end
165
166 methods (Static)
167
168
169 function featSupported = getFeatureSet()
170 % FEATSUPPORTED = GETFEATURESET()
171
172 featSupported = SolverFeatureSet;
173 % MAM features
174 featSupported.setTrue({'Sink','Source',...
175 'Fork','Join','Forker','Joiner',... % Fork-Join support (via FJ_codes)
176 'Delay','DelayStation','Queue',...
177 'APH','Coxian','Erlang','Exp','HyperExp','MMPP2','MAP','MMAP','DMAP','ME','RAP',...
178 'Det','Gamma','Lognormal','Pareto','Uniform','Weibull',...
179 'StatelessClassSwitcher','InfiniteServer',...
180 'ClassSwitch', ...
181 'SharedServer','Buffer','Dispatcher',...
182 'Server','JobSink','RandomSource','ServiceTunnel',...
183 'SchedStrategy_INF','SchedStrategy_PS','SchedStrategy_HOL',...
184 'SchedStrategy_FCFSPRPRIO',... % solver_mam_basic: MMAPPH1PRPR
185 'SchedStrategy_FCFS',...
186 'RoutingStrategy_PROB','RoutingStrategy_RAND',...
187 'ClosedClass','SelfLoopingClass',...
188 'OpenClass'});
189 % Add RCAT (AG) features
190 featSupported.setTrue({'Sink', 'Source', ...
191 'Fork','Join','Forker','Joiner',... % Fork-Join support (via FJ_codes)
192 'Delay', 'DelayStation', 'Queue', ...
193 'APH', 'Coxian', 'Erlang', 'Exp', 'HyperExp', ...
194 'Det','Gamma','Lognormal','Pareto','Uniform','Weibull',...
195 'StatelessClassSwitcher', 'InfiniteServer', ...
196 'SharedServer', 'Buffer', 'Dispatcher', ...
197 'Server', 'JobSink', 'RandomSource', 'ServiceTunnel', ...
198 'SchedStrategy_INF', 'SchedStrategy_PS', ...
199 'SchedStrategy_FCFS', ...
200 'RoutingStrategy_PROB', 'RoutingStrategy_RAND', ...
201 'ClosedClass', ...
202 'OpenClass', ...
203 'OpenSignal', 'ClosedSignal', ... % G-network signals (solver_mam_ag)
204 'SignalType_NEGATIVE', 'SignalType_CATASTROPHE', ...
205 'SignalBatchRemoval'}); % AG reads sn.signalremdist
206 % Add BMAP/PH/N/N retrial queue features
207 featSupported.setTrue({'Retrial', 'BMAP', 'PH'});
208 % Setup/delay-off: open stations are solved exactly by
209 % qbd_setupdelayoff; closed stations use the per-instance
210 % cold-start race of the isfunction branch.
211 featSupported.setTrue({'SetupDelayOff'});
212 end
213
214 function [bool, featSupported] = supports(model)
215 % [BOOL, FEATSUPPORTED] = SUPPORTS(MODEL)
216
217 featUsed = model.getUsedLangFeatures();
218 featSupported = SolverMAM.getFeatureSet();
219 bool = SolverFeatureSet.supports(featSupported, featUsed);
220 end
221
222 function options = defaultOptions()
223 % OPTIONS = DEFAULTOPTIONS()
224 options = SolverOptions('MAM');
225 end
226
227 function libs = getLibrariesUsed(sn, options)
228 % GETLIBRARIESUSED Get list of external libraries used by MAM solver
229 % Detect libraries used by MAM solver based on topology and method
230 libs = {};
231
232 % MAMSolver used for matrix-analytic methods (M/G/1, GI/M/1 types)
233 % This includes default and decomposition methods
234 if ismember(options.method, {'default', 'dec.source', 'dec.mmap', 'dec.poisson', 'dec.source.mmap'})
235 libs{end+1} = 'MAMSolver';
236 end
237
238 % Q-MAM used for specific RCAT-based methods
239 if ismember(options.method, {'mna', 'inap', 'inapplus', 'inapinf'})
240 libs{end+1} = 'Q-MAM';
241 end
242
243 % SMCSolver used for QBD (Quasi-Birth-Death) analysis
244 % Currently available but not actively used in default paths
245 % Uncomment when QBD methods are activated:
246 % if ismember(options.method, {'qbd'})
247 % libs{end+1} = 'SMCSolver';
248 % end
249
250 % BUTools usage detection (via KPCToolbox MAP functions)
251 % BUTools is used when analyzing MAP/PH distributions
252 if ~isempty(sn) && isfield(sn, 'proc') && ~isempty(sn.proc) && any(~cellfun(@isempty, sn.proc(:)))
253 libs{end+1} = 'BUTools';
254 end
255
256 % Remove duplicates and maintain order
257 libs = unique(libs, 'stable');
258 end
259 end
260end
Definition Station.m:245