1function [QN,xvec_it,QNt,UNt,xvec_t,t,iters,runtime] = solver_fluid(sn, options)
2% [QN,XVEC_IT,QNT,UNT,XVEC_T,T,ITERS,RUNTIME] = SOLVER_FLUID(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
7M = sn.nstations; %number of stations
8K = sn.nclasses; %number of classes
9N = sn.nclosedjobs; %population
16NK = sn.njobs
'; %initial population
18% see _kb/06-solver-catalog.md for rationale
19if isfield(sn,'procid
')
22 if sn.procid(ist,k) == ProcessType.NHPP && ~isempty(Mu{ist}{k}) ...
23 && ~isnan(Mu{ist}{k}(1))
25 PH{ist}{k} = {-lam, lam};
32match = zeros(M,K); % indicates whether a class is served at a station
39 match(ist,k) = sum(rt(:, (ist-1)*K+k)) > 0;
41 %Set number of servers in delay station = population
48% Wall-clock time budget (options.timeout, seconds; Inf = no budget). The ODE
49% iteration in solver_fluid_iteration breaks when toc(Tstart) > max_time.
50if isfield(options,'timeout
') && ~isempty(options.timeout) && isfinite(options.timeout) && options.timeout > 0
51 max_time = options.timeout;
61 phases(ist,k) = length(Mu{ist}{k});
68 if ~isempty(Mu{ist}{k})
69 slowrate(ist,k) = min(Mu{ist}{k}(:)); %service completion (exit) rates in each phase
71 slowrate(ist,k) = Inf;
80assigned = zeros(1,K); %number of jobs of each class already assigned
83 if match(ist,k) > 0 % indicates whether a class is served at a station
85 if sched(ist)==SchedStrategy.EXT
86 toAssign = 1; % open job pool
88 toAssign = 0; % set to zero open jobs everywhere
91 toAssign = floor(NK(k)/sum(match(:,k)));
92 if sum( match(ist+1:end, k) ) == 0 % if this is the last station for this job class
93 toAssign = NK(k) - assigned(k);
96 y0 = [y0, toAssign, zeros(1,phases(ist,k)-1)]; % this is just for PS
97 assigned(k) = assigned(k) + toAssign;
99 y0 = [y0, zeros(1,phases(ist,k))];
104if isempty(options.init_sol)
105 xvec_it{iters +1} = y0(:)'; % average state embedded at stage change transitions out of e
106 ydefault = y0(:)
'; % not used in this case
108 xvec_it{iters +1} = options.init_sol(:)';
109 ydefault = y0(:)
'; % default solution if init_sol fails
114 case {'tbi
','fluid.tbi
'}
115 [xvec_it, xvec_t, t, iters] = solver_fluid_tbi_iteration(sn, N, Mu, Phi, PH, rt, S, xvec_it, ydefault, slowrate, Tstart, max_time, options);
117 [xvec_it, xvec_t, t, iters] = solver_fluid_iteration(sn, N, Mu, Phi, PH, rt, S, xvec_it, ydefault, slowrate, Tstart, max_time, options);
120runtime = toc(Tstart);
121% if options.verbose >= 2
123% line_printf('Fluid analysis iteration completed in %0.6f sec [%d iteration]\n
',runtime,iters);
125% line_printf('Fluid analysis iteration completed in %0.6f sec [%d iterations]\n
',runtime,iters);
129% this part assumes PS, DPS, GPS scheduling
137 shift = sum(sum(phases(1:ist-1,:))) + sum( phases(ist,1:k-1) ) + 1;
138 QN(ist,k) = sum(xvec_it{end}(shift:shift+phases(ist,k)-1));
139 QNt{ist,k} = sum(xvec_t(:,shift:shift+phases(ist,k)-1),2);
140 Qt{ist} = Qt{ist} + QNt{ist,k};
141 % computes queue length in each node and stage, summing the total
142 % number in service and waiting in that station
143 % results are weighted with the stat prob of the stage
148 if sn.nservers(ist) > 0 % not INF
150 UNt{ist,k} = min(QNt{ist,k} / S(ist), QNt{ist,k} ./ Qt{ist}); % if not an infinite server then this is a number between 0 and 1
151 UNt{ist,k}(isnan(UNt{ist,k})) = 0; % fix cases where qlen is 0
153 else % infinite server
155 UNt{ist,k} = QNt{ist,k};