LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_mapmap1_exact.m
1function [ok, QN, UN, RN, TN, CN, XN] = solver_mam_mapmap1_exact(sn)
2% SOLVER_MAM_MAPMAP1_EXACT Exact MAP/MAP/1 solution for a single-class,
3% single-server, open Source -> FCFS Queue -> Sink model.
4%
5% The decomposition methods (dec.source/dec.mmap) approximate this queue: they
6% either fit the arrival to a simpler process or treat the service as a renewal
7% phase-type, discarding autocorrelation. When the arrival or the service is a
8% genuinely correlated (non-renewal) MAP, this routine returns the exact mean
9% queue length via the matrix-geometric MAP/MAP/1 solver (Q_CT_MAP_MAP_1), which
10% carries both phase processes across arrivals and departures.
11%
12% Returns ok=false (and empty metrics) when the model is not an exactly-solvable
13% single MAP/MAP/1 queue, so the caller falls back to the decomposition methods.
14%
15% Copyright (c) 2012-2026, Imperial College London
16% All rights reserved.
17ok = false; QN = []; UN = []; RN = []; TN = []; CN = []; XN = [];
18
19M = sn.nstations;
20K = sn.nclasses;
21if K ~= 1
22 return;
23end
24if ~isinf(sn.njobs)
25 return; % open model only
26end
27
28sourceIdx = find(sn.sched == SchedStrategy.EXT);
29queueIdx = find(sn.sched == SchedStrategy.FCFS);
30if numel(sourceIdx) ~= 1 || numel(queueIdx) ~= 1
31 return;
32end
33if sn.nservers(queueIdx) ~= 1
34 return;
35end
36% The arrival seen by the queue equals the source MAP only when the queue is fed
37% directly by the source (no intermediate stations): require that the source and
38% the queue are the only stations.
39if any(~ismember(1:M, [sourceIdx queueIdx]))
40 return;
41end
42
43arrProc = sn.proc{sourceIdx}{1};
44svcProc = sn.proc{queueIdx}{1};
45if numel(arrProc) < 2 || numel(svcProc) < 2
46 return;
47end
48Da0 = arrProc{1}; Da1 = arrProc{2};
49Ds0 = svcProc{1}; Ds1 = svcProc{2};
50
51% Q_CT_MAP_MAP_1 requires both processes to be genuine MAPs, i.e. (D0,D1) must be
52% a Markovian generator pair. A RAP or an ME process is a non-Markovian
53% generalization whose D0 may carry negative off-diagonal entries: it is a valid
54% point process but not a MAP, and BuTools rejects it outright. Leave those to the
55% fallback path rather than erroring out of an exact routine that cannot take them.
56if ~is_markovian_map(Da0, Da1) || ~is_markovian_map(Ds0, Ds1)
57 return;
58end
59
60% Only fire when the decomposition methods are inexact, i.e. the arrival or the
61% service is a genuinely correlated (non-renewal) MAP. Renewal PH/PH/1 is already
62% solved exactly by the existing decomposition path.
63if is_renewal_map(Da0, Da1) && is_renewal_map(Ds0, Ds1)
64 return;
65end
66
67lambda = map_lambda({Da0, Da1});
68mu = map_lambda({Ds0, Ds1});
69if ~(lambda < mu)
70 return; % unstable or degenerate: leave to the fallback path
71end
72
73ql = Q_CT_MAP_MAP_1(Da0, Da1, Ds0, Ds1, 'MaxNumComp', 100000);
74ql = ql(:);
75EN = sum((0:numel(ql)-1)' .* ql);
76rho = lambda / mu;
77
78QN = zeros(M, K); UN = zeros(M, K); RN = zeros(M, K); TN = zeros(M, K);
79TN(sourceIdx, 1) = lambda;
80TN(queueIdx, 1) = lambda;
81QN(queueIdx, 1) = EN;
82UN(queueIdx, 1) = rho;
83RN(queueIdx, 1) = EN / lambda;
84CN = sum(RN, 1);
85XN = lambda;
86ok = true;
87end
88
89function tf = is_markovian_map(D0, D1)
90% A MAP has non-negative off-diagonal rates in D0, non-negative rates in D1, and
91% (D0+D1) is an infinitesimal generator (zero row sums). A RAP/ME violates the
92% sign conditions while still defining a valid point process.
93ns = size(D0, 1);
94tol = 1e-9 * max(1, max(abs([D0(:); D1(:)])));
95offDiag = D0(~eye(ns) > 0);
96tf = all(offDiag >= -tol) && all(D1(:) >= -tol) ...
97 && all(abs(sum(D0 + D1, 2)) <= tol);
98end
99
100function tf = is_renewal_map(D0, D1)
101% Single definition in mam_is_renewal_map, shared with solver_mam_basic, which
102% needs the same predicate to keep a correlated arrival off the marginal-only
103% PH/M/c closed form.
104tf = mam_is_renewal_map(D0, D1);
105end
Definition Station.m:245