LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_analyzer.m
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)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7Tstart = tic;
8
9% Exact fast-path: correlated single-class MAP/MAP/1; see _kb/06-solver-catalog.md for rationale
10[isMapMap1, QN, UN, RN, TN, CN, XN] = solver_mam_mapmap1_exact(sn);
11if isMapMap1
12 method = 'exact.mapmap1';
13 totiter = 1;
14 percResults = [];
15 runtime = toc(Tstart);
16 return;
17end
18
19% Preserve deterministic distributions for exact MAP/D/c analysis
20if ~isfield(options, 'config')
21 options.config = struct();
22end
23if ~isfield(options.config, 'preserveDet')
24 options.config.preserveDet = true; % Enable exact MAP/D/c solver
25end
26
27% Convert non-Markovian distributions to PH (Det preserved if preserveDet=true)
28sn = sn_nonmarkov_toph(sn, options);
29
30% Check if the model is mixed (has both open and closed classes)
31isOpen = sn_is_open_model(sn);
32isClosed = sn_is_closed_model(sn);
33isMixed = isOpen && isClosed;
34
35line_debug('MAM analyzer starting: method=%s, isOpen=%d, isClosed=%d', options.method, isOpen, isClosed);
36
37% Mixed models are supported by the dec.source method
38
39if nargin<2 || isempty(options.config) || ~isfield(options.config,'merge')
40 % Preserve existing config fields (like preserveDet) while setting defaults
41 if ~isfield(options, 'config') || isempty(options.config)
42 options.config = struct();
43 end
44 if ~isfield(options.config, 'merge')
45 options.config.merge = 'super';
46 end
47 if ~isfield(options.config, 'compress')
48 options.config.compress = 'mixture.order1';
49 end
50 if ~isfield(options.config, 'space_max')
51 options.config.space_max = 128;
52 end
53 if ~isfield(options.config, 'etaqa_trunc')
54 options.config.etaqa_trunc = 8;
55 end
56end
57
58method = options.method;
59percResults = []; % Initialize as empty
60
61switch options.method
62 case 'dec.mmap'
63 % service distribution per class scaled by utilization used as
64 % departure process
65 line_debug('Using dec.mmap method, calling solver_mam');
66 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam(sn, options);
67 case {'default', 'dec.source'}
68 % Check if network is a valid Fork-Join topology for FJ_codes
69 [isHomogeneous, fjInfo] = fj_is_homogeneous(sn);
70
71 if isHomogeneous
72 % Use FJ_codes for Fork-Join analysis
73 if strcmpi(options.method, 'default')
74 line_debug('Default method: using FJ_codes for Fork-Join topology\n');
75 end
76 line_debug('Detected Fork-Join topology, using FJ_codes method');
77 [QN,UN,RN,TN,CN,XN,totiter,percResults] = solver_mam_fj(sn, options);
78 method = 'qiu';
79 elseif sn_has_fork_join(sn) && sn_is_open_model(sn)
80 % Use MMAP-based FJ decomposition with mmap_max synchronization
81 line_debug('Detected general Fork-Join topology, using dec.source.mmap method');
82 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_mmap(sn, options);
83 method = 'dec.source.mmap';
84 else
85 % Check if network is a valid BMAP/PH/N/N bufferless retrial queue
86 [isRetrial, ~] = qsys_is_retrial(sn);
87
88 % Check if network has reneging (queue abandonment) for MAPMsG
89 isReneging = hasRenegingPatience(sn);
90
91 if isRetrial
92 % Use BMAP/PH/N/N retrial solver
93 if strcmpi(options.method, 'default')
94 line_debug('Default method: using retrial for BMAP/PH/N/N bufferless topology\n');
95 end
96 line_debug('Detected BMAP/PH/N/N retrial topology, using retrial method');
97 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_retrial(sn, options);
98 method = 'retrial';
99 elseif isReneging
100 % Use MAP/M/s+G reneging solver (MAPMsG)
101 if strcmpi(options.method, 'default')
102 line_debug('Default method: using MAPMsG for MAP/M/s+G with reneging\n');
103 end
104 line_debug('Detected reneging topology, using MAPMsG method');
105 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_retrial(sn, options);
106 method = 'reneging';
107 elseif strcmpi(options.method, 'default') && isClosedDelayQueue(sn)
108 % Single-class closed Delay+Queue: LDQBD is exact; see _kb/06-solver-catalog.md for rationale
109 line_debug('Default method: using LDQBD for single-class closed Delay/Queue\n');
110 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_ldqbd(sn, options);
111 method = 'ldqbd';
112 else
113 % arrival process per chain rescaled by visits at each node
114 if strcmpi(options.method, 'default')
115 line_debug('Default method: using dec.source\n');
116 end
117 line_debug('Using default/dec.source method, calling solver_mam_basic');
118 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic(sn, options);
119 method = 'dec.source';
120 end
121 end
122 case 'dec.poisson'
123 % analyze the network with Poisson streams
124 line_debug('Using dec.poisson method with space_max=1, calling solver_mam_basic');
125 options.config.space_max = 1;
126 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic(sn, options);
127 case 'mna'
128 if sn_is_open_model(sn)
129 line_debug('Using MNA method for open model, calling solver_mna_open');
130 [QN,UN,RN,TN,CN,XN,~,totiter] = solver_mna_open(sn, options);
131 elseif sn_is_closed_model(sn)
132 line_debug('Using MNA method for closed model, calling solver_mna_closed');
133 [QN,UN,RN,TN,CN,XN,~,totiter] = solver_mna_closed(sn, options);
134 else
135 line_error(mfilename,'The mna method in SolverMAM does not support mixed models.');
136 end
137 case 'ldqbd'
138 % Level-Dependent QBD method for single-class Delay/Queue networks:
139 % closed (finite population) or open (Poisson arrivals, truncated).
140 if sn.nclasses ~= 1
141 line_error(mfilename,'The ldqbd method requires a single-class model.');
142 end
143 line_debug('Using LDQBD method, calling solver_mam_ldqbd');
144 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_ldqbd(sn, options);
145 case {'inap', 'inapplus', 'inapinf', 'exact'}
146 % RCAT methods (formerly SolverAG) per Marin, Rota Bulo, Balsamo (INAP,
147 % MASCOTS 2012); see _kb/06-solver-catalog.md for rationale
148 line_debug('Using RCAT method: %s', options.method);
149 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_ag(sn, options);
150 case 'dec.source.mmap'
151 % MMAP-based FJ decomposition with mmap_max synchronization
152 line_debug('Using dec.source.mmap method, calling solver_mam_basic_mmap');
153 [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_mmap(sn, options);
154 otherwise
155 line_error(mfilename,'Unknown method: %s', options.method);
156end
157
158for i=1:sn.nstations
159 switch sn.sched(i)
160 case SchedStrategy.EXT
161 TN(i,:) = sn.rates(i,:);
162 end
163end
164
165QN(isnan(QN))=0;
166CN(isnan(CN))=0;
167RN(isnan(RN))=0;
168UN(isnan(UN))=0;
169XN(isnan(XN))=0;
170TN(isnan(TN))=0;
171
172runtime = toc(Tstart);
173end
174
175function isLdqbd = isClosedDelayQueue(sn)
176% ISCLOSEDDELAYQUEUE Check if model is a single-class closed Delay+Queue
177%
178% Returns true for the exact regime of solver_mam_ldqbd: one class, finite
179% population, exactly two stations, one INF (Delay) and one FCFS (Queue).
180% The open Source+Queue regime of solver_mam_ldqbd is deliberately excluded
181% here: it truncates the level space at options.cutoff, so it is not
182% unconditionally preferable to dec.source and stays opt-in.
183
184isLdqbd = false;
185
186if sn.nclasses ~= 1 || sn.nstations ~= 2
187 return;
188end
189
190if ~all(isfinite(sn.njobs))
191 return;
192end
193
194nDelay = sum(sn.sched == SchedStrategy.INF);
195nQueue = sum(sn.sched == SchedStrategy.FCFS);
196
197isLdqbd = (nDelay == 1) && (nQueue == 1);
198
199end
200
201function isReneging = hasRenegingPatience(sn)
202% HASRENEGINGPATIENCE Check if model has reneging/patience configured
203%
204% Returns true if any queue station has reneging (ImpatienceType.RENEGING)
205% configured with a patience distribution.
206
207isReneging = false;
208
209% Check if impatienceClass field exists (ImpatienceType: RENEGING, BALKING)
210if ~isfield(sn, 'impatienceClass') || isempty(sn.impatienceClass)
211 return;
212end
213
214% Check if patienceProc field exists
215if ~isfield(sn, 'patienceProc') || isempty(sn.patienceProc)
216 return;
217end
218
219% Check each station for reneging configuration
220for ist = 1:sn.nstations
221 for r = 1:sn.nclasses
222 if sn.impatienceClass(ist, r) == ImpatienceType.RENEGING
223 if ~isempty(sn.patienceProc{ist, r})
224 isReneging = true;
225 return;
226 end
227 end
228 end
229end
230
231end