LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_fluid_passage_time.m
1function RTret = solver_fluid_passage_time(sn, options)
2% RTRET = SOLVER_FLUID_PASSAGE_TIME(QN, OPTIONS)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7iter_max = options.iter_max;
8tol = options.tol;
9y0 = options.init_sol;
10stiff = options.stiff;
11T0 = 0;
12M = sn.nstations; %number of stations
13K = sn.nclasses; %number of classes
14N = sn.nclosedjobs; %population
15Lambda = sn.mu;
16Pi = sn.phi;
17PH = sn.proc;
18rt = sn.rt;
19S = sn.nservers;
20
21for j = 1:M
22 %Set number of servers in delay station = population
23 if isinf(S(j))
24 S(j) = N;
25 end
26end
27
28%% initialization
29phases = sn.phases;
30slowrate = zeros(M,K);
31for j = 1:M
32 for k = 1:K
33 slowrate(j,k) = Inf;
34 slowrate(j,k) = min(slowrate(j,k),min(Lambda{j}{k}(:))); %service completion (exit) rates in each phase
35 end
36end
37
38%% response time analysis - starting from fixed point found
39%stiff = 1;
40chains = sn.chains;
41nChains = size(chains,1);
42
43RT = [];
44for i = 1:sn.nstations
45 if sn.nodetype(sn.stationToNode(i)) ~= NodeType.Source
46 for k = 1:nChains %once for each chain
47 idxClassesInChain = find(chains(k,:)==1);
48 for c = idxClassesInChain
49 if phases(i,c) > 0
50 [Kc, ode_h_c, y0_c, phases_c, fluid_c] = generate_odes_passagetime(k,c);
51
52 % determine max integration time
53 nonZeroRates = slowrate(:);
54 nonZeroRates = nonZeroRates( nonZeroRates > tol );
55 T = abs(100/min(nonZeroRates)); % solve ode until T = 100 events with slowest exit rate
56
57 % indices of new classes at station i
58 idxN = [];
59 for j = i % this used to be at all stations
60 idxN = [idxN sum(sum(phases_c(1:j-1,: ) )) + sum(phases_c(j,1:K)) + [1:sum(phases_c(j,K+1:Kc))] ];
61 end
62
63 %% ODE analysis
64 fullt = [];
65 fully = [];
66 iter = 1;
67 finished = 0;
68 tref = 0;
69 odeopt = odeset('AbsTol', tol, 'RelTol', tol, 'NonNegative', 1:length(y0_c));
70 while iter <= iter_max && finished == 0
71 trange = [T0, T];
72 % solve ode - ymean_t_iter is the transient solution in stage e
73 try
74 if stiff
75 [t_iter, ymean_t_iter] = ode_solve_stiff(ode_h_c, trange, y0_c, odeopt, options);
76 else
77 [t_iter, ymean_t_iter] = ode_solve(ode_h_c, trange, y0_c, odeopt, options);
78 end
79 catch ME
80 line_printf('\nODE solver failed. Fluid solver switching to default initialization.');
81 odeopt = odeset('AbsTol', tol, 'RelTol', tol, 'NonNegative', 1:length(y0_c));
82 %try
83 [t_iter, ymean_t_iter] = ode_solve(ode_h_c, trange, y0_c, odeopt, options);
84 %catch
85 % %keyboard
86 %end
87 end
88 %%%
89 iter = iter + 1;
90 if isempty(fullt)
91 fullt = [fullt; t_iter+tref];
92 fully = [fully; ymean_t_iter];
93 else
94 fullt = [fullt; t_iter(2:end)+tref];
95 fully = [fully; ymean_t_iter(2:end,:)];
96 end
97 if sum(ymean_t_iter(end,idxN )) < 10e-10
98 finished = 1;
99 end
100 tref = tref + t_iter(end);
101 y0_c = ymean_t_iter(end,:);
102 end
103
104 % retrieve response time CDF for class c
105 RT{i,c,1} = fullt;
106 if fluid_c > 0
107 RT{i,c,2} = 1 - sum(fully(:,idxN ),2)/fluid_c;
108 else
109 RT{i,c,2} = ones(size(fullt));
110 end
111
112 %% Adaptive CDF Refinement - detect and refine large CDF jumps
113 if fluid_c > 0
114 maxCdfJump = 0.0005; % Maximum allowed CDF jump (0.05%)
115 maxRefinementIterations = 5; % Limit to prevent infinite loops
116 refinementIter = 0;
117
118 keepRefining = true;
119 while keepRefining && refinementIter < maxRefinementIterations
120 keepRefining = false;
121 cdfValues = RT{i,c,2};
122 for row = 2:length(cdfValues)
123 cdfJump = cdfValues(row) - cdfValues(row-1);
124 if cdfJump > maxCdfJump
125 refinementIter = refinementIter + 1;
126
127 % Refine this interval
128 t1 = fullt(row-1);
129 t2 = fullt(row);
130 numRefinedPoints = 20;
131 refinedT = linspace(t1, t2, numRefinedPoints)';
132 refinedStates = zeros(numRefinedPoints, size(fully,2));
133
134 % Integrate to refined time points
135 startState = fully(row-1,:);
136 refinedStates(1,:) = startState;
137 for rp = 2:numRefinedPoints
138 try
139 if stiff
140 [~, tempState] = ode_solve_stiff(ode_h_c, [t1, refinedT(rp)], startState, odeopt, options);
141 else
142 [~, tempState] = ode_solve(ode_h_c, [t1, refinedT(rp)], startState, odeopt, options);
143 end
144 refinedStates(rp,:) = max(0, tempState(end,:));
145 catch
146 % Linear interpolation fallback
147 alpha = (refinedT(rp) - t1) / (t2 - t1);
148 refinedStates(rp,:) = fully(row-1,:) + alpha * (fully(row,:) - fully(row-1,:));
149 end
150 end
151
152 % Merge refined points using ODE-computed states
153 newFullt = [fullt(1:row-1); refinedT(2:end); fullt(row+1:end)];
154 newFully = [fully(1:row-1,:); refinedStates(2:end,:); fully(row+1:end,:)];
155 fullt = newFullt;
156 fully = newFully;
157
158 % Recompute CDF
159 RT{i,c,1} = fullt;
160 RT{i,c,2} = 1 - sum(fully(:,idxN),2)/fluid_c;
161
162 line_printf('INFO: Added %d refined points between t=%.6f and t=%.6f\n', numRefinedPoints, t1, t2);
163 keepRefining = true; % Continue refining
164 break; % Restart the inner loop with updated arrays
165 end
166 end
167 end
168
169 %% Extended Time Interval Logic - extend if first CDF > 1%
170 maxExtendIterations = 10;
171 extendIter = 0;
172 while RT{i,c,2}(1) > 0.01 && extendIter < maxExtendIterations
173 extendIter = extendIter + 1;
174 extendedT = T * (1 + extendIter);
175
176 % Re-run with extended time
177 try
178 if stiff
179 [t_ext, y_ext] = ode_solve_stiff(ode_h_c, [T0, extendedT], y0_c, odeopt, options);
180 else
181 [t_ext, y_ext] = ode_solve(ode_h_c, [T0, extendedT], y0_c, odeopt, options);
182 end
183 fullt = t_ext;
184 fully = y_ext;
185 RT{i,c,1} = fullt;
186 RT{i,c,2} = 1 - sum(fully(:,idxN),2)/fluid_c;
187 catch
188 break; % Stop extending on error
189 end
190 end
191 end
192
193 if iter > iter_max
194 line_printf('\n');
195 line_warning(mfilename,'Maximum number of iterations reached when computing the response time distribution at station %d in class %d.\n',i,c);
196 line_warning(mfilename,'Response time distributions may be inaccurate. Try increasing option.iter_max (currently at %s).\n',num2str(iter_max));
197 end
198 end
199 end
200 end
201 end
202end
203
204RTret = {};
205for i=1:sn.nstations
206 if sn.nodetype(sn.stationToNode(i)) ~= NodeType.Source
207 for c=1:sn.nclasses
208 if size(RT,1) >= i && size(RT,2) >= c && size(RT,3) >= 2 && ~isempty(RT{i,c,1})
209 RTret{i,c} = [RT{i,c,2},RT{i,c,1}];
210 if ~isempty(RTret{i,c}) && RTret{i,c}(end,1) < 0.995
211 line_warning(mfilename,'CDF at station %d in class %d computed only %.3f percent of the total mass.\n',i,c,RTret{i,c}(end,1)*100);
212 end
213 end
214 end
215 end
216end
217return
218
219 function [Kc, ode_h_c, y0_c, phases_c, fluid_c] = generate_odes_passagetime(k,c)
220 Kc = K + 1; % add a single new class
221 numTranClasses = Kc - K;
222 idxTranCl = zeros(1,K); % indices of the transient class corresponding to each class in the original model for chain k
223 idxTranCl(chains(k,:)==1) = K+1:Kc; % this is just K+1 since we are adding a single new class, but this format may be generalizable
224 new_mu = cell(M,1);
225 for m=1:M
226 new_mu{m,1} = cell(1,Kc);
227 end
228 new_pi = cell(M,1);
229 for m=1:M
230 new_pi{m,1} = cell(1,Kc);
231 end
232 new_rt = zeros(M*Kc, M*Kc); % new routing table
233 new_proc = PH;
234
235 for j=1:sn.nstations
236 % service rates
237 for k=1:K
238 new_mu{j}{k} = Lambda{j}{k};
239 end
240 new_mu{j}{K+1} = Lambda{j}{c};
241
242 % completion probabilities
243 for k=1:K
244 new_pi{j}{k} = Pi{j}{k};
245 end
246 new_pi{j}{K+1} = Pi{j}{c};
247
248 % phd distribution
249 for r=1:nChains
250 new_proc{j}{r} = PH{j}{r};
251 end
252 new_proc{j}{K+1} = PH{j}{c};
253 end
254
255 % routing/switching probabilities
256 % among basic classes
257 for l = 1:K
258 for m = 1:K
259 new_rt(l:Kc:end,m:Kc:end) = rt(l:K:end,m:K:end);
260 end
261 end
262
263 % copy routing table from the original to the transient classes (forward)
264 for l = 1:numTranClasses
265 for m = 1:numTranClasses
266 if sum(sum(rt(c:K:end,idxClassesInChain(m):K:end))) > 0
267 new_rt(K+l:Kc:end,K+m:Kc:end) = rt(c:K:end,idxClassesInChain(m):K:end);
268 end
269 end
270 end
271
272 %phases of transient classes
273 phases_c = zeros(M,Kc);
274 phases_c(:,1:K) = phases;
275 phases_c(:,K+1) = phases(:,c);
276
277 % identify classes in chain that complete
278 completingClassesInChain = c;
279
280 %determine final classes (leaves in the class graph)
281 for s = completingClassesInChain' % for each completing class
282 %routing matrix from a transient class that completes is diverted back into the original classes
283 for l = idxClassesInChain
284 for j = 1:sn.nstations
285 % return fluid to original class
286 new_rt((i-1)*Kc+idxTranCl(c), (j-1)*Kc+l) = rt((i-1)*K+c, (j-1)*K+l);
287 % delete corresponding transition among transient classes
288 new_rt((i-1)*Kc+idxTranCl(c), (j-1)*Kc+idxTranCl(l)) = 0;
289 end
290 end
291 end
292
293 % setup the ODEs for the new QN
294 % options.method = 'statedep'; % default doesn't seem to work in some models
295 [ode_h_c, ~] = solver_fluid_odes(sn, N, new_mu', new_pi', new_proc, new_rt, S, sn.sched, sn.schedparam, options);
296
297 % setup initial point
298 y0_c = zeros(1, sum(sum(phases_c(:,:))));
299 fluid_c = 0;
300 for j = 1:sn.nstations
301 for l = 1:sn.nclasses
302 idxNew_jl = sum(sum(phases_c(1:j-1,:))) + sum(phases_c(j,1:l-1));
303 idxNew_jt = sum(sum(phases_c(1:j-1,:))) + sum(phases_c(j,1:idxTranCl(l)-1));
304 idx_jl = sum(sum(phases(1:j-1,:))) + sum(phases(j,1:l-1));
305 if i == j && l==c
306 y0_c( idxNew_jt + 1 ) = sum(y0(idx_jl+1:idx_jl + phases(j,l))); % mass in phases all moved back into phase 1
307 fluid_c = fluid_c + sum(y0(idx_jl+1:idx_jl + phases(j,l)));
308 else % leave mass as it is
309 y0_c( idxNew_jl + 1: idxNew_jl + phases_c(j,l) ) = y0(idx_jl+1:idx_jl + phases(j,l));
310 end
311 end
312 end
313 end
314
315end