1function [QN, UN, RN, TN, CN, XN, t, QNt, UNt, TNt, xvec, iter, aoiResults] = solver_fluid_analyzer(sn, options)
2% [QN, UN, RN, TN, CN, XN, T, QNT, UNT, TNT, XVEC, iter, aoiResults] = SOLVER_FLUID_ANALYZER(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
6%global GlobalConstants.Immediate
7%global GlobalConstants.FineTol
9% Initialize aoiResults (will be populated
if AoI solver
is used)
16V = cellsum(sn.visits);
20line_debug(
'Fluid analyzer starting: method=%s, nstations=%d, nclasses=%d', options.method, M, K);
22phases_last = sn.phases;
25if isempty(options.init_sol)
26 options.init_sol = solver_fluid_initsol(sn, options);
32 case {
'matrix',
'fluid.matrix',
'default',
'pnorm',
'fluid.pnorm'}
33 % pnorm uses matrix method with pstar smoothing parameter
34 if strcmpi(options.method,
'default')
35 line_printf('Default method: using matrix/pnorm fluid method\n');
37 line_debug('Using matrix/pnorm method, calling solver_fluid_matrix');
38 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_matrix(sn, options);
39 case {
'closing',
'statedep',
'softmin',
'tbi',
'fluid.closing',
'fluid.statedep',
'fluid.softmin',
'fluid.tbi'}
40 line_debug(
'Using closing/statedep/tbi method, calling solver_fluid_closing');
41 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_closing(sn, options);
42 case {
'diffusion',
'fluid.diffusion'}
43 % Diffusion approximation
using Euler-Maruyama SDE solver (closed networks only)
44 line_debug(
'Using diffusion method, calling solver_fluid_diffusion');
45 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_diffusion(sn, options);
46 case {
'mfq',
'fluid.mfq'}
47 % Markovian fluid queue method
using BUTools
for exact single-queue analysis
48 % Also supports Age of Information (AoI) analysis
for valid topologies
50 % Check
for AoI topology first (more specific than general single-queue)
51 [isAoI, aoiInfo] = aoi_is_aoi(sn);
54 % Route to AoI solver
for Age of Information analysis
55 line_debug(
'AoI topology detected, calling solver_mfq_aoi');
56 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t, ~, ~, aoiResults] = solver_mfq_aoi(sn, options);
58 % Check
for general single-queue topology
59 [isSingleQueue, fluidInfo] = fluid_is_single_queue(sn);
62 if numel(unique(sn.classprio)) > 1
63 % Priority classes: use
the fluid priority queue
64 line_debug(
'MFQ priority topology, calling solver_mfq_prio');
65 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_mfq_prio(sn, options);
67 line_debug(
'MFQ topology check passed, calling solver_mfq');
68 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_mfq(sn, options);
71 % Fallback to matrix method
if topology
is not suitable
72 line_warning(mfilename,
'MFQ not applicable: %s. Falling back to matrix method.', fluidInfo.errorMsg);
73 options.method =
'matrix';
74 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_matrix(sn, options);
77 case {
'rmf',
'fluid.rmf'}
78 % Refined mean field method
for cache analysis
79 % Use cacheqn analyzer
for integrated cache+queueing network
80 line_debug(
'Using refined mean field method, calling solver_fld_cacheqn_analyzer');
81 [QN, UN, RN, TN, CN, XN, t, QNt, UNt, TNt, xvec_iter, cacheHitProb, cacheMissProb, ~, ~] = solver_fld_cacheqn_analyzer(sn, options);
83 line_error(mfilename,sprintf(
'The ''%s'' method is unsupported by this solver.',options.method));
85outer_runtime = toc(outer_runtime);
89 case {
'matrix',
'closing',
'tbi'}
90 % approximate FCFS
nodes as state-independent stations
91 if any(sched==SchedStrategy.FCFS)
92 line_debug('FCFS
nodes detected, starting iterative approximation');
96 tol = GlobalConstants.CoarseTol;
98 while max(abs(1-eta./eta_1)) > tol & iter <= options.iter_max %
#ok<AND2>
102 sd = rates0(ist,:)>0;
103 UN(ist,sd) = TN(ist,sd) ./ rates0(ist,sd);
106 ST0(isinf(ST0)) = GlobalConstants.Immediate;
107 ST0(isnan(ST0)) = GlobalConstants.FineTol;
111 if sn.refstat(k)>0 % ignore artificial classes
112 XN(k) = TN(sn.refstat(k),k);
115 [ST,gamma,~,~,~,~,eta] = npfqn_nonexp_approx(options.config.highvar,sn,ST0,V,SCV,TN,UN,gamma,S);
118 rates(isinf(rates)) = GlobalConstants.Immediate;
119 rates(isnan(rates)) = GlobalConstants.FineTol; %#ok<NASGU>
123 case SchedStrategy.FCFS
125 if rates(ist,k)>0 && SCV(ist,k)>0
126 [cx,muik,phiik] = Coxian.fitMeanAndSCV(1/rates(ist,k), SCV(ist,k));
127 % we now handle
the case that due to either numerical issues
128 % or different relationship between scv and mean
the size of
129 %
the phase-type representation has changed
130 phases(ist,k) = length(muik);
131 if phases(ist,k) ~= phases_last(ist,k) %
if number of phases changed
132 % before we update sn we adjust
the initial state
133 isf = sn.stationToStateful(ist);
134 [~, nir, sir] = State.toMarginal(sn, ist, sn.state{isf});
136 sn.proc{ist}{k} = cx.getProcess;
137 sn.mu{ist}{k} = muik;
138 sn.phi{ist}{k} = phiik;
139 % For Coxian, jobs always start in phase 1 (entry probability 1 on first phase)
140 sn.pie{ist}{k} = [1, zeros(1, length(muik)-1)];
142 sn.phasessz = max(sn.phases,ones(size(sn.phases)));
143 sn.phaseshift = [zeros(size(phases,1),1),cumsum(sn.phasessz,2)];
144 if phases(ist,k) ~= phases_last(ist,k)
145 isf = sn.stationToStateful(ist);
146 % we now initialize
the new service process
147 sn.state{isf} = State.fromMarginalAndStarted(sn, ist, nir, sir, options);
148 sn.state{isf} = sn.state{isf}(1,:); % pick one as
the marginals won
't change
154 % xvec_iter{end} may be shorter than expected due to
155 % state space reduction (keep filter / immediate
156 % elimination) in solver_fluid_matrix. Regenerate
157 % init_sol from current sn to ensure correct size.
158 expected_init_sol = solver_fluid_initsol(sn);
159 if ~isempty(xvec_iter) && numel(xvec_iter{end}) == numel(expected_init_sol)
160 options.init_sol = xvec_iter{end}(:);
162 options.init_sol = expected_init_sol;
164 if any(phases_last-phases~=0) % If there is a change of phases reset
165 options.init_sol = solver_fluid_initsol(sn);
169 switch options.method
171 [~, UN, ~, TN, xvec_iter, ~, ~, ~, ~, ~, inner_iters, inner_runtime] = solver_fluid_matrix(sn, options);
172 case {'closing
','statedep
','tbi
'}
173 [~, UN, ~, TN, xvec_iter, ~, ~, ~, ~, ~, inner_iters, inner_runtime] = solver_fluid_closing(sn, options);
175 phases_last = phases;
176 outer_iters = outer_iters + inner_iters;
177 outer_runtime = outer_runtime + inner_runtime;
178 end % FCFS iteration ends here
179 % The FCFS iteration reinitializes at the solution of the last
180 % iterative step. We now have converged in the substitution of the
181 % model parameters and we rerun everything from the true initial point
182 % so that we get the correct transient.
183 options.init_sol = solver_fluid_initsol(sn, options);
184 switch options.method
186 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_matrix(sn, options);
187 case {'closing
','statedep
','tbi
'}
188 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_closing(sn, options);
192 % do nothing, a single iteration is sufficient
196 t(1) = GlobalConstants.FineTol;
201 %Qfull_t{i,k} = cumsum(Qfull_t{i,k}.*[0;diff(t)])./t;
202 %Ufull_t{i,k} = cumsum(Ufull_t{i,k}.*[0;diff(t)])./t;
208 sd = find(QN(ist,:)>0);
209 UN(ist,QN(ist,:)==0)=0;
211 case SchedStrategy.INF
213 UN(ist,k) = QN(ist,k);
214 UNt{ist,k} = QNt{ist,k};
215 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k);
217 case SchedStrategy.DPS
218 %w = sn.schedparam(i,:);
219 %wcorr = w(:)*QN(i,:)/(w(sd)*QN(i,sd)');
221 % correct
for the real rates, instead of
the diffusion
222 % approximation rates
223 UN(ist,k) = min([1,QN(ist,k)/S(ist),sum(Ufull0(ist,sd)) * (TN(ist,k)./(rates0(ist,k)))/sum(TN(ist,sd)./(rates0(ist,sd)))]);
224 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k)*sn.nservers(ist); % not sure
if this is needed
228 % correct
for the real rates, instead of
the diffusion
229 % approximation rates
230 UN(ist,k) = min([1,QN(ist,k)/S(ist),sum(Ufull0(ist,sd)) * (TN(ist,k)./rates0(ist,k))/sum(TN(ist,sd)./rates0(ist,sd))]);
231 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k)*sn.nservers(ist);
237%
switch options.method
238%
case {
'closing',
'statedep'}
240%
if sn.nservers(i) > 0 % not INF
242% UNt{i,k} = min(QNt{i,k} / S(i), QNt{i,k} ./ cellsum({QNt{i,:}}) ); %
if not an infinite server then
this is a number between 0 and 1
243% UNt{i,k}(isnan(UNt{i,k})) = 0; % fix cases where qlen
is 0
245%
else % infinite server
247% UNt{i,k} = QNt{i,k};
253 sd = find(QN(ist,:)>0);
254 RN(ist,QN(ist,:)==0)=0;
257 case SchedStrategy.INF
260 RN(ist,k) = QN(ist,k) / TN(ist,k);
270 if sn.refstat(k)>0 % ignore artificial classes
271 XN(k) = TN(sn.refstat(k),k);
272 CN(k) = sn.njobs(k) ./ XN(k);
275if ~isempty(xvec_iter)
276 xvec.odeStateVec = xvec_iter{end};
278 xvec = []; % Signal failure to caller (runAnalyzer checks isempty)
281if exist(
'cacheHitProb',
'var')
282 xvec.cacheHitProb = cacheHitProb;
283 xvec.cacheMissProb = cacheMissProb;