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 % Cached MMT fork-join transformation, reused across
the outer
11 % iterations of SolverLN so that each layer
is not deep-copied once per
12 % iteration. Tagged with
the model
's structVersion and only reused while
13 % that still matches; its rates are re-fed from the base model on every
14 % reuse (ModelAdapter.refreshServicesFromBase). Deliberately NOT cleared
15 % by reset(): correctness lives at the point of use, and clearing it
16 % there would throw the transformation away on every outer iteration.
20 properties (Access = public)
21 % Auxiliary-class arrival rates of the fork-join (MMT) fixed point,
22 % retained across runAnalyzer calls so that an outer iteration (e.g.
23 % SolverLN) restarts the MMT loop from the previous converged point
24 % instead of from GlobalConstants.FineTol. Like options.init_sol it
25 % survives reset(); use resetForkWarmStart to discard it.
30 function self = SolverMVA(model,varargin)
31 % SOLVERMVA Create an MVA solver instance
33 % @brief Creates a Mean Value Analysis solver for the given model
34 % @param model Network model to be analyzed
35 % @param varargin Optional solver options (method, tolerance, etc.)
36 % @return self SolverMVA instance configured with specified options
38 self@NetworkSolver(model, mfilename);
39 self.setOptions(Solver.parseOptions(varargin, SolverMVA.defaultOptions));
43 function sn = getStruct(self)
44 % GETSTRUCT Get model data structure for analysis
46 % @brief Returns the internal data structure representing the model
47 % @return sn Structured data representing the queueing network
48 sn = self.model.getStruct(false);
51 function tf = supportsExactSensitivity(self) %#ok<MANU>
52 % TF = SUPPORTSEXACTSENSITIVITY()
53 % MVA differentiates its own recursion: getSensitivityTable uses
54 % the analytic branch (pfqn_sens) wherever the model is in scope.
58 function resetForkWarmStart(self)
59 % RESETFORKWARMSTART()
60 % Discard the retained MMT fixed point. Mirrors options.init_sol:
61 % the iterate survives reset() and is invalidated explicitly by the
62 % caller when the chain basis changes.
63 self.fjForkLambda = [];
66 [runtime, analyzer] = runAnalyzer(self, options);
67 [lNormConst] = getProbNormConstAggr(self);
68 [Pnir,logPnir] = getProbAggr(self, ist);
69 [Pnir,logPn] = getProbSysAggr(self);
71 function [allMethods] = listValidMethods(self)
72 % LISTVALIDMETHODS Get all valid MVA solution methods
74 % @brief Returns cell array of valid MVA methods for the current model
75 % @return allMethods Cell array of method names available for this model
77 sn = self.model.getStruct;
79 allMethods = {'default',...
80 'mva
','exact
','amva
', ...
82 'qdlin
','amva.qdlin
', ...
89 'schmidt
','amva.schmidt
', ...
90 'schmidt-ext
','amva.schmidt-ext
'};
92 % QNA and RQNA are open-network decomposition analyzers driven by
93 % arrival (Poisson/MAP) flows; they have no meaningful fixed point
94 % on a model with closed chains (the mixed path is disabled and a
95 % fully closed model has no exogenous arrivals), and previously
96 % returned degenerate queue lengths there. Advertise them only for
97 % fully open models (matching the RQNA default dispatch and the
98 % native Python gate), inserting between 'amva
' and 'qdlin
' to
99 % preserve the open regression-baseline ordering.
100 if sn_is_open_model(sn)
101 allMethods = [allMethods(1:4), {'qna
','rqna
'}, allMethods(5:end)];
104 % SQD (Smith Queue Decomposition) is only valid for closed
105 % single-chain Blocking-After-Service networks; kept at this
106 % position to preserve BAS regression-baseline ordering.
107 if sn_is_bas_model(sn)
108 allMethods{end+1} = 'sqd
'; %#ok<AGROW>
111 % Marie's aggregation-decomposition (method
'marie')
is a closed-
112 % network mean-value method
for FCFS non-exponential service; it
is
113 % not defined
for open models (no exogenous arrivals to decompose).
114 if ~sn_is_open_model(sn)
115 allMethods = [allMethods, {
'marie',
'amva.marie'}]; %#ok<AGROW>
118 allMethods = {allMethods{:}, ...
119 'lin',
'egflin',
'gflin',
'amva.lin'}; %#ok<CCAT>
121 % Bound methods (aba/bjb/gb/pb/sb/mwba) are NOT listed here: they
122 % moved to SolverBA, and runAnalyzer raises line_error
for the whole
123 % family. Listing them made
this a
false claim, since every listed
124 % name errored when run. See SolverBA.listValidMethods.
126 if sn_is_open_model(sn) && sn.nstations == 2 && sn.nclasses == 1
127 % methods to add
for queueing systems
128 qsys = {
'mm1',
'mmk',
'mg1',
'mgi1',
'gm1',
'gig1',
'gim1',
'gig1.kingman', ...
129 'gigk',
'gigk.kingman_approx', ...
130 'gig1.gelenbe',
'gig1.heyman',
'gig1.kimura',
'gig1.allen', ...
131 'gig1.kobayashi',
'gig1.klb',
'gig1.marchal'};
132 % append, keeping original order and avoiding duplicates
133 allMethods = {allMethods{:}, qsys{:}}; %#ok<CCAT>
137 function method = resolveMethod(self, options)
138 % Feature-driven resolution of options.method=
'default'. A bursty
139 % single-
class open network has a non-renewal (MAP/MMPP) arrival
140 % process whose autocorrelation a two-moment method cannot capture,
141 % so
the default dispatch selects RQNA (robust queueing network
142 % analyzer, indices of dispersion). All other cases keep
'default',
143 % which
the analyzer expands with its own heuristics. Expressed as
144 % RQNA
's MAP-family coverage rather than a bespoke gate.
145 method = options.method;
146 if strcmp(options.method, 'default')
147 sn = self.model.getStruct();
148 if (sn.nclasses == 1) && all(isinf(sn.njobs)) && sn_has_bursty_arrival(sn)
154 function featSupported = getMethodFeatureSet(self, method) %#ok<INUSL>
155 % Per-method feature deltas applied to the base MVA envelope.
156 % RQNA natively consumes non-renewal MAP/MMPP/RAP arrival and
157 % service processes (open only); QNA is a two-moment open-network
158 % method. The queueing-system and bounds methods are already
159 % structurally restricted by listValidMethods, so they inherit the
160 % base envelope unchanged.
161 featSupported = SolverMVA.getFeatureSet();
164 featSupported.setTrue({'MAP
','MMPP2
','MMAP','RAP
'});
165 featSupported.setFalse({'ClosedClass
','SelfLoopingClass
'});
167 featSupported.setFalse({'ClosedClass
','SelfLoopingClass
'});
171 function [bool, reason] = supportsModelMethod(self, method)
172 % Finite station/class capacity has no registry feature name, so
173 % the coarse per-method feature gate cannot see it. Apply the
174 % structural capacity check on top of it, otherwise MVA silently
175 % returns the unconstrained product-form answer for models built
176 % with setCapacity / a finite classCap (BUG-39).
177 [bool, reason] = supportsModelMethod@NetworkSolver(self, method);
178 if bool && isa(self.model, 'Network
')
179 [bool, reason] = SolverMVA.supportsFiniteCapacity(self.model);
186 function [bool, reason] = supportsFiniteCapacity(model)
187 % [BOOL, REASON] = SUPPORTSFINITECAPACITY(MODEL)
188 % MVA-specific finite-capacity gate: Blocking-After-Service models
189 % are exempt because MVA offers the Smith queue-decomposition
190 % method 'sqd
', and solver_mva_analyzer routes a BAS model to
191 % solver_sqd under the default method too, so the finite buffers
192 % ARE honoured on every MVA path. Everything else defers to the
193 % shared product-form gate.
196 if ~isa(model, 'Network
')
199 if sn_is_bas_model(model.getStruct())
202 [bool, reason] = NetworkSolver.checkBindingCapacity(model, 'SolverMVA
');
205 function featSupported = getFeatureSet()
206 % FEATSUPPORTED = GETFEATURESET()
208 featSupported = SolverFeatureSet;
209 featSupported.setTrue({'Sink
','Source
',...
210 'ClassSwitch
','Delay
','DelayStation
','Queue
',...
211 'APH
','Coxian
','Erlang
','Exp
','HyperExp
','BMAP
',...
212 'Pareto
','Weibull
','Lognormal
','Uniform
','Det
', ...
213 'StatelessClassSwitcher
','InfiniteServer
','SharedServer
','Buffer
','Dispatcher
',...
214 'CacheClassSwitcher
','Cache
', ...
215 'CacheRetrieval
', ...
216 'Server
','JobSink
','RandomSource
','ServiceTunnel
',...
217 'SchedStrategy_INF
','SchedStrategy_PS
',...
218 'SchedStrategy_DPS
','SchedStrategy_FCFS
','SchedStrategy_SIRO
','SchedStrategy_HOL
',...
219 'SchedStrategy_LCFS
','SchedStrategy_LCFSPR
','SchedStrategy_POLLING
',...
220 'SchedStrategy_OI
','SchedStrategy_PAS
',... % exact order-independent path only (solver_mva_oi_analyzer)
221 'Fork
','Forker
','Join
','Joiner
',...
222 'RoutingStrategy_PROB
','RoutingStrategy_RAND
',...
223 'ReplacementStrategy_RR
', 'ReplacementStrategy_FIFO
', 'ReplacementStrategy_LRU
',...
224 'ReplacementStrategy_HLRU
',...
225 'MMAP',... % marked MAP sources (cache LRU via cache_ttl_lrum_map)
226 'ClosedClass
','SelfLoopingClass
','OpenClass
','Replayer
',...
227 'LoadDependence
','ClassDependence
'});
230 function [bool, featSupported] = supports(model)
231 % [BOOL, FEATSUPPORTED] = SUPPORTS(MODEL)
233 featUsed = model.getUsedLangFeatures();
234 featSupported = SolverMVA.getFeatureSet();
235 bool = SolverFeatureSet.supports(featSupported, featUsed);
237 % Registry inclusion cannot see finite capacity (no feature
238 % name); apply the structural gate too, so that the static
239 % supports() agrees with the runAnalyzer feature gate.
240 [bool, reason] = SolverMVA.supportsFiniteCapacity(model);
242 line_warning(mfilename, '%s\n
', reason);
247 function options = defaultOptions
248 % OPTIONS = DEFAULTOPTIONS()
250 options = SolverOptions('MVA
');
253 function libs = getLibrariesUsed(sn, options)
254 % GETLIBRARIESUSED Get list of external libraries used by MVA solver
255 % MVA uses internal algorithms, no external library attribution needed