1function [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_mva_analyzer(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME] = SOLVER_MVA_ANALYZER(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
8% Convergence flag as reported by the handler; [] means it reports none,
9% in which
case the count-vs-budget test below applies instead.
12method = options.method;
13method = regexprep(method,
'^amva\.',
'');
15line_debug(options,
'MVA analyzer starting: method=%s, nclasses=%d, njobs=%s, nchains=%d', method, sn.nclasses, mat2str(sn.njobs), sn.nchains);
19 line_debug(options,
'Using exact MVA method, calling solver_mva');
20 [Q,U,R,T,C,X,lG] = solver_mva(sn, options);
22 line_debug(options,
'Using exact MVAC method, calling solver_mvac');
23 [Q,U,R,T,C,X,lG] = solver_mvac(sn, options);
25 line_debug(options,
'Using BAS method, calling solver_sqd');
26 [Q,U,R,T,C,X,lG,iter] = solver_sqd(sn, options);
28 line_debug(options,
'Using QNA method, calling solver_qna');
29 [Q,U,R,T,C,X] = solver_qna(sn, options);
32 line_debug(options,
'Using summation method, calling solver_mva_sum');
33 [Q,U,R,T,C,X,lG,iter] = solver_mva_sum(sn, options);
35 line_debug(options,
'Using RQNA method, calling solver_rqna');
36 [Q,U,R,T,C,X] = solver_rqna(sn, options);
39 %
for non-exponential open queueing networks, use qna
40 % (commented as not ready yet, it fails on example_cacheModel_3.m)
41 %
if all(isinf(sn.njobs)) && any(any(sn.scv ~= 1.0))
42 % line_warning(mfilename,
'QNA implementation is still in beta version.')
43 % [Q,U,R,T,C,X] = solver_qna(sn, options);
46 % see _kb/06-solver-catalog.md (MVA section)
for the
default-dispatch rules
47 if sn.nclasses == 1 && all(isinf(sn.njobs)) && sn_has_bursty_arrival(sn)
48 line_debug(
'Default method: bursty single-class open network, using RQNA\n');
49 line_debug(options,
'Non-renewal arrivals detected, calling solver_rqna');
50 [Q,U,R,T,C,X] = solver_rqna(sn, options);
53 % Closed single-chain Blocking-After-Service network (finite buffers)
55 line_debug(
'Default method: using BAS for blocking-after-service model\n');
56 line_debug(options,
'Model has Blocking-After-Service finite buffers, calling solver_sqd');
57 [Q,U,R,T,C,X,lG,iter] = solver_sqd(sn, options);
59 % Force AMVA
for class- or joint-dependent models - exact MVA doesn
't support them
60 elseif ~isempty(sn.cdscaling) || ~isempty(sn.jdscaling)
61 line_debug('Default method:
using AMVA
for class-/joint-dependent model\n
');
62 line_debug(options, 'Model has
class/joint dependence, calling solver_amva
');
63 [Q,U,R,T,C,X,lG,iter,method,converged] = solver_amva(sn, options);
64 elseif any(isinf(sn.njobs)) && any(isfinite(sn.njobs) & sn.njobs > 0) && max(sn.nservers(isfinite(sn.nservers))) == 1 && sn_has_product_form(sn) && all(sn.njobs(isfinite(sn.njobs)) == floor(sn.njobs(isfinite(sn.njobs))))
65 % see _kb/06-solver-catalog.md (MVA section) for the default-dispatch rules
66 line_debug('Default method:
using exact mixed MVA\n
');
67 [Q,U,R,T,C,X,lG] = solver_mva(sn, options);
69 elseif sn.nchains <= 4 && sum(sn.njobs) <= 20 && sn_has_product_form(sn) && ~sn_has_fractional_populations(sn)
70 line_debug('Default method:
using exact MVA\n
');
71 % The parameters above take in the worst case a handful of ms
72 line_debug(options, 'Model qualifies
for exact MVA (nchains=%d, njobs=%d, product-
form=%d), calling solver_mva
', sn.nchains, sum(sn.njobs), sn_has_product_form(sn));
73 [Q,U,R,T,C,X,lG] = solver_mva(sn, options);
76 line_debug('Default method:
using approximate MVA\n
');
77 line_debug(options, 'Model
requires approximation (nchains=%d, njobs=%d, product-
form=%d), calling solver_amva
', sn.nchains, sum(sn.njobs), sn_has_product_form(sn));
78 [Q,U,R,T,C,X,lG,iter,method,converged] = solver_amva(sn, options);
81 case {'amva
','bs
','qd
','qli
','fli
','lin
','qdlin
','sqni
','egflin
','gflin
','ab
','schmidt
','schmidt-ext
'} %,'aql
','qdaql
'
82 line_debug(options, 'Using approximate MVA method: %s, calling solver_amva
', method);
83 [Q,U,R,T,C,X,lG,iter,~,converged] = solver_amva(sn, options);
85 % An unsupported method must error by name (returning empty metrics let the
86 % caller index into [] with an opaque message). 'aql
'/'qdaql
' exist in
87 % solver_amva but are deliberately not dispatched here.
88 line_error(mfilename, sprintf(['The
''%s
'' method
is not dispatched by
' ...
89 'solver_mva_analyzer. Supported:
default, exact, mva, mvac, amva, bs, qd, qli, fli,
' ...
90 'lin, qdlin, sqni, egflin, gflin, ab, schmidt, schmidt-ext.
'], method));
94% see _kb/06-solver-catalog.md (MVA section) for AMVA convergence flag-vs-count
95iterBudget = min(options.iter_max, 10000);
97 nonConverged = ~converged;
99 nonConverged = ~isempty(iter) && isscalar(iter) && ~isnan(iter) && isfinite(iter) && iter >= iterBudget;
102 line_warning_always(mfilename, ...
103 'AMVA method
''%s
'' did not meet the convergence tolerance %g after %d iterations; the returned metrics may not be converged. Try another method (e.g.
''qd
'' or
''bs
''), raise options.iter_max, or loosen options.iter_tol.
', ...
104 method, options.iter_tol, iter);
108 %line_printf('\nMVA analysis completed. Runtime: %f seconds.\n
',runtime);
113function tf = isBasModel(sn)
114% Detect a closed single-chain network with Blocking-After-Service (BAS) finite-buffer
115% blocking, which solver_sqd handles but exact/AMVA MVA does not.
117if sn.nchains ~= 1 || sn.nclosedjobs <= 0 || isempty(sn.droprule)
120if any(isinf(sn.njobs))
121 return; % open class present
123tf = any(sn.droprule(:) == DropStrategy.BAS);