1function [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic(sn, options)
2% [Q,U,R,T,C,X] = SOLVER_MAM_BASIC(QN, PH, OPTIONS)
4% This solver uses MAM to solve queues in isolation, but simplifies the
5% traffic equations by
using visits to rescale the flows into the queue
8% Copyright (c) 2012-2026, Imperial College London
11config = options.config;
15%% generate local state spaces
21V = cellsum(sn.visits);
23Lchain = sn_get_demands_chain(sn);
32% Track stations using exact MAP/D/c solver (skip post-processing for these)
33mapdcStations = false(M, 1);
39chainSysArrivals = cell(1,C);
44% open queueing system (one node is the external world)
45% first build the joint arrival process
48 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO, SchedStrategy.PS}
50 % Skip Det processes - they will be handled by qsys_mapdc
51 if sn.procid(ist, k) == ProcessType.DET
52 % For Det, we don't need MAP representation since we use exact solver
57 % divide service time by number of servers and put
58 % later a surrogate delay server in tandem to compensate
59 PH{ist}{k} = map_scale(PH{ist}{k}, S(ist,k)/sn.nservers(ist));
60 pie{ist}{k} = map_pie(PH{ist}{k});
61 D0{ist,k} = PH{ist}{k}{1};
62 if any(isnan(D0{ist,k}))
63 D0{ist,k} = -GlobalConstants.Immediate;
65 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
73if any(isinf(sn.njobs))
76if any(isfinite(sn.njobs))
79isMixed = isOpen & isClosed;
81lambdas_inchain = cell(1,C);
83 inchain = sn.inchain{c};
84 lambdas_inchain{c} = sn.rates(sn.refstat(inchain(1)),inchain);
85 %lambdas_inchain{c} = lambdas_inchain{c}(isfinite(lambdas_inchain{c}));
86 lambda(c) = sum(lambdas_inchain{c}(isfinite(lambdas_inchain{c})));
87 ist = sn.refstat(inchain(1)); % identical
for all
classes in the chain
88 if isinf(sum(sn.njobs(inchain))) %
if open chain
89 % ist here
is the source
90 % assemble a
MMAP for the arrival process from all
classes
92 if isnan(PH{ist}{k}{1})
93 PH{ist}{k} = map_exponential(Inf); % no arrivals from
this class
96 inchain = sn.inchain{c};
99 for ki=2:length(inchain)
101 if isnan(PH{ist}{k}{1})
102 PH{ist}{k} = map_exponential(Inf); % no arrivals from
this class
106 TN(ist,inchain
') = lambdas_inchain{c};
110sd = isfinite(sn.nservers);
112while max(max(abs(TN-TN_1))) > tol && it <= options.iter_max %#ok<max>
115 Umax = max(sum(UN(sd,:),2));
117 lambda = lambda * 1/Umax;
120 inchain = sn.inchain{c};
121 if ~isinf(sum(sn.njobs(inchain))) % if closed chain
122 Nc = sum(sn.njobs(inchain)); % closed population
123 QNc = max(tol, sum(sum(QN(:,inchain),2,"omitnan"))); %#ok<NANSUM>
124 TNlb = Nc./sum(Lchain(:,c));
126 lambda(c) = TNlb; % lower bound
128 lambda(c) = lambda(c) * it/options.iter_max + (Nc / QNc) * lambda(c) * (options.iter_max-it)/options.iter_max; % iteration-averaged regula falsi;
135 inchain = sn.inchain{c};
136 chainSysArrivals{c} = mmap_exponential(lambda(c));
138 TN(m,inchain) = V(m,inchain) .* lambda(c);
144 ist = sn.nodeToStation(ind);
145 switch sn.nodetype(ind)
148 inchain = sn.inchain{c};
150 fanin = nnz(sn.rtnodes(:, (ind-1)*K+k));
151 TN(ist,k) = lambda(c)*V(ist,k)/fanin;
159 case SchedStrategy.INF
161 inchain = sn.inchain{c};
163 TN(ist,k) = lambda(c)*V(ist,k);
164 UN(ist,k) = S(ist,k)*TN(ist,k);
165 QN(ist,k) = TN(ist,k).*S(ist,k)*V(ist,k);
166 RN(ist,k) = QN(ist,k)/TN(ist,k);
169 case SchedStrategy.PS
171 inchain = sn.inchain{c};
173 TN(ist,k) = lambda(c)*V(ist,k);
174 UN(ist,k) = S(ist,k)*TN(ist,k);
176 %Nc = sum(sn.njobs(inchain)); % closed population
177 Uden = min([1-GlobalConstants.FineTol,sum(UN(ist,:))]);
179 %QN(ist,k) = (UN(ist,k)-UN(ist,k)^(Nc+1))/(1-Uden); % geometric bound type approximation
180 QN(ist,k) = UN(ist,k)/(1-Uden);
181 RN(ist,k) = QN(ist,k)/TN(ist,k);
184 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
185 chainArrivalAtNode = cell(1,C);
187 mapdcUsed = false; % Flag for exact MAP/D/c solver
188 for c=1:C %for each chain
189 rates{ist,c} = V(ist,:) .* lambda(c); % visits of classes within the chain
190 inchain = find(sn.chains(c,:))';
191 markProb = rates{ist,c}(inchain) / sum(rates{ist,c}(inchain));
192 markProb(isnan(markProb)) = 0;
194 chainArrivalAtNode{c} = mmap_normalize(chainArrivalAtNode{c});
195 chainArrivalAtNode{c} = mmap_scale(chainArrivalAtNode{c}, 1./rates{ist,c}, 0); % non-iterative approximation
197 aggrArrivalAtNode = mmap_super_safe({chainArrivalAtNode{c}, mmap_exponential(0,1)}, config.space_max,
'default');
198 aggrArrivalAtNode = {aggrArrivalAtNode{1} aggrArrivalAtNode{2} aggrArrivalAtNode{2}};
199 lc = map_lambda(chainArrivalAtNode{c});
201 aggrArrivalAtNode = mmap_scale(aggrArrivalAtNode, 1/lc, 0); % non-iterative approximation
204 aggrArrivalAtNode = mmap_super_safe({aggrArrivalAtNode, chainArrivalAtNode{c}}, config.space_max,
'default');
208 if (strcmp(sn.sched(ist),SchedStrategy.HOL) && any(sn.classprio ~= sn.classprio(1))) %
if priorities are not identical
209 [uK,iK] = unique(sn.classprio);
210 % BUTools convention: D1=lowest priority, DK=highest priority
211 % LINE convention: lower value = higher priority
212 % unique() returns ascending order, so we need to reverse for BUTools
214 if length(uK) == length(sn.classprio) % if all priorities are different
215 [Qret{iK
'}] = MMAPPH1NPPR({aggrArrivalAtNode{[1;2+iK]}}, {pie{ist}{iK}}, {D0{ist,iK}}, 'ncMoms
', 1);
217 line_error(mfilename,'Solver MAM
requires either identical priorities or all distinct priorities
');
219 elseif (sn.sched(ist)==SchedStrategy.FCFSPRPRIO && any(sn.classprio ~= sn.classprio(1))) % FCFS preemptive resume priority
220 [uK,iK] = unique(sn.classprio);
221 % BUTools convention: D1=lowest priority, DK=highest priority
222 % LINE convention: lower value = higher priority
223 % unique() returns ascending order, so we need to reverse for BUTools
225 if length(uK) == length(sn.classprio) % if all priorities are different
226 [Qret{iK'}] = MMAPPH1PRPR({aggrArrivalAtNode{[1;2+iK]}}, {pie{ist}{iK}}, {D0{ist,iK}},
'ncMoms', 1);
228 line_error(mfilename,
'Solver MAM requires either identical priorities or all distinct priorities');
231 aggrUtil = sum(mmap_lambda(aggrArrivalAtNode)./(GlobalConstants.FineTol+sn.rates(ist,1:K)*sn.nservers(ist)),
'omitnan'); %% to debug
232 aggrLambda = mmap_lambda(aggrArrivalAtNode);
233 if aggrUtil < 1-GlobalConstants.FineTol
235 % Check
for MAP/D/c: single-
class open model with deterministic service
236 isMapDc = (K == 1) && (sn.procid(ist, 1) == ProcessType.DET);
238 % Use exact MAP/D/c solver from Q-MAM
239 D0_arr = aggrArrivalAtNode{1};
240 D1_arr = aggrArrivalAtNode{2};
241 detServiceTime = S(ist, 1); % 1/rate = service time
242 numServers = sn.nservers(ist);
244 mapdcResult = qsys_mapdc(D0_arr, D1_arr, detServiceTime, numServers);
245 Qret{1} = mapdcResult.meanQueueLength;
246 % Store result
for later use to skip surrogate delay adjustment
248 mapdcStations(ist) =
true; % Mark
for skipping post-processing
249 line_debug(
'Using exact MAP/D/%d solver: Q=%.4f, W=%.4f', ...
250 numServers, mapdcResult.meanQueueLength, mapdcResult.meanWaitingTime);
253 [Qret{1:K}] = MMAPPH1FCFS({aggrArrivalAtNode{[1,3:end]}}, {pie{ist}{:}}, {D0{ist,:}},
'ncMoms', 1);
256 maxLevel = sum(N(isfinite(N)))+1;
257 D = {aggrArrivalAtNode{[1,3:end]}};
258 pdistr_k = cell(1,K);
259 if map_lambda(D)< GlobalConstants.FineTol
261 pdistr_k = [1-GlobalConstants.FineTol, GlobalConstants.FineTol];
262 Qret{k} = GlobalConstants.FineTol / sn.rates(ist);
265 if sn.isfunction(ist)
266 alpharate = map_lambda(sn.nodeparam{ist}{end}.setupTime);
267 alphascv = map_scv(sn.nodeparam{ist}{end}.setupTime);
268 betarate = map_lambda(sn.nodeparam{ist}{end}.delayoffTime);
269 betascv = map_scv(sn.nodeparam{ist}{end}.delayoffTime);
271 % TODO: at the moment
this
272 % maps each
class to an
274 % setup-delayoff queue
278 mu_k = 1 / (pie_k * inv(-D0{ist,k}) * ones(size(pie_k))
');
280 [Qret{k}] = qbd_setupdelayoff(aggrLambda, aggrRate, alpharate, alphascv, betarate, betascv);
286 [pdistr] = MMAPPH1FCFS(D, {pie{ist}{:}}, {D0{ist,:}}, 'ncDistr
', maxLevel);
287 % rough approximation
289 pdistr_k = abs(pdistr(1:(N(k)+1)));
290 pdistr_k(end) = abs(1-sum(pdistr(1:end-1)));
291 pdistr_k = pdistr_k / sum(pdistr_k(1:(N(k)+1)));
292 Qret{k} = max(0,min(N(k),(0:N(k))*pdistr_k(1:(N(k)+1))'));
299 Qret{k} = sn.njobs(k);
303 QN(ist,:) = cell2mat(Qret);
305 c = find(sn.chains(:,k),1);
306 TN(ist,k) = rates{ist,c}(k);
307 UN(ist,k) = TN(ist,k) * S(ist,k) / sn.nservers(ist);
310 % For MAP/D/c, use exact results from qsys_mapdc
311 % No surrogate delay adjustment needed
312 RN(ist,k) = mapdcResult.meanSojournTime;
314 % add number of jobs at the surrogate delay server
315 QN(ist,k) = QN(ist,k) + TN(ist,k)*S(ist,k) * (sn.nservers(ist)-1)/sn.nservers(ist);
316 RN(ist,k) = QN(ist,k) ./ TN(ist,k);
322 switch sn.nodetype(ind)
324 % line_error(mfilename,
'Fork nodes not supported yet by MAM solver.');
329 %max(max(abs(TN-TN_1)))
334for it=1:2 % second pass to rescale again QN based on RN correction
336 inchain = sn.inchain{c};
337 Nc = sum(sn.njobs(inchain));
339 QNc = sum(sum(QN(:,inchain)));
340 QN(:,inchain) = QN(:,inchain) * (Nc / QNc);
345 ist = sn.nodeToStation(ind);
346 % Skip stations
using exact MAP/D/c solver (already have exact values)
347 if mapdcStations(ist)
351 if isinf(sn.nservers(ist))
352 RN(ist,k) = S(ist,k);
354 RN(ist,k) = max([S(ist,k), QN(ist,k) ./ TN(ist,k)]);
359 QN(ist,k) = RN(ist,k) .* TN(ist,k);
363 Nc = sum(sn.njobs(inchain)); % closed population
364 if Nc == 0 %
if closed chain