1#ifndef LINE_API_MC_CTMC_MEMORY_GATE_H
2#define LINE_API_MC_CTMC_MEMORY_GATE_H
54#if defined(__unix__) || defined(__APPLE__)
55#include <sys/utsname.h>
103#if defined(_SC_AVPHYS_PAGES) && defined(_SC_PAGE_SIZE)
104 const long pages = ::sysconf(_SC_AVPHYS_PAGES);
105 const long page_size = ::sysconf(_SC_PAGE_SIZE);
106 if (pages > 0 && page_size > 0)
107 return static_cast<double>(pages) *
static_cast<double>(page_size);
117#if defined(__x86_64__) || defined(_M_X64)
119#elif defined(__aarch64__) || defined(_M_ARM64)
121#elif defined(__i386__) || defined(_M_IX86)
126 unsigned ncores = std::thread::hardware_concurrency();
127 if (ncores == 0) ncores = 1;
128 std::string platform =
"unknown";
129#if defined(__unix__) || defined(__APPLE__)
131 if (::uname(&u) == 0) platform = std::string(u.sysname) +
" " + u.machine;
133 platform =
"Windows";
135 return arch +
"|" + std::to_string(ncores) +
"|" + platform;
140 const char* base = std::getenv(
"TMPDIR");
141 std::string dir = (base && *base) ? std::string(base) : std::string(
"/tmp");
142 if (!dir.empty() && dir.back() ==
'/') dir.pop_back();
143 return dir +
"/line_ctmc_calib_cpp.json";
154inline void profile_point(
int g,
double bytes_per_nz, std::size_t& n,
double& bytes,
156 const std::size_t ntot =
static_cast<std::size_t
>(g) *
static_cast<std::size_t
>(g);
157 const std::size_t N = ntot - 1;
159 const std::size_t bw =
static_cast<std::size_t
>(g);
160 const std::size_t w = bw + 1;
161 std::vector<double> U(N * w, 0.0);
162 std::vector<double> work(2 * bw + 1, 0.0);
163 std::size_t nnzL = 0, nnzU = 0;
164 const std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
165 for (std::size_t i = 0; i < N; ++i) {
166 const std::size_t lo = i >= bw ? i - bw : 0;
167 const std::size_t hi = std::min(N - 1, i + bw);
168 std::fill(work.begin(), work.end(), 0.0);
171 const std::size_t r = i % bw, c = i / bw;
174 if (c + 1 < bw) ++deg;
176 if (r + 1 < bw) ++deg;
177 work[i - lo] = -
static_cast<double>(deg);
178 if (c > 0) work[(i - bw) - lo] = 1.0;
179 if (c + 1 < bw && i + bw <= hi) work[(i + bw) - lo] = 1.0;
180 if (r > 0) work[(i - 1) - lo] = 1.0;
181 if (r + 1 < bw && i + 1 <= hi) work[(i + 1) - lo] = 1.0;
182 for (std::size_t k = lo; k < i; ++k) {
183 const double a = work[k - lo];
184 if (a == 0.0)
continue;
185 const double ukk = U[k * w];
186 const double lik = a / ukk;
188 const std::size_t kh = std::min(N - 1, k + bw);
189 for (std::size_t j = k + 1; j <= kh; ++j) work[j - lo] -= lik * U[k * w + (j - k)];
192 for (std::size_t j = i; j <= hi; ++j) {
193 const double v = work[j - lo];
194 U[i * w + (j - i)] = v;
195 if (v != 0.0) ++nnzU;
198 const std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
199 secs = std::chrono::duration<double>(t1 - t0).count();
200 bytes = bytes_per_nz *
static_cast<double>(nnzL + nnzU);
204inline bool fit_power_law(
double x1,
double y1,
double x2,
double y2,
double& alpha,
206 if (x1 <= 0 || x2 <= 0 || y1 <= 0 || y2 <= 0 || x1 == x2)
return false;
207 beta = std::log(y2 / y1) / std::log(x2 / x1);
208 alpha = y1 / std::pow(x1, beta);
213 return std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch())
226 static bool ready =
false;
227 static std::mutex mu;
228 std::lock_guard<std::mutex> lock(mu);
229 if (ready)
return calib;
235 std::ifstream in(cachefile);
239 if (data.contains(
"sig") && data[
"sig"].is_string() && data[
"sig"] == sig &&
240 data.contains(
"alpha_mem") && data.contains(
"beta_mem")) {
242 calib.
alpha_mem = data[
"alpha_mem"].get<
double>();
243 calib.
beta_mem = data[
"beta_mem"].get<
double>();
244 calib.
alpha_t = data.value(
"alpha_t", 0.0);
245 calib.
beta_t = data.value(
"beta_t", 1.0);
246 calib.
timestamp = data.value(
"timestamp", 0.0);
256 std::size_t n1 = 0, n2 = 0;
257 double b1 = 0, b2 = 0, t1 = 0, t2 = 0, am = 0, bm = 0, at = 0, bt = 0;
261 static_cast<double>(n2), b2, am, bm);
263 static_cast<double>(n2), std::max(t2, 1e-9),
275 data[
"sig"] = calib.
sig;
278 data[
"alpha_t"] = calib.
alpha_t;
279 data[
"beta_t"] = calib.
beta_t;
281 std::ofstream out(cachefile);
282 if (out) out << data.dump();
285 if (verbose) std::printf(
"CTMC calibration: bytes ~ %.3g*N^%.3f\n", am, bm);
286 }
catch (
const std::exception& e) {
288 std::printf(
"CTMC calibration failed (%s); using fallback model\n", e.what());
290 if (verbose) std::printf(
"CTMC calibration failed; using fallback model\n");
316 bool verbose =
false) {
319 const double budget = safety_fraction * avail;
325 const double log_pred = std::log(calib.
alpha_mem) + calib.
beta_mem * log_nstates;
326 const double log_budget = std::log(budget > 1.0 ? budget : 1.0);
327 const double gb = 1024.0 * 1024.0 * 1024.0;
329 res.
predicted_gb = std::exp(log_pred < 700.0 ? log_pred : 700.0) / gb;
331 if (log_pred > log_budget) {
332 std::ostringstream os;
333 os.setf(std::ios::fixed);
335 os <<
"CTMC predicted peak memory ~" << res.
predicted_gb
336 <<
" GB exceeds the safe budget ~" << res.
budget_gb <<
" GB ("
337 <<
static_cast<long long>(std::floor(100.0 * safety_fraction + 0.5)) <<
"% of "
339 <<
" GB available). Reduce the state space (e.g. lower 'cutoff'), use the 'mdd' method, "
340 "use another solver (MVA/NC/FLD), or set force=true to override.";
346 if (verbose) std::printf(
"Warning (forced): %s\n", res.
message.c_str());
347 }
else if (verbose && log_pred > std::log(std::max(0.5 * budget, 1.0))) {
348 std::printf(
"CTMC predicted peak memory ~%.2f GB (budget ~%.2f GB).\n", res.
predicted_gb,
NumericError(const std::string &what)
The exception types the port throws.
std::string cache_file()
The reference's cache file, fullfile(tempdir, 'line_ctmc_calib_<lang>.json').
std::string machine_signature()
The reference's ctmc_machine_signature: arch | cores | platform.
void profile_point(int g, double bytes_per_nz, std::size_t &n, double &bytes, double &secs)
The reference's ctmc_profile_point: sparse LU of the (g^2 - 1)-state lattice generator,...
bool fit_power_law(double x1, double y1, double x2, double y2, double &alpha, double &beta)
The reference's ctmc_fit_power_law; false on a degenerate pair.
constexpr double CTMC_DEFAULT_SAFETY_FRACTION
Fraction of available memory the solver may target.
constexpr int CTMC_CALIB_G2
constexpr double CTMC_BYTES_PER_NZ
8 bytes of value plus 8 amortized for the index, per stored nonzero.
constexpr double CTMC_FALLBACK_ALPHA
Fallback power-law coefficients, identical to MATLAB and Python.
CtmcGateResult ctmc_memory_gate(double log_nstates, bool force=false, double safety_fraction=CTMC_DEFAULT_SAFETY_FRACTION, bool verbose=false)
Decide whether a state space of log-size log_nstates can be solved here.
const CtmcCalibration & ctmc_get_calibration(bool verbose=false)
The reference's ctmc_get_calibration: the cached per-host fit, profiled once when the cache is missin...
double ctmc_available_memory_bytes()
Available physical memory in bytes.
constexpr double CTMC_FALLBACK_AVAIL_BYTES
Conservative available-memory default when the host probe fails.
constexpr int CTMC_CALIB_G1
The two lattice sides the reference profiles (G1, G2 of ctmc_get_calibration).
constexpr double CTMC_FALLBACK_BETA
Conservation laws of a layered queueing network, enumerated from its structure.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
The per-host power-law fit, the reference's calib struct.
The gate verdict, plus the message the caller reports either way.