1function [QN,UN,RN,TN,CN,XN,runtime,method,totiter,percResults] = solver_mam_analyzer(sn, options)
2% [QN,UN,RN,TN,CN,XN,RUNTIME,METHOD] = SOLVER_MAM_ANALYZER(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
9% Preserve deterministic distributions
for exact MAP/D/c analysis
10if ~isfield(options,
'config')
11 options.config = struct();
13if ~isfield(options.config, 'preserveDet')
14 options.config.preserveDet = true; % Enable exact MAP/D/c solver
17% Convert non-Markovian distributions to PH (Det preserved if preserveDet=true)
18sn = sn_nonmarkov_toph(sn, options);
20% Check if the model
is mixed (has both open and closed
classes)
21isOpen = sn_is_open_model(sn);
22isClosed = sn_is_closed_model(sn);
23isMixed = isOpen && isClosed;
25line_debug('MAM analyzer starting: method=%s, isOpen=%d, isClosed=%d', options.method, isOpen, isClosed);
27% Mixed models are supported by the dec.source method
29if nargin<2 || isempty(options.config) || ~isfield(options.config,'merge')
30 % Preserve existing config fields (like preserveDet) while setting defaults
31 if ~isfield(options, 'config') || isempty(options.config)
32 options.config = struct();
34 if ~isfield(options.config, 'merge')
35 options.config.merge = 'super';
37 if ~isfield(options.config, 'compress')
38 options.config.compress = 'mixture.order1';
40 if ~isfield(options.config, 'space_max')
41 options.config.space_max = 128;
45method = options.method;
46percResults = []; % Initialize as empty
50 % service distribution per class scaled by utilization used as
52 line_debug('Using dec.mmap method, calling solver_mam');
53 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam(sn, options);
54 case {
'default',
'dec.source'}
55 % Check
if network
is a valid Fork-Join topology
for FJ_codes
56 [isFJ, fjInfo] = fj_isfj(sn);
59 % Use FJ_codes
for Fork-Join analysis
60 if strcmpi(options.method,
'default')
61 line_debug('Default method: using FJ_codes for Fork-Join topology\n');
63 line_debug('Detected Fork-Join topology, using FJ_codes method');
64 [QN,UN,RN,TN,CN,XN,totiter,percResults] = solver_mam_fj(sn, options);
66 elseif sn_has_fork_join(sn) && sn_is_open_model(sn)
67 % Use
MMAP-based FJ decomposition with mmap_max synchronization
68 line_debug('Detected general Fork-Join topology, using dec.source.fj method');
69 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_fj(sn, options);
70 method = 'dec.source.fj';
72 % Check if network
is a valid BMAP/PH/N/N bufferless retrial queue
73 [isRetrial, ~] = qsys_is_retrial(sn);
75 % Check if network has reneging (queue abandonment) for MAPMSG
76 isReneging = hasRenegingPatience(sn);
79 % Use BMAP/PH/N/N retrial solver
80 if strcmpi(options.method, 'default')
81 line_debug('Default method: using retrial for BMAP/PH/N/N bufferless topology\n');
83 line_debug('Detected BMAP/PH/N/N retrial topology, using retrial method');
84 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_retrial(sn, options);
87 % Use MAP/M/s+G reneging solver (MAPMSG)
88 if strcmpi(options.method, 'default')
89 line_debug('Default method: using MAPMSG for MAP/M/s+G with reneging\n');
91 line_debug('Detected reneging topology, using MAPMSG method');
92 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_retrial(sn, options);
95 % arrival process per chain rescaled by
visits at each node
96 if strcmpi(options.method, 'default')
97 line_debug('Default method: using dec.source\n');
99 line_debug('Using default/dec.source method, calling solver_mam_basic');
100 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic(sn, options);
101 method = 'dec.source';
105 % analyze the network with Poisson streams
106 line_debug('Using dec.poisson method with space_max=1, calling solver_mam_basic');
107 options.config.space_max = 1;
108 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic(sn, options);
110 if sn_is_open_model(sn)
111 line_debug('Using MNA method for open model, calling solver_mna_open');
112 [QN,UN,RN,TN,CN,XN,~,totiter] = solver_mna_open(sn, options);
113 elseif sn_is_closed_model(sn)
114 line_debug('Using MNA method for closed model, calling solver_mna_closed');
115 [QN,UN,RN,TN,CN,XN,~,totiter] = solver_mna_closed(sn, options);
117 line_error(mfilename,'The mna method in SolverMAM does not support mixed models.');
120 % Level-Dependent QBD method for single-class closed networks
121 if ~sn_is_closed_model(sn)
122 line_error(mfilename,'The ldqbd method requires a closed model.');
125 line_error(mfilename,'The ldqbd method requires a single-class model.');
127 line_debug('Using LDQBD method for closed model, calling solver_mam_ldqbd');
128 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_ldqbd(sn, options);
129 case {
'inap',
'inapplus',
'exact'}
130 % RCAT-based methods (formerly SolverAG)
131 line_debug(
'Using RCAT method: %s', options.method);
132 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_ag(sn, options);
134 %
MMAP-based FJ decomposition with mmap_max synchronization
135 line_debug(
'Using dec.source.fj method, calling solver_mam_basic_fj');
136 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_fj(sn, options);
138 line_error(mfilename,
'Unknown method: %s', options.method);
143 case SchedStrategy.EXT
144 TN(i,:) = sn.rates(i,:);
155runtime = toc(Tstart);
158function isReneging = hasRenegingPatience(sn)
159% HASRENEGINGPATIENCE Check
if model has reneging/patience configured
161% Returns
true if any queue station has reneging (ImpatienceType.RENEGING)
162% configured with a patience distribution.
166% Check
if impatienceClass field exists (ImpatienceType: RENEGING, BALKING)
167if ~isfield(sn,
'impatienceClass') || isempty(sn.impatienceClass)
171% Check
if patienceProc field exists
172if ~isfield(sn,
'patienceProc') || isempty(sn.patienceProc)
176% Check each station for reneging configuration
177for ist = 1:sn.nstations
178 for r = 1:sn.nclasses
179 if sn.impatienceClass(ist, r) == ImpatienceType.RENEGING
180 if ~isempty(sn.patienceProc{ist, r})