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)
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.
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.
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.
45% Copyright (c) 2012-2026, Imperial College London
51statefulNodes = find(sn.isstateful)
';
52statefulNodesClasses = [];
53for ind = statefulNodes
54 statefulNodesClasses(end+1:end+K) = ((ind-1)*K+1):(ind*K);
57caches = find(sn.nodetype == NodeType.Cache);
59 line_error(mfilename, 'da_cacheqn_retrieval
requires exactly one Cache node.
');
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.
');
71fetchNode = queueNodes(1);
72fetchStation = sn.nodeToStation(fetchNode);
74totcap = sum(ch.itemcap);
75n_eff = max(1, nitems - totcap); % distinct uncacheable items
78Npop = round(sum(sn.njobs(~isinf(sn.njobs))));
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);
84% --- load-dependent COALESCING rate on the fetch station: lldscaling(k)=k/d(k) ---
85alpha = ones(1, max(1, Npop));
87 d = n_eff * (1 - (1 - 1/n_eff)^k);
90if isempty(sn.lldscaling)
91 sn.lldscaling = ones(sn.nstations, Npop);
92elseif size(sn.lldscaling,2) < Npop
93 sn.lldscaling(:, end+1:Npop) = 1;
95sn.lldscaling(fetchStation, 1:Npop) = alpha;
97% relabel the cache as a class switch
98sn.nodetype(ci) = NodeType.ClassSwitch;
100hitClass = ch.hitclass;
101missClass = ch.missclass;
102retrievalClasses = ch.retrievalClasses; % (item x class) -> retrieval class
103pread = ch.pread{readClass};
104pread = pread(:).' / sum(pread);
106% --- fixed point on the read-
class cache arrival rate ---
107hitprob = zeros(1, K);
108missprob = zeros(1, K);
109delayedprob = zeros(1, K);
112lambda0 = zeros(1, K);
113lambda0(readClass) = 1; % seed
115fpopts.config.da_norm = @(d) norm(d, 1);
116[~, it] = da_fpi(@da_sweep, lambda0, fpopts);
118 function [xnew, xref] = da_sweep(x, itnum) %#ok<INUSD>
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
128 % see _kb/09-ldes-and-cache.md (da_cacheqn_retrieval)
for rationale
130 sn.rtnodes((ci-1)*K+r, :) = 0;
131 sn.rtnodes((ci-1)*K+r, (ci-1)*K+hitClass(r)) = hp;
133 rcls = retrievalClasses(i, r);
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
141 sn.rt = dtmc_stochcomp(sn.rtnodes, statefulNodesClasses);
143 [
visits,
nodevisits, sn] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
149 % throughput ->
new read arrival rate at the cache
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));
157 denom = nv(refnode, r);
159 Xr = sum(res.XN(inchain));
160 lambda(r) = Xr * nv(ci, r) / denom;
162 % see _kb/09-ldes-and-cache.md (da_cacheqn_retrieval)
for rationale
164 missprob(r) = nonhit;