1classdef SolverMAM < NetworkSolver
2 % Matrix-Analytic and RCAT methods solver
4 % Implements matrix-analytic methods and RCAT
for structured Markov chain analysis.
6 % Copyright (c) 2012-2026, Imperial College London
10 function self = SolverMAM(model,varargin)
11 % SOLVERMAM Create a Matrix-Analytic Methods solver instance
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
18 self@NetworkSolver(model, mfilename);
19 self.setOptions(Solver.parseOptions(varargin, self.defaultOptions));
23 function sn = getStruct(self)
26 % Get data structure summarizing the model
27 sn = self.model.getStruct(
true);
30 runtime = runAnalyzer(self, options);
31 RD = getCdfRespT(self, R);
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'};
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.
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
53 featSupported = SolverMAM.getFeatureSet();
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();
67 if ~sn_is_open_model(sn) && ~sn_is_closed_model(sn)
69 reason = 'The mna method does not support mixed open/closed models.';
72 % mna rejects self-looping classes (no inter-station flow);
73 % see _kb/06-solver-catalog.md for rationale
75 for kmna = 1:sn.nclasses
76 % Only a CLOSED class
is self-looping (njobs<Inf); see _kb/06-solver-catalog.md for rationale
77 if ~isfinite(sn.njobs(kmna))
80 vis = find(Vmna(:, kmna) > GlobalConstants.FineTol);
81 if isscalar(vis) && sn.sched(vis) ~= SchedStrategy.INF ...
82 && sn.sched(vis) ~= SchedStrategy.EXT
84 reason = sprintf(['The mna method does not support self-looping ' ...
85 'classes (class %d
is confined to station %d with no ' ...
86 'inter-station flow to decompose). Use the dec.source method.'], ...
94 reason = 'The ldqbd method requires a single-class model.';
97 case {
'inap',
'inapplus',
'inapinf',
'exact'}
98 % RCAT collapses each process to its mean rate: reject
99 % non-exponential; see _kb/06-solver-catalog.md
for rationale
100 nonExp = (sn.procid ~= ProcessType.EXP) & isfinite(sn.rates) & (sn.rates > 0);
101 % Exempt signal service entries from the nonExp mask;
102 % see _kb/06-solver-catalog.md
for rationale
103 if isfield(sn,
'issignal') && ~isempty(sn.issignal) && any(sn.issignal)
104 issource = false(size(nonExp, 1), 1);
105 for ist = 1:numel(issource)
106 issource(ist) = sn.nodetype(sn.stationToNode(ist)) == NodeType.Source;
108 nonExp(~issource, logical(sn.issignal(:))') = false;
111 [ist, r] = find(nonExp, 1);
113 reason = sprintf(['The %s method supports exponential processes only ' ...
114 '(RCAT models each station-class by its mean rate, with no service-phase ' ...
115 'dimension), but station %d class %d
is %s. Use the dec.source method ' ...
116 'for non-exponential models.'], method, ist, r, ...
117 ProcessType.toText(sn.procid(ist, r)));
120 % RCAT models every station single-server: reject multi-server;
121 % see _kb/06-solver-catalog.md for rationale
122 multi = isfinite(sn.nservers) & (sn.nservers > 1);
124 ist = find(multi, 1);
126 reason = sprintf(['The %s method supports single-server stations only ' ...
127 '(RCAT does not model sn.nservers, so a multiserver station
is driven ' ...
128 'at rho = lambda/mu instead of lambda/(c*mu)), but station %d has %d ' ...
129 'servers. Use the dec.source method for multiserver models.'], ...
130 method, ist, sn.nservers(ist));
134 [
bool, reason] = supportsModelMethod@NetworkSolver(self, method);
141 function featSupported = getFeatureSet()
142 % FEATSUPPORTED = GETFEATURESET()
144 featSupported = SolverFeatureSet;
146 featSupported.setTrue({
'Sink',
'Source',...
147 'Fork',
'Join',
'Forker',
'Joiner',... % Fork-Join support (via FJ_codes)
148 'Delay',
'DelayStation',
'Queue',...
149 'APH',
'Coxian',
'Erlang',
'Exp',
'HyperExp',
'MMPP2',
'MAP',
'MMAP',
'DMAP',
'ME',
'RAP',...
150 'Det',
'Gamma',
'Lognormal',
'Pareto',
'Uniform',
'Weibull',...
151 'StatelessClassSwitcher',
'InfiniteServer',...
153 'SharedServer',
'Buffer',
'Dispatcher',...
154 'Server',
'JobSink',
'RandomSource',
'ServiceTunnel',...
155 'SchedStrategy_INF',
'SchedStrategy_PS',
'SchedStrategy_HOL',...
156 'SchedStrategy_FCFSPRPRIO',... % solver_mam_basic: MMAPPH1PRPR
157 'SchedStrategy_FCFS',...
158 'RoutingStrategy_PROB',
'RoutingStrategy_RAND',...
159 'ClosedClass',
'SelfLoopingClass',...
161 % Add RCAT (AG) features
162 featSupported.setTrue({
'Sink',
'Source', ...
163 'Fork',
'Join',
'Forker',
'Joiner',... % Fork-Join support (via FJ_codes)
164 'Delay',
'DelayStation',
'Queue', ...
165 'APH',
'Coxian',
'Erlang',
'Exp',
'HyperExp', ...
166 'Det',
'Gamma',
'Lognormal',
'Pareto',
'Uniform',
'Weibull',...
167 'StatelessClassSwitcher',
'InfiniteServer', ...
168 'SharedServer',
'Buffer',
'Dispatcher', ...
169 'Server',
'JobSink',
'RandomSource',
'ServiceTunnel', ...
170 'SchedStrategy_INF',
'SchedStrategy_PS', ...
171 'SchedStrategy_FCFS', ...
172 'RoutingStrategy_PROB',
'RoutingStrategy_RAND', ...
175 'OpenSignal',
'ClosedSignal', ... % G-network signals (solver_mam_ag)
176 'SignalType_NEGATIVE',
'SignalType_CATASTROPHE', ...
177 'SignalBatchRemoval'}); % AG reads sn.signalremdist
178 % Add BMAP/PH/N/N retrial queue features
179 featSupported.setTrue({
'Retrial',
'BMAP',
'PH'});
180 % Setup/delay-off: open stations are solved exactly by
181 % qbd_setupdelayoff; closed stations use the per-instance
182 % cold-start race of the isfunction branch.
183 featSupported.setTrue({
'SetupDelayOff'});
186 function [bool, featSupported] = supports(model)
187 % [BOOL, FEATSUPPORTED] = SUPPORTS(MODEL)
189 featUsed = model.getUsedLangFeatures();
190 featSupported = SolverMAM.getFeatureSet();
191 bool = SolverFeatureSet.supports(featSupported, featUsed);
194 function options = defaultOptions()
195 % OPTIONS = DEFAULTOPTIONS()
196 options = SolverOptions('MAM');
199 function libs = getLibrariesUsed(sn, options)
200 % GETLIBRARIESUSED Get list of external libraries used by MAM solver
201 % Detect libraries used by MAM solver based on topology and method
204 % MAMSolver used
for matrix-analytic methods (M/G/1, GI/M/1 types)
205 % This includes
default and decomposition methods
206 if ismember(options.method, {
'default',
'dec.source',
'dec.mmap',
'dec.poisson',
'dec.source.mmap'})
207 libs{end+1} =
'MAMSolver';
210 % Q-MAM used
for specific RCAT-based methods
211 if ismember(options.method, {
'mna',
'inap',
'inapplus',
'inapinf'})
212 libs{end+1} =
'Q-MAM';
215 % SMCSolver used
for QBD (Quasi-Birth-Death) analysis
216 % Currently available but not actively used in
default paths
217 % Uncomment when QBD methods are activated:
218 %
if ismember(options.method, {
'qbd'})
219 % libs{end+1} =
'SMCSolver';
222 % BUTools usage detection (via KPCToolbox MAP functions)
223 % BUTools
is used when analyzing MAP/PH distributions
224 if ~isempty(sn) && isfield(sn,
'proc') && ~isempty(sn.proc) && any(~cellfun(@isempty, sn.proc(:)))
225 libs{end+1} =
'BUTools';
228 % Remove duplicates and maintain order
229 libs = unique(libs,
'stable');