1classdef SolverMVA < NetworkSolver
2 % Mean Value Analysis solver
for queueing networks
4 % Implements MVA algorithms
for analyzing closed and open queueing networks.
6 % Copyright (c) 2012-2026, Imperial College London
10 function self = SolverMVA(model,varargin)
11 % SOLVERMVA Create an MVA solver instance
13 % @brief Creates a Mean Value Analysis solver
for the given model
14 % @param model Network model to be analyzed
15 % @param varargin Optional solver options (method, tolerance, etc.)
16 % @
return self SolverMVA instance configured with specified options
18 self@NetworkSolver(model, mfilename);
19 self.setOptions(Solver.parseOptions(varargin, SolverMVA.defaultOptions));
23 function sn = getStruct(self)
24 % GETSTRUCT Get model data structure
for analysis
26 % @brief Returns the internal data structure representing the model
27 % @
return sn Structured data representing the queueing network
28 sn = self.model.getStruct(
false);
31 function tf = supportsExactSensitivity(self) %#ok<MANU>
32 % TF = SUPPORTSEXACTSENSITIVITY()
33 % MVA differentiates its own recursion: getSensitivityTable uses
34 % the analytic branch (pfqn_sens) wherever the model
is in scope.
38 [runtime, analyzer] = runAnalyzer(self, options);
39 [lNormConst] = getProbNormConstAggr(self);
40 [Pnir,logPnir] = getProbAggr(self, ist);
41 [Pnir,logPn] = getProbSysAggr(self);
43 function [allMethods] = listValidMethods(self)
44 % LISTVALIDMETHODS Get all valid MVA solution methods
46 % @brief Returns cell array of valid MVA methods
for the current model
47 % @
return allMethods Cell array of method names available
for this model
49 sn = self.model.getStruct;
51 allMethods = {
'default',...
52 'mva',
'exact',
'amva', ...
54 'qdlin',
'amva.qdlin', ...
61 'schmidt',
'amva.schmidt', ...
62 'schmidt-ext',
'amva.schmidt-ext'};
64 % QNA/RQNA advertised
for fully open models only; see
65 % _kb/06-solver-catalog.md (MVA section)
for the
default-dispatch rules
66 if sn_is_open_model(sn)
67 allMethods = [allMethods(1:4), {
'qna',
'rqna'}, allMethods(5:end)];
70 % SQD (Smith Queue Decomposition)
is only valid
for closed
71 % single-chain Blocking-After-Service networks; kept at
this
72 % position to preserve BAS regression-baseline ordering.
73 if sn_is_bas_model(sn)
74 allMethods{end+1} =
'sqd'; %#ok<AGROW>
77 % MVAC (exact mean value analysis by chain, pfqn_mvac): closed
78 % single-server product-
form networks only; rejects open/mixed and
79 % multiserver at solve time.
80 if ~any(isinf(sn.njobs))
81 allMethods{end+1} =
'mvac'; %#ok<AGROW>
84 % Marie withheld
for open models and
for class-dependent routing;
85 % see _kb/06-solver-catalog.md (MVA section)
for the rationale
86 if ~sn_is_open_model(sn) && ~sn_has_classdep_routing(sn)
87 allMethods = [allMethods, {
'marie',
'amva.marie'}]; %#ok<AGROW>
90 allMethods = {allMethods{:}, ...
91 'lin',
'egflin',
'gflin',
'amva.lin'}; %#ok<CCAT>
93 % Bound methods (aba/bjb/gb/pb/sb/mwba) are NOT listed here: they
94 % moved to SolverBA, and runAnalyzer raises line_error
for the whole
95 % family. Listing them made
this a
false claim, since every listed
96 % name errored when run. See SolverBA.listValidMethods.
98 if sn_is_open_model(sn) && sn.nstations == 2 && sn.nclasses == 1
99 % methods to add
for queueing systems
100 qsys = {
'mm1',
'mmk',
'mg1',
'mgi1',
'gm1',
'gig1',
'gim1',
'gig1.kingman', ...
101 'gigk',
'gigk.kingman_approx', ...
102 'gig1.gelenbe',
'gig1.heyman',
'gig1.kimura',
'gig1.allen', ...
103 'gig1.kobayashi',
'gig1.klb',
'gig1.marchal'};
104 % append, keeping original order and avoiding duplicates
105 allMethods = {allMethods{:}, qsys{:}}; %#ok<CCAT>
109 function method = resolveMethod(self, options)
110 % Feature-driven resolution of options.method=
'default'. A bursty
111 % single-
class open network has a non-renewal (MAP/MMPP) arrival
112 % process whose autocorrelation a two-moment method cannot capture,
113 % so the
default dispatch selects RQNA (robust queueing network
114 % analyzer, indices of dispersion). All other cases keep
'default',
115 % which the analyzer expands with its own heuristics. Expressed as
116 % RQNA
's MAP-family coverage rather than a bespoke gate.
117 method = options.method;
118 if strcmp(options.method, 'default')
119 sn = self.model.getStruct();
120 if (sn.nclasses == 1) && all(isinf(sn.njobs)) && sn_has_bursty_arrival(sn)
126 function featSupported = getMethodFeatureSet(self, method) %#ok<INUSL>
127 % Per-method feature deltas applied to the base MVA envelope.
128 % RQNA natively consumes non-renewal MAP/MMPP/RAP arrival and
129 % service processes (open only); QNA is a two-moment open-network
130 % method. The queueing-system and bounds methods are already
131 % structurally restricted by listValidMethods, so they inherit the
132 % base envelope unchanged.
133 featSupported = SolverMVA.getFeatureSet();
136 featSupported.setTrue({'MAP
','MMPP2
','MMAP','RAP
'});
137 featSupported.setFalse({'ClosedClass
','SelfLoopingClass
'});
139 featSupported.setFalse({'ClosedClass
','SelfLoopingClass
'});
143 function [bool, reason] = supportsModelMethod(self, method)
144 % Finite station/class capacity has no registry feature name, so
145 % the coarse per-method feature gate cannot see it. Apply the
146 % structural capacity check on top of it, otherwise MVA silently
147 % returns the unconstrained product-form answer for models built
148 % with setCapacity / a finite classCap (BUG-39).
149 [bool, reason] = supportsModelMethod@NetworkSolver(self, method);
150 if bool && isa(self.model, 'Network
')
151 [bool, reason] = SolverMVA.supportsFiniteCapacity(self.model, method);
158 function [bool, reason] = supportsFiniteCapacity(model, method)
159 % [BOOL, REASON] = SUPPORTSFINITECAPACITY(MODEL, METHOD)
160 % MVA-specific finite-capacity gate: Blocking-After-Service models
161 % are exempt because MVA offers the Smith queue-decomposition
162 % method 'sqd
', and solver_mva_analyzer routes a BAS model to
163 % solver_sqd under the default method too, so the finite buffers
164 % ARE honoured on every MVA path. Everything else defers to the
165 % shared product-form gate.
171 if ~isa(model, 'Network
')
174 if sn_is_bas_model(model.getStruct())
177 % Single-station M/M/1/K with tail drop is handled by the
178 % moment-based (MacGregor Smith) qsys_mg1k_loss_mgs branch in
179 % solver_mva_qsys_analyzer, exact only at scv=1. It is an
180 % approximation in general, so method='exact
' is NOT exempted (it
181 % must reject); every other method is.
182 if ~strcmp(method, 'exact
') && sn_is_mm1k_loss(model.getStruct())
185 [bool, reason] = NetworkSolver.checkBindingCapacity(model, 'SolverMVA
');
188 function featSupported = getFeatureSet()
189 % FEATSUPPORTED = GETFEATURESET()
191 featSupported = SolverFeatureSet;
192 featSupported.setTrue({'Sink
','Source
',...
193 'ClassSwitch
','Delay
','DelayStation
','Queue
',...
194 'APH
','Coxian
','Erlang
','Exp
','HyperExp
','BMAP
',...
195 'Pareto
','Weibull
','Lognormal
','Uniform
','Det
', ...
196 'StatelessClassSwitcher
','InfiniteServer
','SharedServer
','Buffer
','Dispatcher
',...
197 'CacheClassSwitcher
','Cache
', ...
198 'CacheRetrieval
', ...
199 'Server
','JobSink
','RandomSource
','ServiceTunnel
',...
200 'SchedStrategy_INF
','SchedStrategy_PS
',...
201 'SchedStrategy_DPS
','SchedStrategy_FCFS
','SchedStrategy_SIRO
','SchedStrategy_HOL
',...
202 'SchedStrategy_LCFS
','SchedStrategy_LCFSPR
','SchedStrategy_POLLING
',...
203 'SchedStrategy_OI
','SchedStrategy_PAS
',... % exact order-independent path only (solver_mva_oi_analyzer)
204 'Fork
','Forker
','Join
','Joiner
',...
205 'RoutingStrategy_PROB
','RoutingStrategy_RAND
',...
206 'ReplacementStrategy_RR
', 'ReplacementStrategy_FIFO
', 'ReplacementStrategy_LRU
',...
207 'ReplacementStrategy_HLRU
',...
208 'MMAP',... % marked MAP sources (cache LRU via cache_ttl_lrum_map)
209 'ClosedClass
','SelfLoopingClass
','OpenClass
','Replayer
',...
210 'LoadDependence
','ClassDependence
','JointDependence
'});
213 function [bool, featSupported] = supports(model)
214 % [BOOL, FEATSUPPORTED] = SUPPORTS(MODEL)
216 featUsed = model.getUsedLangFeatures();
217 featSupported = SolverMVA.getFeatureSet();
218 bool = SolverFeatureSet.supports(featSupported, featUsed);
220 % Registry inclusion cannot see finite capacity (no feature
221 % name); apply the structural gate too, so that the static
222 % supports() agrees with the runAnalyzer feature gate.
223 [bool, reason] = SolverMVA.supportsFiniteCapacity(model);
225 line_warning(mfilename, '%s\n
', reason);
230 function options = defaultOptions
231 % OPTIONS = DEFAULTOPTIONS()
233 options = SolverOptions('MVA
');
236 function libs = getLibrariesUsed(sn, options)
237 % GETLIBRARIESUSED Get list of external libraries used by MVA solver
238 % MVA uses internal algorithms, no external library attribution needed