LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
CacheRMF.m
1% Copyright (c) 2012-2026, Imperial College London
2% All rights reserved.
3
4classdef CacheRMF
5 % CacheRMF Multi-list cache with RANDOM(m) replacement as a DDPP.
6 %
7 % Models a cache with h lists of capacities m = [m_1, ..., m_h] and n
8 % items with popularity distribution p = [p_1, ..., p_n]. The state
9 % space tracks in which list each item resides (or if it is outside the
10 % cache).
11 %
12 % State vector layout (1-based MATLAB indexing):
13 % x(i + k * n) = density of item i in list k
14 % where i = 1..n, k = 0..h (k=0 means outside cache, k=1..h are
15 % cache lists)
16 %
17 % Transition dynamics (RANDOM(m) replacement):
18 % When item i is requested (rate p_i) and item i is in list k:
19 % - A uniformly random item j from list k+1 is displaced to list k
20 % - Item i moves from list k to list k+1
21 %
22 % Reference:
23 % N. Gast, "Expected Values Estimated via Mean-Field Approximation
24 % are 1/N-Accurate", Proc. ACM Meas. Anal. Comput. Syst., 2017.
25
26 properties
27 p % double array (1 x n), item popularity distribution
28 m % int array (1 x h), list capacities
29 numberOfItems % int, number of items (n)
30 numberOfLists % int, number of cache lists (h)
31 modelDimension % int, total state dimension n*(h+1)
32 x0 % double array (1 x modelDimension), initial state
33 end
34
35 methods
36 function obj = CacheRMF(p, m)
37 % CacheRMF Constructor.
38 %
39 % Parameters:
40 % p - double array (1 x n), item request probabilities
41 % m - int array (1 x h), capacity of each cache list
42 %
43 % Builds initial state: first m(1) items in list 1, next m(2)
44 % in list 2, etc. Remaining items outside cache (list 0).
45
46 obj.p = p(:)'; % ensure row vector
47 obj.m = m(:)'; % ensure row vector
48 obj.numberOfItems = length(p);
49 obj.numberOfLists = length(m);
50 obj.modelDimension = obj.numberOfItems * (obj.numberOfLists + 1);
51
52 n = obj.numberOfItems;
53 h = obj.numberOfLists;
54
55 % Build initial state
56 obj.x0 = zeros(1, obj.modelDimension);
57 objIdx = 1; % 1-based item counter
58 for k = 1:h
59 for c = 1:m(k)
60 if objIdx <= n
61 obj.x0(obj.index(objIdx, k)) = 1.0;
62 objIdx = objIdx + 1;
63 end
64 end
65 end
66 for i = objIdx:n
67 obj.x0(obj.index(i, 0)) = 1.0;
68 end
69 end
70
71 function idx = index(obj, i, k)
72 % index Map (item i, list k) to flat state vector index.
73 %
74 % Parameters:
75 % i - item index (1-based, 1..n), scalar or vector
76 % k - list index (0-based, 0..h), scalar
77 %
78 % Returns:
79 % idx - 1-based flat index into state vector
80
81 idx = i + k * obj.numberOfItems;
82 end
83
84 function hr = hitRate(obj, x, listNumber)
85 % hitRate Compute hit rate contribution from a specific list.
86 %
87 % Parameters:
88 % x - state vector (1 x modelDimension) or column vector
89 % listNumber - list index (0 = outside cache, 1..h = cache lists)
90 %
91 % Returns:
92 % hr - sum of p(i) * x(index(i, listNumber)) over all items
93
94 indices = obj.index(1:obj.numberOfItems, listNumber);
95 hr = dot(obj.p(:), x(indices(:)));
96 end
97
98 function dX = drift(obj, x)
99 % drift Compute the mean field drift F(x).
100 %
101 % The drift for RANDOM(m) replacement is:
102 % dx(i,k)/dt = -p(i)*x(i,k) + hitRate(k)*x(i,k+1)/m(k)
103 % dx(i,k+1)/dt = p(i)*x(i,k) - hitRate(k)*x(i,k+1)/m(k)
104 %
105 % Parameters:
106 % x - state vector (modelDimension x 1) or (1 x modelDimension)
107 %
108 % Returns:
109 % dX - drift vector, same shape as x
110
111 x = x(:)'; % ensure row vector for internal computation
112 n = obj.numberOfItems;
113 h = obj.numberOfLists;
114
115 hitRates = zeros(1, h + 1);
116 for k = 0:h
117 hitRates(k + 1) = obj.hitRate(x, k);
118 end
119
120 dX = zeros(1, obj.modelDimension);
121 for i = 1:n
122 for k = 0:(h - 1)
123 ik = obj.index(i, k);
124 ik1 = obj.index(i, k + 1);
125 flow = obj.p(i) * x(ik) - hitRates(k + 1) * x(ik1) / obj.m(k + 1);
126 dX(ik) = dX(ik) - flow;
127 dX(ik1) = dX(ik1) + flow;
128 end
129 end
130 dX = dX(:); % return column vector (for ODE solvers)
131 end
132
133 function Fp = jacobian(obj, x)
134 % jacobian Compute Jacobian dF/dx at state x.
135 %
136 % Parameters:
137 % x - state vector (1 x modelDimension)
138 %
139 % Returns:
140 % Fp - Jacobian matrix (modelDimension x modelDimension)
141
142 x = x(:)';
143 n = obj.numberOfItems;
144 h = obj.numberOfLists;
145 dim = obj.modelDimension;
146
147 hitRates = zeros(1, h + 1);
148 for k = 0:h
149 hitRates(k + 1) = obj.hitRate(x, k);
150 end
151
152 Fp = zeros(dim, dim);
153 for i = 1:n
154 for k = 0:(h - 1)
155 ik = obj.index(i, k);
156 ik1 = obj.index(i, k + 1);
157 mk = obj.m(k + 1); % m is 1-indexed, k is 0-based list
158
159 % Direct rate terms
160 Fp(ik, ik) = Fp(ik, ik) - obj.p(i);
161 Fp(ik1, ik) = Fp(ik1, ik) + obj.p(i);
162 Fp(ik, ik1) = Fp(ik, ik1) + hitRates(k + 1) / mk;
163 Fp(ik1, ik1) = Fp(ik1, ik1) - hitRates(k + 1) / mk;
164
165 % Indirect terms via hit rate dependence
166 for j = 1:n
167 jk = obj.index(j, k);
168 jk1 = obj.index(j, k + 1);
169 % d(hitRate[k])/d(x[j,k+1]) = p[j] for list k+1
170 % but here hitRates(k+1) is hitRate for list k
171 Fp(ik, jk1) = Fp(ik, jk1) - obj.p(i) * x(ik) / mk;
172 Fp(ik1, jk1) = Fp(ik1, jk1) + obj.p(i) * x(ik) / mk;
173 Fp(ik, jk) = Fp(ik, jk) + obj.p(j) * x(ik1) / mk;
174 Fp(ik1, jk) = Fp(ik1, jk) - obj.p(j) * x(ik1) / mk;
175 end
176 end
177 end
178 end
179
180 function Fpp = hessian(obj, x) %#ok<INUSD>
181 % hessian Compute Hessian d^2F/dx^2.
182 %
183 % The Hessian is constant (drift is quadratic in x), so x is
184 % unused but kept for interface consistency.
185 %
186 % Parameters:
187 % x - state vector (unused)
188 %
189 % Returns:
190 % Fpp - Hessian tensor (modelDimension x modelDimension x modelDimension)
191
192 n = obj.numberOfItems;
193 h = obj.numberOfLists;
194 dim = obj.modelDimension;
195 Fpp = zeros(dim, dim, dim);
196
197 for i = 1:n
198 for k = 0:(h - 1)
199 ik = obj.index(i, k);
200 ik1 = obj.index(i, k + 1);
201 mk = obj.m(k + 1);
202 for j = 1:n
203 if j ~= i
204 jk = obj.index(j, k);
205 jk1 = obj.index(j, k + 1);
206 % d^2 F[ik] / (d x[jk] d x[ik1]) = p[j]/mk
207 Fpp(ik, jk, ik1) = Fpp(ik, jk, ik1) + obj.p(j) / mk;
208 Fpp(ik, ik1, jk) = Fpp(ik, ik1, jk) + obj.p(j) / mk;
209 % d^2 F[ik] / (d x[jk1] d x[ik]) = -p[i]/mk
210 Fpp(ik, jk1, ik) = Fpp(ik, jk1, ik) - obj.p(i) / mk;
211 Fpp(ik, ik, jk1) = Fpp(ik, ik, jk1) - obj.p(i) / mk;
212 % Symmetric for ik1
213 Fpp(ik1, jk, ik1) = Fpp(ik1, jk, ik1) - obj.p(j) / mk;
214 Fpp(ik1, ik1, jk) = Fpp(ik1, ik1, jk) - obj.p(j) / mk;
215 Fpp(ik1, jk1, ik) = Fpp(ik1, jk1, ik) + obj.p(i) / mk;
216 Fpp(ik1, ik, jk1) = Fpp(ik1, ik, jk1) + obj.p(i) / mk;
217 end
218 end
219 end
220 end
221 end
222
223 function Q = noiseMatrix(obj, x)
224 % noiseMatrix Compute noise intensity matrix Q(x) for the DDPP.
225 %
226 % Q(a,b) = sum_ell ell(a) * ell(b) * beta_ell(x)
227 % where each transition ell is a swap between items i and j
228 % across lists k and k+1, with rate p(i)*x(i,k)*x(j,k+1)/m(k).
229 %
230 % Parameters:
231 % x - state vector (1 x modelDimension)
232 %
233 % Returns:
234 % Q - noise matrix (modelDimension x modelDimension)
235
236 x = x(:)';
237 n = obj.numberOfItems;
238 h = obj.numberOfLists;
239 dim = obj.modelDimension;
240 Q = zeros(dim, dim);
241
242 signs = [-1, 1, 1, -1];
243 for i = 1:n
244 for k = 0:(h - 1)
245 mk = obj.m(k + 1);
246 ik = obj.index(i, k);
247 ik1 = obj.index(i, k + 1);
248 for j = 1:n
249 jk = obj.index(j, k);
250 jk1 = obj.index(j, k + 1);
251 rate = obj.p(i) * x(ik) * x(jk1) / mk;
252 indices = [ik, jk, ik1, jk1];
253 for ia = 1:4
254 for ib = 1:4
255 Q(indices(ia), indices(ib)) = Q(indices(ia), indices(ib)) + rate * signs(ia) * signs(ib);
256 end
257 end
258 end
259 end
260 end
261 end
262
263 function pi = fixedPoint(obj, tmax)
264 % fixedPoint Compute mean field fixed point by ODE integration.
265 %
266 % Integrates dx/dt = F(x) until steady state using ode15s.
267 %
268 % Parameters:
269 % tmax - maximum integration time (default: 10000)
270 %
271 % Returns:
272 % pi - fixed point state vector (1 x modelDimension)
273
274 if nargin < 2
275 tmax = 10000;
276 end
277 opts = odeset('RelTol', 1e-8, 'AbsTol', 1e-10);
278 %[~, Y] = ode15s(@(t, x) obj.drift(x), [0, tmax], obj.x0', opts);
279 [~, Y] = lsoda_solve(@(t, x) obj.drift(x), [0, tmax], obj.x0', opts);
280 pi = Y(end, :);
281 end
282
283 function [P, Pinv, r] = dimensionReduction(obj, Fp)
284 % dimensionReduction SVD-based dimension reduction for singular Jacobian.
285 %
286 % The Jacobian is singular because item populations are conserved
287 % (sum over lists for each item = 1). This method finds a change
288 % of basis that separates the rank-deficient directions.
289 %
290 % Parameters:
291 % Fp - Jacobian matrix (modelDimension x modelDimension)
292 %
293 % Returns:
294 % P - change-of-basis matrix
295 % Pinv - inverse of P
296 % r - rank of Fp
297
298 dim = obj.modelDimension;
299 n = obj.numberOfItems;
300 h = obj.numberOfLists;
301
302 r = rank(Fp);
303
304 % Build change-of-basis: first r rows are independent coordinates,
305 % remaining rows span the null space of Fp
306 C = zeros(dim, dim);
307 d = 1;
308 for lIdx = 0:h
309 for i = 1:(n - 1)
310 C(d, obj.index(i, lIdx)) = 1.0;
311 d = d + 1;
312 end
313 end
314
315 [U, ~, ~] = svd(Fp);
316 C((r + 1):end, :) = U(:, (r + 1):end)';
317 Pinv = inv(C); %#ok<MINV>
318 P = C;
319 end
320
321 function [Fp_r, Fpp_r, Q_r, P, Pinv, r] = reduceFpFppQ(obj, Fp, Fpp, Q)
322 % reduceFpFppQ Apply dimension reduction to Fp, Fpp, Q.
323 %
324 % Projects the Jacobian, Hessian, and noise matrix onto the
325 % non-singular subspace identified by dimension reduction.
326 %
327 % Parameters:
328 % Fp - Jacobian matrix (dim x dim)
329 % Fpp - Hessian tensor (dim x dim x dim)
330 % Q - noise matrix (dim x dim)
331 %
332 % Returns:
333 % Fp_r - reduced Jacobian (r x r)
334 % Fpp_r - reduced Hessian (r x r x r)
335 % Q_r - reduced noise matrix (r x r)
336 % P - change-of-basis matrix
337 % Pinv - inverse of P
338 % r - rank
339
340 [P, Pinv, r] = obj.dimensionReduction(Fp);
341
342 % Reduced Jacobian
343 Fp_full = P * Fp * Pinv;
344 Fp_r = Fp_full(1:r, 1:r);
345
346 % Reduced Hessian via tensor contraction
347 % Fpp_r(a,b,c) = sum_{i,j,k} P(a,i) * Fpp(i,j,k) * Pinv(j,b) * Pinv(k,c)
348 dim = obj.modelDimension;
349 Fpp_r = zeros(r, r, r);
350 % First contraction: T1(a,j,k) = sum_i P(a,i) * Fpp(i,j,k)
351 T1 = zeros(r, dim, dim);
352 for a = 1:r
353 for j = 1:dim
354 for k = 1:dim
355 val = 0;
356 for i = 1:dim
357 val = val + P(a, i) * Fpp(i, j, k);
358 end
359 T1(a, j, k) = val;
360 end
361 end
362 end
363 % Second contraction: T2(a,b,k) = sum_j T1(a,j,k) * Pinv(j,b)
364 T2 = zeros(r, r, dim);
365 for a = 1:r
366 for b = 1:r
367 for k = 1:dim
368 val = 0;
369 for j = 1:dim
370 val = val + T1(a, j, k) * Pinv(j, b);
371 end
372 T2(a, b, k) = val;
373 end
374 end
375 end
376 % Third contraction: Fpp_r(a,b,c) = sum_k T2(a,b,k) * Pinv(k,c)
377 for a = 1:r
378 for b = 1:r
379 for c = 1:r
380 val = 0;
381 for k = 1:dim
382 val = val + T2(a, b, k) * Pinv(k, c);
383 end
384 Fpp_r(a, b, c) = val;
385 end
386 end
387 end
388
389 % Reduced noise matrix
390 Q_full = P * Q * P';
391 Q_r = Q_full(1:r, 1:r);
392 end
393
394 function [V, W] = expandVW(obj, V_r, W_r, Pinv, r) %#ok<INUSL>
395 % expandVW Expand reduced V, W back to full dimension.
396 %
397 % Parameters:
398 % V_r - reduced correction vector (r x 1)
399 % W_r - reduced covariance matrix (r x r)
400 % Pinv - inverse change-of-basis matrix
401 % r - rank
402 %
403 % Returns:
404 % V - full correction vector (modelDimension x 1)
405 % W - full covariance matrix (modelDimension x modelDimension)
406
407 V = Pinv(:, 1:r) * V_r;
408 W = Pinv(:, 1:r) * W_r * Pinv(:, 1:r)';
409 end
410
411 function [pi, V, VW] = meanFieldExpansionSteadyState(obj, order)
412 % meanFieldExpansionSteadyState Compute refined mean field steady-state.
413 %
414 % Computes the mean field fixed point pi and the 1/N correction V
415 % using the Lyapunov equation approach with dimension reduction.
416 %
417 % The refined approximation for a system of N items is:
418 % E[X] ~ pi + V/N + O(1/N^2)
419 %
420 % Parameters:
421 % order - expansion order (0 = plain MF, 1 = with 1/N correction)
422 % default: 1
423 %
424 % Returns:
425 % pi - mean field fixed point (1 x modelDimension)
426 % V - first-order correction (modelDimension x 1)
427 % VW - cell {V, W} with correction and covariance
428
429 if nargin < 2
430 order = 1;
431 end
432
433 pi = obj.fixedPoint();
434
435 if order == 0
436 V = zeros(obj.modelDimension, 1);
437 W = zeros(obj.modelDimension);
438 VW = {V, W};
439 return;
440 end
441
442 Fp = obj.jacobian(pi);
443 Fpp = obj.hessian(pi);
444 Q = obj.noiseMatrix(pi);
445
446 % Dimension reduction: project onto non-singular subspace
447 [Fp_r, Fpp_r, Q_r, ~, Pinv, r] = obj.reduceFpFppQ(Fp, Fpp, Q);
448
449 % Solve Lyapunov equation in reduced space:
450 % Fp_r * W_r + W_r * Fp_r' + Q_r = 0
451 % MATLAB lyap(A, Q) solves A*X + X*A' + Q = 0
452 W_r = lyap(Fp_r, Q_r);
453
454 % First-order correction: Hessian contraction
455 % C_r(a) = sum_{b,c} Fpp_r(a,b,c) * W_r(b,c)
456 C_r = zeros(r, 1);
457 for a = 1:r
458 C_r(a) = sum(sum(squeeze(Fpp_r(a, :, :)) .* W_r));
459 end
460 V_r = -Fp_r \ (C_r / 2);
461
462 % Expand back to full dimension
463 [V, W] = obj.expandVW(V_r, W_r, Pinv, r);
464 VW = {V, W};
465 end
466
467 function [T, X, Vt, Wt] = meanFieldExpansionTransient(obj, time, n_points, order)
468 % meanFieldExpansionTransient Compute refined mean field transient expansion.
469 %
470 % Integrates the coupled ODE system for (X, V, W) where:
471 % X(t): mean field trajectory
472 % V(t): 1/N correction trajectory
473 % W(t): covariance trajectory
474 %
475 % Parameters:
476 % time - maximum integration time (default: 50)
477 % n_points - number of output time points (default: 200)
478 % order - expansion order (0 or 1, default: 1)
479 %
480 % Returns:
481 % T - time points (n_points x 1)
482 % X - mean field trajectory (n_points x modelDimension)
483 % Vt - correction trajectory (n_points x modelDimension)
484 % Wt - covariance trajectory (n_points x modelDimension x modelDimension)
485
486 if nargin < 2 || isempty(time), time = 50; end
487 if nargin < 3 || isempty(n_points), n_points = 200; end
488 if nargin < 4 || isempty(order), order = 1; end
489
490 dim = obj.modelDimension;
491 T = linspace(0, time, n_points)';
492
493 if order == 0
494 odeopt = odeset('RelTol', 1e-6, 'AbsTol', 1e-10);
495 %[~, X] = ode15s(@(t, x) obj.drift(x), T, obj.x0', odeopt);
496 [~, X] = lsoda_solve(@(t, x) obj.drift(x), T, obj.x0', odeopt);
497 Vt = zeros(n_points, dim);
498 Wt = zeros(n_points, dim, dim);
499 return;
500 end
501
502 % Coupled ODE: state = [X (dim), V (dim), W_flat (dim^2)]
503 total_dim = dim + dim + dim * dim;
504 y0 = zeros(total_dim, 1);
505 y0(1:dim) = obj.x0(:);
506
507 function dy = coupled_rhs(~, y)
508 x = y(1:dim);
509 v = y((dim+1):(2*dim));
510 w = reshape(y((2*dim+1):end), dim, dim);
511
512 F = obj.drift(x);
513 Fp = obj.jacobian(x(:)');
514 Fpp = obj.hessian(x(:)');
515 Qmat = obj.noiseMatrix(x(:)');
516
517 dx = F(:);
518 dv = Fp * v;
519 % Add Hessian contraction: 0.5 * tensordot(Fpp, w, axes=([1,2],[0,1]))
520 for a = 1:dim
521 dv(a) = dv(a) + 0.5 * sum(sum(squeeze(Fpp(a, :, :)) .* w));
522 end
523 dw = Fp * w + w * Fp' + Qmat;
524
525 dy = [dx; dv; dw(:)];
526 end
527
528 odeopt = odeset('RelTol', 1e-6, 'AbsTol', 1e-10);
529 %[~, Y] = ode15s(@coupled_rhs, T, y0, odeopt);
530 [~, Y] = lsoda_solve(@coupled_rhs, T, y0, odeopt);
531
532 X = Y(:, 1:dim);
533 Vt = Y(:, (dim+1):(2*dim));
534 W_flat = Y(:, (2*dim+1):end);
535 Wt = reshape(W_flat', dim, dim, []);
536 Wt = permute(Wt, [3, 1, 2]);
537 end
538
539 function hr = hitRatesAll(obj, x)
540 % hitRatesAll Compute hit rates for all lists.
541 %
542 % Parameters:
543 % x - state vector (1 x modelDimension)
544 %
545 % Returns:
546 % hr - array (1 x numberOfLists+1) with hit rate per list
547
548 h = obj.numberOfLists;
549 hr = zeros(1, h + 1);
550 for k = 0:h
551 hr(k + 1) = obj.hitRate(x, k);
552 end
553 end
554
555 function out = convertTo2D(obj, x)
556 % convertTo2D Convert flat state vector to (n x h+1) matrix.
557 %
558 % Parameters:
559 % x - state vector (1 x modelDimension) or matrix
560 % (T x modelDimension) for trajectories
561 %
562 % Returns:
563 % out - array (n x h+1) or (T x n x h+1)
564
565 n = obj.numberOfItems;
566 h = obj.numberOfLists;
567 if isvector(x)
568 x = x(:)';
569 out = zeros(n, h + 1);
570 for i = 1:n
571 for k = 0:h
572 out(i, k + 1) = x(obj.index(i, k));
573 end
574 end
575 else
576 T = size(x, 1);
577 out = zeros(T, n, h + 1);
578 for t = 1:T
579 for i = 1:n
580 for k = 0:h
581 out(t, i, k + 1) = x(t, obj.index(i, k));
582 end
583 end
584 end
585 end
586 end
587 end
588end