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% Require source-adjacent queue (only two stations); see _kb/06-solver-catalog.md for rationale
37if any(~ismember(1:M, [sourceIdx queueIdx]))
38 return;
39end
40
41arrProc = sn.proc{sourceIdx}{1};
42svcProc = sn.proc{queueIdx}{1};
43if numel(arrProc) < 2 || numel(svcProc) < 2
44 return;
45end
46Da0 = arrProc{1}; Da1 = arrProc{2};
47Ds0 = svcProc{1}; Ds1 = svcProc{2};
48
49% Genuine MAPs only (RAP/ME left to fallback); see _kb/06-solver-catalog.md for rationale
50if ~is_markovian_map(Da0, Da1) || ~is_markovian_map(Ds0, Ds1)
51 return;
52end
53
54% Fire only for non-renewal (correlated) processes; see _kb/06-solver-catalog.md for rationale
55if is_renewal_map(Da0, Da1) && is_renewal_map(Ds0, Ds1)
56 return;
57end
58
59lambda = map_lambda({Da0, Da1});
60mu = map_lambda({Ds0, Ds1});
61if ~(lambda < mu)
62 return; % unstable or degenerate: leave to the fallback path
63end
64
65ql = Q_CT_MAP_MAP_1(Da0, Da1, Ds0, Ds1, 'MaxNumComp', 100000);
66ql = ql(:);
67EN = sum((0:numel(ql)-1)' .* ql);
68rho = lambda / mu;
69
70QN = zeros(M, K); UN = zeros(M, K); RN = zeros(M, K); TN = zeros(M, K);
71TN(sourceIdx, 1) = lambda;
72TN(queueIdx, 1) = lambda;
73QN(queueIdx, 1) = EN;
74UN(queueIdx, 1) = rho;
75RN(queueIdx, 1) = EN / lambda;
76CN = sum(RN, 1);
77XN = lambda;
78ok = true;
79end
80
81function tf = is_markovian_map(D0, D1)
82% A MAP has non-negative off-diagonal rates in D0, non-negative rates in D1, and
83% (D0+D1) is an infinitesimal generator (zero row sums). A RAP/ME violates the
84% sign conditions while still defining a valid point process.
85ns = size(D0, 1);
86tol = 1e-9 * max(1, max(abs([D0(:); D1(:)])));
87offDiag = D0(~eye(ns) > 0);
88tf = all(offDiag >= -tol) && all(D1(:) >= -tol) ...
89 && all(abs(sum(D0 + D1, 2)) <= tol);
90end
91
92function tf = is_renewal_map(D0, D1)
93% Single definition in mam_is_renewal_map, shared with solver_mam_basic, which
94% needs the same predicate to keep a correlated arrival off the marginal-only
95% PH/M/c closed form.
96tf = mam_is_renewal_map(D0, D1);
97end