1function [ok, msg] = ctmc_memory_gate(logNstates, force, verbose, safetyFraction)
2% CTMC_MEMORY_GATE Hardware-aware, profiling-calibrated CTMC memory pre-gate.
4% [ok,msg] = ctmc_memory_gate(logNstates, force, verbose, safetyFraction)
6% Decides whether a CTMC steady-state solve of a state space of worst-
case
7% size exp(logNstates)
is safe on
the current host. The budget
is a fraction
8% of
the memory actually available (see lineGetAvailableMemory);
the per-state
9% cost
is calibrated by profiling
the local sparse LU once and caching
the
10% fitted power law per machine in tempdir.
12% ok
is false only when
the predicted peak footprint exceeds
the budget and
13% force
is not set. This replaces
the historical hard-coded threshold.
15if nargin < 2 || isempty(force); force =
false; end
16if nargin < 3 || isempty(verbose); verbose =
false; end
17if nargin < 4 || isempty(safetyFraction); safetyFraction = 0.6; end
19avail = lineGetAvailableMemory();
20budget = safetyFraction * avail;
21calib = ctmc_get_calibration(verbose);
23logPred = log(calib.alpha_mem) + calib.beta_mem * logNstates;
24logBudget = log(max(budget, 1));
25predGB = exp(min(logPred, 700)) / 1024^3;
26budgetGB = budget / 1024^3;
31 msg = sprintf([
'CTMC predicted peak memory ~%.2f GB exceeds the safe ' ...
32 'budget ~%.2f GB (%.0f%% of %.2f GB available). Reduce the state ' ...
33 'space (e.g. lower ''cutoff''), use another solver (MVA/NC/FLD), or ' ...
34 'set force=true to override.'], predGB, budgetGB, ...
35 100*safetyFraction, avail/1024^3);
41 line_printf(
'Warning (forced): %s\n', msg);
43elseif verbose && logPred > log(max(0.5*budget, 1))
44 line_printf('CTMC predicted peak memory ~%.2f GB (budget ~%.2f GB).\n', ...
49% ------------------------------------------------------------------------
50function calib = ctmc_get_calibration(verbose)
51% Return calibrated power-law coefficients {alpha_mem,beta_mem,alpha_t,beta_t}
52%
for factor bytes and factorization time,
using a per-machine tempdir cache.
55FALLBACK_ALPHA = BYTES_PER_NZ * 8;
58sig = ctmc_machine_signature();
59cachefile = fullfile(tempdir,
'line_ctmc_calib_matlab.json');
61if exist(cachefile,
'file')
63 txt = fileread(cachefile);
64 data = jsondecode(txt);
65 if isfield(data, 'sig') && strcmp(data.sig, sig)
74 [n1, b1, t1] = ctmc_profile_point(G1, BYTES_PER_NZ);
75 [n2, b2, t2] = ctmc_profile_point(G2, BYTES_PER_NZ);
76 [am, bm] = ctmc_fit_power_law(n1, b1, n2, b2);
77 [at, bt] = ctmc_fit_power_law(n1, max(t1,1e-9), n2, max(t2,1e-9));
78 calib = struct('sig', sig, 'alpha_mem', am, 'beta_mem', bm, ...
79 'alpha_t', at, 'beta_t', bt, 'timestamp', posixtime(datetime('now')));
81 fid = fopen(cachefile, 'w');
83 fwrite(fid, jsonencode(calib));
89 line_printf('CTMC calibration: bytes ~ %.3g*N^%.3f\n', am, bm);
93 line_printf('CTMC calibration failed (%s); using fallback model\n', ME.message);
95 calib = struct('sig', sig, 'alpha_mem', FALLBACK_ALPHA, ...
96 'beta_mem', FALLBACK_BETA, 'alpha_t', 0, 'beta_t', 1, 'timestamp', posixtime(datetime('now')));
100% ------------------------------------------------------------------------
101function sig = ctmc_machine_signature()
103 ncores = feature('numcores');
107sig = sprintf('%s|%d|%s', computer('arch'), ncores, computer);
110% ------------------------------------------------------------------------
111function [n, bytes, secs] = ctmc_profile_point(g, bytesPerNz)
112% Factorize
the nonsingular block of a g-by-g nearest-neighbour lattice
113% generator (n=g*g states). This QBD-like structure
is representative of
114% multi-station queueing generators and cheap and safe to factorize.
115A = ctmc_lattice_generator(g);
120bytes = bytesPerNz * (nnz(L) + nnz(U));
123% ------------------------------------------------------------------------
124function A = ctmc_lattice_generator(g)
126idx = reshape(1:n, g, g);
129s = idx(:,1:end-1); d = idx(:,2:end);
130src = [src; s(:)]; dst = [dst; d(:)];
132s = idx(:,2:end); d = idx(:,1:end-1);
133src = [src; s(:)]; dst = [dst; d(:)];
135s = idx(1:end-1,:); d = idx(2:end,:);
136src = [src; s(:)]; dst = [dst; d(:)];
138s = idx(2:end,:); d = idx(1:end-1,:);
139src = [src; s(:)]; dst = [dst; d(:)];
140Q = sparse(src, dst, 1, n, n);
141rowsum = full(sum(Q, 2));
142Q = Q - spdiags(rowsum, 0, n, n);
143A = Q(1:n-1, 1:n-1); % drop last state -> nonsingular
146% ------------------------------------------------------------------------
147function [alpha, beta] = ctmc_fit_power_law(x1, y1, x2, y2)
148if x1 <= 0 || x2 <= 0 || y1 <= 0 || y2 <= 0 || x1 == x2
149 error('ctmc_memory_gate:degenerateFit', 'degenerate power-law fit');
151beta = log(y2 / y1) / log(x2 / x1);
152alpha = y1 / (x1 ^ beta);