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