LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
da_cacheqn_retrieval.m
1function [res, hitprob, missprob, delayedprob, it, sn] = da_cacheqn_retrieval(sn, netfun, options)
2% [RES,HITPROB,MISSPROB,DELAYEDPROB,IT,SN] = DA_CACHEQN_RETRIEVAL(SN, NETFUN, OPTIONS)
3%
4% Decomposition-aggregation driver for a CLOSED integrated cache-queueing model
5% whose Cache node has a delayed-hit retrieval system (Cache.setRetrievalSystem).
6% Adapts da_cacheqn: the cache is relabelled as a ClassSwitch (read -> hit / miss)
7% and the finite-population delayed-hit coalescing is made to emerge from the
8% closed AMVA by giving the retrieval (fetch) station a load-dependent COALESCING
9% service rate. With k would-be-miss jobs at the fetch station spanning
10% d(k) = n_eff*(1-(1-1/n_eff)^k) distinct uncached items (n_eff = nitems -
11% totalcapacity), the single fetch server releases a whole coalesced batch per
12% fetch, so the job-completion rate is mu(k) = (1/F)*k/d(k), i.e.
13% lldscaling(fetch,k) = k/d(k). The distinct-fetch (backend) throughput then
14% saturates at 1/F and the delayed-hit fraction = would-be-miss - fetcher/X
15% emerges from the finite population, rather than from an open-arrival closed form.
16%
17% Returns the netsolve result RES, and per-cache hitprob/missprob/delayedprob
18% (1 x nclasses on the read class), the iteration count IT and the mutated SN.
19%
20% LIMITATIONS (FURTHER WORK NEEDED - EXPERIMENTAL):
21% - hitprob/missprob are the true cache probabilities (hit = P(item cached),
22% miss = 1 - hit) and are accurate (uniform: exact m/n; validated vs LDES).
23% delayedprob is returned as 0: the finite-population delayed-hit FRACTION is
24% NOT recovered analytically (it folds into miss). Deriving the exact
25% closed-population coalescing split remains an open problem.
26% - The coalescing THROUGHPUT benefit is captured only in DIRECTION and is
27% UNDERSTATED: vs LDES it recovers the right sign of the throughput gain but a
28% smaller magnitude (e.g. LN ~+21% where LDES shows ~+31%). The load-dependent
29% lldscaling(k)=k/d(k) closure is calibrated for small closed populations (the
30% LN sublayer regime) and drifts for large N.
31% - Absolute LCQ throughput may carry the pre-existing LN(MVA) approximation
32% error for backend-bottleneck models (present with or without retrieval).
33% - Single fetch station (single-backend) only.
34% - NO CLOSED-RETRIEVAL EXAMPLE ships in the suite: every retrieval_* example is
35% OPEN. On a hand-built closed model this driver readily hits a reducible /
36% singular routing and a zero read-rate denominator, so a plain closed model
37% can return an empty or unreliable table. Treat the closed path as
38% experimental and validate any closed model against LDES before trusting it.
39% The counterpart is ported to Python (api/da/cacheqn_retrieval.py) and the
40% JAR (Da_cacheqn_retrieval), which share the same caveats; the C++ SolverMVA
41% (line-mp) deliberately REFUSES it by name (the OPEN retrieval analyzer IS
42% ported there). Verified 2026-07-24 that the open path matches across
43% codebases while the closed path lacks a clean, exampled reference.
44%
45% Copyright (c) 2012-2026, Imperial College London
46% All rights reserved.
47
48I = sn.nnodes;
49K = sn.nclasses;
50
51statefulNodes = find(sn.isstateful)';
52statefulNodesClasses = [];
53for ind = statefulNodes
54 statefulNodesClasses(end+1:end+K) = ((ind-1)*K+1):(ind*K);
55end
56
57caches = find(sn.nodetype == NodeType.Cache);
58if numel(caches) ~= 1
59 line_error(mfilename, 'da_cacheqn_retrieval requires exactly one Cache node.');
60end
61ci = caches(1);
62ch = sn.nodeparam{ci};
63
64% --- retrieval configuration ---
65rk = keys(ch.retrievalSystemQueueIndices);
66readClass = double(rk{1}) + 1; % 1-indexed read class
67queueNodes = double(ch.retrievalSystemQueueIndices(rk{1}));
68if numel(queueNodes) ~= 1
69 line_error(mfilename, 'da_cacheqn_retrieval currently supports a single-station (single-backend) retrieval system.');
70end
71fetchNode = queueNodes(1);
72fetchStation = sn.nodeToStation(fetchNode);
73nitems = ch.nitems;
74totcap = sum(ch.itemcap);
75n_eff = max(1, nitems - totcap); % distinct uncacheable items
76
77% closed population
78Npop = round(sum(sn.njobs(~isinf(sn.njobs))));
79
80% mean fetch service F of the read class at the fetch station (per-item identical here)
81rcls0 = ch.retrievalClasses(1, readClass);
82F = 1 / sn.rates(fetchStation, rcls0);
83
84% --- load-dependent COALESCING rate on the fetch station: lldscaling(k)=k/d(k) ---
85alpha = ones(1, max(1, Npop));
86for k = 1:Npop
87 d = n_eff * (1 - (1 - 1/n_eff)^k);
88 alpha(k) = k / d;
89end
90if isempty(sn.lldscaling)
91 sn.lldscaling = ones(sn.nstations, Npop);
92elseif size(sn.lldscaling,2) < Npop
93 sn.lldscaling(:, end+1:Npop) = 1;
94end
95sn.lldscaling(fetchStation, 1:Npop) = alpha;
96
97% relabel the cache as a class switch
98sn.nodetype(ci) = NodeType.ClassSwitch;
99
100hitClass = ch.hitclass;
101missClass = ch.missclass;
102retrievalClasses = ch.retrievalClasses; % (item x class) -> retrieval class
103pread = ch.pread{readClass};
104pread = pread(:).' / sum(pread);
105
106% --- fixed point on the read-class cache arrival rate ---
107hitprob = zeros(1, K);
108missprob = zeros(1, K);
109delayedprob = zeros(1, K);
110res = struct();
111
112lambda0 = zeros(1, K);
113lambda0(readClass) = 1; % seed
114fpopts = options;
115fpopts.config.da_norm = @(d) norm(d, 1);
116[~, it] = da_fpi(@da_sweep, lambda0, fpopts);
117
118 function [xnew, xref] = da_sweep(x, itnum) %#ok<INUSD>
119 lambda = x;
120
121 % isolated cache occupancy -> per-item uncached (would-be-miss) prob pi0_i
122 [gamma, lambda_cache, ~] = da_cache_isolate(ch, lambda);
123 [~, ~, ~, pi0] = cache_miss_fpi(gamma, ch.itemcap, lambda_cache); % 1 x nitems
124 pi0 = reshape(pi0, 1, []);
125 nonhit = sum(pread .* pi0); % aggregate would-be-miss prob
126 hp = 1 - nonhit;
127
128 % see _kb/09-ldes-and-cache.md (da_cacheqn_retrieval) for rationale
129 r = readClass;
130 sn.rtnodes((ci-1)*K+r, :) = 0;
131 sn.rtnodes((ci-1)*K+r, (ci-1)*K+hitClass(r)) = hp;
132 for i = 1:nitems
133 rcls = retrievalClasses(i, r);
134 if rcls > 0
135 sn.rtnodes((ci-1)*K+r, (fetchNode-1)*K+rcls) = pread(i) * pi0(i);
136 sn.rtnodes((fetchNode-1)*K+rcls, :) = 0;
137 sn.rtnodes((fetchNode-1)*K+rcls, (ci-1)*K+missClass(r)) = 1;
138 sn.rtnodes((ci-1)*K+rcls, :) = 0; % drop the unused Cache->Retr edge
139 end
140 end
141 sn.rt = dtmc_stochcomp(sn.rtnodes, statefulNodesClasses);
142
143 [visits, nodevisits, sn] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
144 sn.visits = visits;
145 sn.nodevisits = nodevisits;
146
147 res = netfun(sn);
148
149 % throughput -> new read arrival rate at the cache
150 nv = cellsum(nodevisits);
151 c = find(sn.chains(:, r));
152 inchain = find(sn.chains(c, :));
153 refnode = sn.stationToNode(sn.refstat(r));
154 if sn.refclass(c) > 0
155 denom = nv(refnode, sn.refclass(c));
156 else
157 denom = nv(refnode, r);
158 end
159 Xr = sum(res.XN(inchain));
160 lambda(r) = Xr * nv(ci, r) / denom;
161
162 % see _kb/09-ldes-and-cache.md (da_cacheqn_retrieval) for rationale
163 hitprob(r) = hp;
164 missprob(r) = nonhit;
165 delayedprob(r) = 0;
166
167 xnew = lambda;
168 xref = x;
169 end
170end