LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_fluid_analyzer.m
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)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6%global GlobalConstants.Immediate
7%global GlobalConstants.FineTol
8
9% Initialize aoiResults (will be populated if AoI solver is used)
10aoiResults = [];
11
12M = sn.nstations;
13K = sn.nclasses;
14S = sn.nservers;
15SCV = sn.scv;
16V = cellsum(sn.visits);
17gamma = zeros(M,1);
18sched = sn.sched;
19
20line_debug('Fluid analyzer starting: method=%s, nstations=%d, nclasses=%d', options.method, M, K);
21phases = sn.phases;
22phases_last = sn.phases;
23rates0 = sn.rates;
24
25if isempty(options.init_sol)
26 options.init_sol = solver_fluid_initsol(sn, options);
27end
28
29outer_iters = 1;
30outer_runtime = tic;
31switch options.method
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');
36 end
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
49
50 % Check for AoI topology first (more specific than general single-queue)
51 [isAoI, aoiInfo] = aoi_is_aoi(sn);
52
53 if isAoI
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);
57 else
58 % Check for general single-queue topology
59 [isSingleQueue, fluidInfo] = fluid_is_single_queue(sn);
60
61 if isSingleQueue
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);
66 else
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);
69 end
70 else
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);
75 end
76 end
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);
82 otherwise
83 line_error(mfilename,sprintf('The ''%s'' method is unsupported by this solver.',options.method));
84end
85outer_runtime = toc(outer_runtime);
86
87
88switch options.method
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');
93 iter = 0;
94 eta_1 = zeros(1,M);
95 eta = Inf*ones(1,M);
96 tol = GlobalConstants.CoarseTol;
97
98 while max(abs(1-eta./eta_1)) > tol & iter <= options.iter_max %#ok<AND2>
99 iter = iter + 1;
100 eta_1 = eta;
101 for ist=1:M
102 sd = rates0(ist,:)>0;
103 UN(ist,sd) = TN(ist,sd) ./ rates0(ist,sd);
104 end
105 ST0 = 1./rates0;
106 ST0(isinf(ST0)) = GlobalConstants.Immediate;
107 ST0(isnan(ST0)) = GlobalConstants.FineTol;
108
109 XN = zeros(1,K);
110 for k=1:K
111 if sn.refstat(k)>0 % ignore artificial classes
112 XN(k) = TN(sn.refstat(k),k);
113 end
114 end
115 [ST,gamma,~,~,~,~,eta] = npfqn_nonexp_approx(options.config.highvar,sn,ST0,V,SCV,TN,UN,gamma,S);
116
117 rates = 1./ST;
118 rates(isinf(rates)) = GlobalConstants.Immediate;
119 rates(isnan(rates)) = GlobalConstants.FineTol; %#ok<NASGU>
120
121 for ist=1:M
122 switch sn.sched(ist)
123 case SchedStrategy.FCFS
124 for k=1:K
125 if rates(ist,k)>0 && SCV(ist,k)>0
126 [cx,muik,phiik] = Coxian.fitMeanAndSCV(1/rates(ist,k), SCV(ist,k));
127 % see _kb/06-solver-catalog.md for rationale
128 phases(ist,k) = length(muik);
129 if phases(ist,k) ~= phases_last(ist,k) % if number of phases changed
130 % before we update sn we adjust the initial state
131 isf = sn.stationToStateful(ist);
132 [~, nir, sir] = State.toMarginal(sn, ist, sn.state{isf});
133 end
134 sn.proc{ist}{k} = cx.getProcess;
135 sn.mu{ist}{k} = muik;
136 sn.phi{ist}{k} = phiik;
137 % For Coxian, jobs always start in phase 1 (entry probability 1 on first phase)
138 sn.pie{ist}{k} = [1, zeros(1, length(muik)-1)];
139 sn.phases = phases;
140 sn.phasessz = max(sn.phases,ones(size(sn.phases)));
141 sn.phaseshift = [zeros(size(phases,1),1),cumsum(sn.phasessz,2)];
142 if phases(ist,k) ~= phases_last(ist,k)
143 isf = sn.stationToStateful(ist);
144 % we now initialize the new service process
145 sn.state{isf} = State.fromMarginalAndStarted(sn, ist, nir, sir, options);
146 sn.state{isf} = sn.state{isf}(1,:); % pick one as the marginals won't change
147 end
148 end
149 end
150 end
151
152 % see _kb/06-solver-catalog.md for rationale
153 expected_init_sol = solver_fluid_initsol(sn);
154 if ~isempty(xvec_iter) && numel(xvec_iter{end}) == numel(expected_init_sol)
155 options.init_sol = xvec_iter{end}(:);
156 else
157 options.init_sol = expected_init_sol;
158 end
159 if any(phases_last-phases~=0) % If there is a change of phases reset
160 options.init_sol = solver_fluid_initsol(sn);
161 end
162 end
163 sn.phases = phases;
164 switch options.method
165 case {'matrix'}
166 [~, UN, ~, TN, xvec_iter, ~, ~, ~, ~, ~, inner_iters, inner_runtime] = solver_fluid_matrix(sn, options);
167 case {'closing','statedep','tbi'}
168 [~, UN, ~, TN, xvec_iter, ~, ~, ~, ~, ~, inner_iters, inner_runtime] = solver_fluid_closing(sn, options);
169 end
170 phases_last = phases;
171 outer_iters = outer_iters + inner_iters;
172 outer_runtime = outer_runtime + inner_runtime;
173 end % FCFS iteration ends here
174 % see _kb/06-solver-catalog.md for rationale
175 options.init_sol = solver_fluid_initsol(sn, options);
176 switch options.method
177 case {'matrix'}
178 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_matrix(sn, options);
179 case {'closing','statedep','tbi'}
180 [QN, UN, RN, TN, xvec_iter, QNt, UNt, TNt, ~, t] = solver_fluid_closing(sn, options);
181 end
182 end
183 case 'statedep'
184 % do nothing, a single iteration is sufficient
185end
186
187if t(1) == 0
188 t(1) = GlobalConstants.FineTol;
189end
190
191for ist=1:M
192 for k=1:K
193 %Qfull_t{i,k} = cumsum(Qfull_t{i,k}.*[0;diff(t)])./t;
194 %Ufull_t{i,k} = cumsum(Ufull_t{i,k}.*[0;diff(t)])./t;
195 end
196end
197
198Ufull0 = UN;
199for ist=1:M
200 sd = find(QN(ist,:)>0);
201 UN(ist,QN(ist,:)==0)=0;
202 switch sn.sched(ist)
203 case SchedStrategy.INF
204 for k=sd
205 UN(ist,k) = QN(ist,k);
206 UNt{ist,k} = QNt{ist,k};
207 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k);
208 end
209 case SchedStrategy.DPS
210 %w = sn.schedparam(i,:);
211 %wcorr = w(:)*QN(i,:)/(w(sd)*QN(i,sd)');
212 for k=sd
213 % correct for the real rates, instead of the diffusion
214 % approximation rates
215 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)))]);
216 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k)*sn.nservers(ist); % not sure if this is needed
217 end
218 otherwise
219 for k=sd
220 % correct for the real rates, instead of the diffusion
221 % approximation rates
222 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))]);
223 TNt{ist,k} = UNt{ist,k}*sn.rates(ist,k)*sn.nservers(ist);
224 end
225 end
226end
227UN(isnan(UN))=0;
228
229%switch options.method
230%case {'closing','statedep'}
231% for i=1:M
232% if sn.nservers(i) > 0 % not INF
233% for k = 1:K
234% 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
235% UNt{i,k}(isnan(UNt{i,k})) = 0; % fix cases where qlen is 0
236% end
237% else % infinite server
238% for k = 1:K
239% UNt{i,k} = QNt{i,k};
240% end
241% end
242% end
243
244for ist=1:M
245 sd = find(QN(ist,:)>0);
246 RN(ist,QN(ist,:)==0)=0;
247 for k=sd
248 switch sn.sched(ist)
249 case SchedStrategy.INF
250 % no-op
251 otherwise
252 RN(ist,k) = QN(ist,k) / TN(ist,k);
253 end
254 end
255end
256RN(isnan(RN))=0;
257%end
258
259XN = zeros(1,K);
260CN = zeros(1,K);
261for k=1:K
262 if sn.refstat(k)>0 % ignore artificial classes
263 XN(k) = TN(sn.refstat(k),k);
264 CN(k) = sn.njobs(k) ./ XN(k);
265 end
266end
267if ~isempty(xvec_iter)
268 xvec.odeStateVec = xvec_iter{end};
269else
270 xvec = []; % Signal failure to caller (runAnalyzer checks isempty)
271end
272xvec.sn = sn;
273if exist('cacheHitProb', 'var')
274 xvec.cacheHitProb = cacheHitProb;
275 xvec.cacheMissProb = cacheMissProb;
276end
277iter = outer_iters;
278end
Definition fjtag.m:161