Program Listing for File delay_lib.h¶
↰ Return to documentation for file (src/tdm/delay_lib.h)
#pragma once
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include "json.hpp"
enum class RatioLib {
GIO,
MGT
};
enum class DelayScope {
Default,
Board,
InterBoard,
InterCluster,
InterRack
};
struct LinearModel {
double a = 0.0, b = 0.0;
bool valid = false;
double eval(double x) const
{
return a * x + b;
}
};
struct PWLModel {
std::vector<double> xs;
std::vector<double> ys;
std::vector<double> slopes;
bool valid = false;
static PWLModel fromPoints(std::vector<double> x, std::vector<double> y)
{
PWLModel m;
if (x.size() != y.size() || x.size() < 2) {
m.valid = false;
return m;
}
// sort & unique by x
std::vector<std::pair<double, double>> kv;
kv.reserve(x.size());
for (size_t i = 0; i < x.size(); ++i) kv.push_back({x[i], y[i]});
std::sort(kv.begin(), kv.end(),
[](auto &a, auto &b) { return a.first < b.first; });
// 去重相同x,保留最后一个
std::vector<double> X, Y;
X.reserve(kv.size());
Y.reserve(kv.size());
for (size_t i = 0; i < kv.size(); ++i) {
if (!X.empty() && std::abs(kv[i].first - X.back()) < 1e-12) {
Y.back() = kv[i].second; // 覆盖
} else {
X.push_back(kv[i].first);
Y.push_back(kv[i].second);
}
}
if (X.size() < 2) {
m.valid = false;
return m;
}
m.xs = std::move(X);
m.ys = std::move(Y);
m.slopes.resize(m.xs.size() - 1);
for (size_t i = 0; i < m.slopes.size(); ++i) {
double dx = m.xs[i + 1] - m.xs[i];
m.slopes[i] = (m.ys[i + 1] - m.ys[i]) / dx;
}
m.valid = true;
return m;
}
double eval(double r) const
{
if (!valid)
throw std::runtime_error("PWLModel not valid");
if (r <= xs.front()) {
// 左端外推:用第一段斜率
return ys.front() + (r - xs.front()) * slopes.front();
}
if (r >= xs.back()) {
// 右端外推:用最后一段斜率
return ys.back() + (r - xs.back()) * slopes.back();
}
// 中间插值:找到 r 所在的段
auto it = std::upper_bound(xs.begin(), xs.end(), r);
size_t i = size_t(std::max<int>(0, int(it - xs.begin()) - 1));
return ys[i] + (r - xs[i]) * slopes[i];
}
double slopeAt(double r) const
{
if (!valid)
throw std::runtime_error("PWLModel not valid");
if (r <= xs.front())
return slopes.front();
if (r >= xs.back())
return slopes.back();
auto it = std::upper_bound(xs.begin(), xs.end(), r);
size_t i = size_t(std::max<int>(0, int(it - xs.begin()) - 1));
return slopes[i];
}
};
struct LibModel {
bool has_bypass = false;
double bypass_ratio = 2.0;
double bypass_delay = 0.0;
bool has_low_linear = false;
double low_lo = 1.0, low_hi = 8.0;
LinearModel low_linear;
PWLModel pwl;
LinearModel fallback;
double delay(double r) const
{
// bypass 精确点
if (has_bypass && std::abs(r - bypass_ratio) < 1e-12)
return bypass_delay;
// 低范围线性段
if (has_low_linear && r >= low_lo && r <= low_hi && low_linear.valid) {
return low_linear.eval(r);
}
// 主体 PWL
if (pwl.valid)
return pwl.eval(r);
// 兜底 linear
if (fallback.valid)
return fallback.eval(r);
throw std::runtime_error("LibModel has no valid model to evaluate delay()");
}
double slope(double r) const
{
// bypass 是一个离散点,没有“段”的斜率;可返回:
// - 若落在低范围线性:返回线性斜率
// - 否则用 PWL 的 slope或 fallback 的 a
if (has_low_linear && r >= low_lo && r <= low_hi && low_linear.valid) {
return low_linear.a;
}
if (pwl.valid)
return pwl.slopeAt(r);
if (fallback.valid)
return fallback.a;
throw std::runtime_error("LibModel has no valid model to evaluate slope()");
}
};
struct LibMeta {
double rmin = 1.0, rmax = 1e12;
std::vector<int> choices;
bool has_bounds = false;
};
struct ScopedLibModels {
bool has_gio = false;
bool has_mgt = false;
LibModel gio;
LibModel mgt;
LibMeta gio_meta;
LibMeta mgt_meta;
};
struct DelayLibrary {
LibModel gio, mgt;
LibMeta gio_meta, mgt_meta;
static constexpr int kDelayScopeCount = 5;
std::array<ScopedLibModels, kDelayScopeCount>
scoped_models;
bool has_scoped_models = false;
static inline LinearModel fitLinear(const std::vector<double> &xs,
const std::vector<double> &ys)
{
LinearModel lm;
if (xs.size() < 2 || xs.size() != ys.size())
return lm;
double Sx = 0, Sy = 0, Sxx = 0, Sxy = 0;
const size_t n = xs.size();
for (size_t i = 0; i < n; ++i) {
Sx += xs[i];
Sy += ys[i];
Sxx += xs[i] * xs[i];
Sxy += xs[i] * ys[i];
}
double denom = n * Sxx - Sx * Sx;
if (std::abs(denom) < 1e-12)
return lm;
lm.a = (n * Sxy - Sx * Sy) / denom;
lm.b = (Sy - lm.a * Sx) / n;
lm.valid = true;
return lm;
}
static inline bool parsePoints(const nlohmann::json &arr,
std::vector<double> &xs,
std::vector<double> &ys)
{
if (!arr.is_array() || arr.empty())
return false;
xs.clear();
ys.clear();
xs.reserve(arr.size());
ys.reserve(arr.size());
for (const auto &p : arr) {
if (p.is_array() && p.size() == 2) {
xs.push_back(p[0].get<double>());
ys.push_back(p[1].get<double>());
} else if (p.is_object()) {
if (!p.contains("ratio"))
return false;
double r = p["ratio"].get<double>();
double d = 0.0;
if (p.contains("delay"))
d = p["delay"].get<double>();
else if (p.contains("delay_ns"))
d = p["delay_ns"].get<double>();
else
return false;
xs.push_back(r);
ys.push_back(d);
} else
return false;
}
return xs.size() >= 2;
}
static inline bool parseBypass(const nlohmann::json &b, double &r,
std::optional<double> &dopt)
{
if (!b.is_object() || !b.contains("ratio"))
return false;
r = b["ratio"].get<double>();
if (b.contains("delay")) {
dopt = b["delay"].get<double>();
return true;
}
if (b.contains("delay_ns")) {
dopt = b["delay_ns"].get<double>();
return true;
}
// 只有 ratio:允许,delay 由外部推导
dopt = std::nullopt;
return true;
}
static inline void parseMeta(const nlohmann::json &j, LibMeta &meta)
{
if (j.contains("min_ratio"))
meta.rmin = j["min_ratio"].get<double>();
if (j.contains("max_ratio"))
meta.rmax = j["max_ratio"].get<double>();
meta.has_bounds = j.contains("min_ratio") || j.contains("max_ratio");
if (j.contains("choices") && j["choices"].is_array()) {
meta.choices.clear();
for (auto &c : j["choices"]) meta.choices.push_back(c.get<int>());
}
}
static inline int scopeIndex(DelayScope scope)
{
return static_cast<int>(scope);
}
static inline std::string normalizeName(std::string name)
{
for (char &ch : name) {
unsigned char uch = static_cast<unsigned char>(ch);
if (std::isalnum(uch))
ch = static_cast<char>(std::tolower(uch));
else
ch = '_';
}
return name;
}
static inline std::optional<DelayScope> parseScopeName(
const std::string &name)
{
const std::string n = normalizeName(name);
if (n == "default" || n == "base")
return DelayScope::Default;
if (n == "board" || n == "local" || n == "board_local")
return DelayScope::Board;
if (n == "inter_board" || n == "ibm" || n == "cluster_local")
return DelayScope::InterBoard;
if (n == "inter_cluster" || n == "icm")
return DelayScope::InterCluster;
if (n == "inter_rack" || n == "irm")
return DelayScope::InterRack;
return std::nullopt;
}
static inline double parseOffsetNs(const nlohmann::json &j)
{
if (j.contains("offset_ns"))
return j["offset_ns"].get<double>();
if (j.contains("delay_offset_ns"))
return j["delay_offset_ns"].get<double>();
if (j.contains("add_delay_ns"))
return j["add_delay_ns"].get<double>();
return 0.0;
}
static inline void addDelayOffset(LibModel &model, double offset_ns)
{
if (std::abs(offset_ns) < 1e-12)
return;
if (model.has_bypass)
model.bypass_delay += offset_ns;
if (model.has_low_linear && model.low_linear.valid)
model.low_linear.b += offset_ns;
if (model.pwl.valid) {
for (double &y : model.pwl.ys) y += offset_ns;
}
if (model.fallback.valid)
model.fallback.b += offset_ns;
}
static inline bool modelUsable(const LibModel &model, RatioLib lib)
{
if (lib == RatioLib::GIO) {
return model.pwl.valid || model.fallback.valid || model.has_low_linear ||
model.has_bypass;
}
return model.pwl.valid || model.fallback.valid;
}
static inline void inferBoundsFromPoints(const LibModel &model, LibMeta &meta)
{
if (model.pwl.valid && !meta.has_bounds) {
meta.rmin = model.pwl.xs.front();
meta.rmax = model.pwl.xs.back();
meta.has_bounds = true;
}
}
static inline void buildGioBypassAndLowLinear(const nlohmann::json &j,
LibModel &model)
{
bool bypass_specified = false;
if (j.contains("bypass")) {
double br = 0.0;
std::optional<double> bdopt;
if (parseBypass(j["bypass"], br, bdopt)) {
bypass_specified = true;
model.has_bypass = true;
model.bypass_ratio = br;
if (bdopt.has_value()) {
model.bypass_delay = *bdopt;
} else if (model.pwl.valid) {
const double tol = 1e-9;
auto it =
std::lower_bound(model.pwl.xs.begin(), model.pwl.xs.end(), br);
if (it != model.pwl.xs.end() && std::abs(*it - br) < tol) {
size_t idx = size_t(it - model.pwl.xs.begin());
model.bypass_delay = model.pwl.ys[idx];
} else {
model.bypass_delay = model.pwl.eval(br);
}
}
}
}
if (!bypass_specified && model.pwl.valid && !model.pwl.xs.empty()) {
model.has_bypass = true;
model.bypass_ratio = model.pwl.xs.front();
model.bypass_delay = model.pwl.ys.front();
}
if (model.has_bypass && model.pwl.valid) {
double left_r = model.bypass_ratio, left_d = model.bypass_delay;
double right_r = -1, right_d = 0;
for (size_t i = 0; i < model.pwl.xs.size(); ++i) {
if (model.pwl.xs[i] > left_r + 1e-12) {
right_r = model.pwl.xs[i];
right_d = model.pwl.ys[i];
break;
}
}
if (right_r > 0 && std::abs(right_r - left_r) > 1e-12) {
model.low_linear.valid = true;
model.low_linear.a = (right_d - left_d) / (right_r - left_r);
model.low_linear.b = left_d - model.low_linear.a * left_r;
model.has_low_linear = true;
model.low_lo = left_r;
model.low_hi = right_r;
}
}
}
static inline bool buildStandaloneModel(const nlohmann::json &j, RatioLib lib,
LibModel &model, LibMeta &meta)
{
if (!j.is_object() || !j.contains("points"))
return false;
model = LibModel{};
meta = LibMeta{};
std::vector<double> xs, ys;
if (!parsePoints(j["points"], xs, ys))
return false;
model.pwl = PWLModel::fromPoints(xs, ys);
parseMeta(j, meta);
inferBoundsFromPoints(model, meta);
if (lib == RatioLib::GIO) {
buildGioBypassAndLowLinear(j, model);
if (!model.fallback.valid && model.pwl.valid) {
std::vector<double> fx, fy;
for (size_t i = 0; i < model.pwl.xs.size(); ++i) {
double r = model.pwl.xs[i], d = model.pwl.ys[i];
if (!model.has_bypass || r > model.bypass_ratio + 1e-12) {
fx.push_back(r);
fy.push_back(d);
}
}
if (fx.size() >= 2)
model.fallback = fitLinear(fx, fy);
}
} else if (!model.fallback.valid && model.pwl.valid) {
model.fallback = fitLinear(model.pwl.xs, model.pwl.ys);
}
addDelayOffset(model, parseOffsetNs(j));
return modelUsable(model, lib);
}
static inline bool buildScopedModel(const nlohmann::json &j, RatioLib lib,
const LibModel &base_model,
const LibMeta &base_meta, LibModel &model,
LibMeta &meta)
{
if (!j.is_object())
return false;
if (j.contains("points"))
return buildStandaloneModel(j, lib, model, meta);
if (j.contains("inherit") || j.contains("offset_ns") ||
j.contains("delay_offset_ns") || j.contains("add_delay_ns")) {
model = base_model;
meta = base_meta;
addDelayOffset(model, parseOffsetNs(j));
parseMeta(j, meta);
return modelUsable(model, lib);
}
return false;
}
inline void initDefaultScopeModels()
{
auto &base = scoped_models[scopeIndex(DelayScope::Default)];
base.has_gio = true;
base.has_mgt = true;
base.gio = gio;
base.mgt = mgt;
base.gio_meta = gio_meta;
base.mgt_meta = mgt_meta;
}
inline bool parseScopeModels(const nlohmann::json &j)
{
initDefaultScopeModels();
if (!j.contains("scopes"))
return true;
if (!j["scopes"].is_object())
return false;
for (auto it = j["scopes"].begin(); it != j["scopes"].end(); ++it) {
auto scope_opt = parseScopeName(it.key());
if (!scope_opt.has_value())
return false;
const int idx = scopeIndex(*scope_opt);
const auto &sj = it.value();
if (!sj.is_object())
return false;
ScopedLibModels scoped;
if (sj.contains("gio")) {
if (!buildScopedModel(sj["gio"], RatioLib::GIO, gio, gio_meta,
scoped.gio, scoped.gio_meta))
return false;
scoped.has_gio = true;
}
if (sj.contains("mgt")) {
if (!buildScopedModel(sj["mgt"], RatioLib::MGT, mgt, mgt_meta,
scoped.mgt, scoped.mgt_meta))
return false;
scoped.has_mgt = true;
}
if (!scoped.has_gio && !scoped.has_mgt) {
scoped.gio = gio;
scoped.mgt = mgt;
scoped.gio_meta = gio_meta;
scoped.mgt_meta = mgt_meta;
addDelayOffset(scoped.gio, parseOffsetNs(sj));
addDelayOffset(scoped.mgt, parseOffsetNs(sj));
scoped.has_gio = true;
scoped.has_mgt = true;
}
scoped_models[idx] = std::move(scoped);
has_scoped_models = true;
}
return true;
}
bool buildFromJson(const nlohmann::json &j)
{
// 1) GIO
if (!j.contains("gio") || !j["gio"].is_object())
return false;
{
const auto &gj = j["gio"];
std::vector<double> xs, ys;
if (gj.contains("points")) {
if (!parsePoints(gj["points"], xs, ys))
return false;
gio.pwl = PWLModel::fromPoints(xs, ys);
}
parseMeta(gj, gio_meta);
// 若未显式给 rmin/rmax,则用 points 自动推断
if (gio.pwl.valid && !gio_meta.has_bounds) {
gio_meta.rmin = gio.pwl.xs.front();
gio_meta.rmax = gio.pwl.xs.back();
gio_meta.has_bounds = true;
}
// bypass 单独挂在 root 或 gio 下都支持
// 同时支持:只给 ratio,不给 delay(delay 从 points/PWL 推导)
auto apply_bypass = [&](const nlohmann::json &bj) -> bool {
double br = 0.0;
std::optional<double> bdopt;
if (!parseBypass(bj, br, bdopt))
return false;
gio.has_bypass = true;
gio.bypass_ratio = br;
if (bdopt.has_value()) {
// JSON 显式给了 bypass delay:原行为
gio.bypass_delay = *bdopt;
return true;
}
// JSON 只给 ratio:delay 从 points/PWL 推导,避免重复定义
// 注意:这里不要调用 discreteDelay(),因为 has_bypass 已经置 true,
// discreteDelay 会优先返回 bypass_delay(尚未赋值,可能是 0)。
if (gio.pwl.valid) {
const double tol = 1e-9;
auto it = std::lower_bound(gio.pwl.xs.begin(), gio.pwl.xs.end(), br);
if (it != gio.pwl.xs.end() && std::abs(*it - br) < tol) {
size_t idx = size_t(it - gio.pwl.xs.begin());
gio.bypass_delay = gio.pwl.ys[idx];
return true;
}
// 找不到离散点就用 PWL 估值
gio.bypass_delay = gio.pwl.eval(br);
return true;
}
// 3) 再否则:没法推导
return false;
};
bool bypass_specified = false;
if (j.contains("bypass")) {
bypass_specified = true;
if (!apply_bypass(j["bypass"]))
return false;
} else if (gj.contains("bypass")) {
bypass_specified = true;
if (!apply_bypass(gj["bypass"]))
return false;
}
// 如果 JSON 没写 bypass:默认把 points 的最小点当 bypass
if (!bypass_specified) {
if (gio.pwl.valid && gio.pwl.xs.size() >= 1) {
gio.has_bypass = true;
gio.bypass_ratio = gio.pwl.xs.front();
gio.bypass_delay = gio.pwl.ys.front();
}
}
// 自动构造 GIO 低段线性(bypass -> 第一段 ≥bypass 的点)
if (gio.has_bypass && gio.pwl.valid) {
// 找到第一个 >= bypass_ratio 的 pwl 点
double left_r = gio.bypass_ratio, left_d = gio.bypass_delay;
double right_r = -1, right_d = 0;
for (size_t i = 0; i < gio.pwl.xs.size(); ++i) {
if (gio.pwl.xs[i] > left_r + 1e-12) {
right_r = gio.pwl.xs[i];
right_d = gio.pwl.ys[i];
break;
}
}
if (right_r > 0 && std::abs(right_r - left_r) > 1e-12) {
gio.low_linear.valid = true;
gio.low_linear.a = (right_d - left_d) / (right_r - left_r);
gio.low_linear.b = left_d - gio.low_linear.a * left_r;
gio.has_low_linear = true;
gio.low_lo = left_r;
gio.low_hi = right_r; // 例如 [1,8]
}
}
// 线性回归 fallback(建议:GIO 用 r>=8 的点做回归,避免把直连混进去)
if (!gio.fallback.valid && gio.pwl.valid) {
std::vector<double> fx, fy;
for (size_t i = 0; i < gio.pwl.xs.size(); ++i) {
double r = gio.pwl.xs[i], d = gio.pwl.ys[i];
if (!gio.has_bypass || r > gio.bypass_ratio + 1e-12) {
fx.push_back(r);
fy.push_back(d);
}
}
if (fx.size() >= 2)
gio.fallback = fitLinear(fx, fy);
}
}
// 2) MGT
if (!j.contains("mgt") || !j["mgt"].is_object())
return false;
{
const auto &mj = j["mgt"];
std::vector<double> xs, ys;
if (mj.contains("points")) {
if (!parsePoints(mj["points"], xs, ys))
return false;
mgt.pwl = PWLModel::fromPoints(xs, ys);
}
parseMeta(mj, mgt_meta);
// 若未显式给 rmin/rmax,则用 points 自动推断
if (mgt.pwl.valid && !mgt_meta.has_bounds) {
mgt_meta.rmin = mgt.pwl.xs.front();
mgt_meta.rmax = mgt.pwl.xs.back();
mgt_meta.has_bounds = true;
}
if (!mgt.fallback.valid && mgt.pwl.valid) {
mgt.fallback = fitLinear(mgt.pwl.xs, mgt.pwl.ys);
}
}
// 最少保证能评估
bool gio_ok = gio.pwl.valid || gio.fallback.valid || gio.has_low_linear ||
gio.has_bypass;
bool mgt_ok = mgt.pwl.valid || mgt.fallback.valid;
if (!(gio_ok && mgt_ok))
return false;
return parseScopeModels(j);
}
inline double delayGio(double r) const
{
return gio.delay(r);
}
inline double delayMgt(double r) const
{
return mgt.delay(r);
}
inline double slopeGio(double r) const
{
return gio.slope(r);
}
inline double slopeMgt(double r) const
{
return mgt.slope(r);
}
inline double clampGio(double r) const
{
return std::max(gio_meta.rmin, std::min(r, gio_meta.rmax));
}
inline double clampMgt(double r) const
{
return std::max(mgt_meta.rmin, std::min(r, mgt_meta.rmax));
}
inline double delay(RatioLib lib, double r) const
{
return (lib == RatioLib::GIO) ? delayGio(r) : delayMgt(r);
}
inline double slope(RatioLib lib, double r) const
{
return (lib == RatioLib::GIO) ? slopeGio(r) : slopeMgt(r);
}
inline const LibModel &modelFor(DelayScope scope, RatioLib lib) const
{
const int idx = scopeIndex(scope);
if (idx >= 0 && idx < kDelayScopeCount) {
const auto &scoped = scoped_models[idx];
if (lib == RatioLib::GIO && scoped.has_gio)
return scoped.gio;
if (lib == RatioLib::MGT && scoped.has_mgt)
return scoped.mgt;
}
return (lib == RatioLib::GIO) ? gio : mgt;
}
inline const LibMeta &metaFor(DelayScope scope, RatioLib lib) const
{
const int idx = scopeIndex(scope);
if (idx >= 0 && idx < kDelayScopeCount) {
const auto &scoped = scoped_models[idx];
if (lib == RatioLib::GIO && scoped.has_gio)
return scoped.gio_meta;
if (lib == RatioLib::MGT && scoped.has_mgt)
return scoped.mgt_meta;
}
return (lib == RatioLib::GIO) ? gio_meta : mgt_meta;
}
inline double delay(DelayScope scope, RatioLib lib, double r) const
{
return modelFor(scope, lib).delay(r);
}
inline double slope(DelayScope scope, RatioLib lib, double r) const
{
return modelFor(scope, lib).slope(r);
}
inline double delayGio(DelayScope scope, double r) const
{
return delay(scope, RatioLib::GIO, r);
}
inline double delayMgt(DelayScope scope, double r) const
{
return delay(scope, RatioLib::MGT, r);
}
inline double slopeGio(DelayScope scope, double r) const
{
return slope(scope, RatioLib::GIO, r);
}
inline double slopeMgt(DelayScope scope, double r) const
{
return slope(scope, RatioLib::MGT, r);
}
inline double minRatio(RatioLib lib) const
{
return (lib == RatioLib::GIO) ? gio_meta.rmin : mgt_meta.rmin;
}
inline double maxRatio(RatioLib lib) const
{
return (lib == RatioLib::GIO) ? gio_meta.rmax : mgt_meta.rmax;
}
inline double minRatio(DelayScope scope, RatioLib lib) const
{
return metaFor(scope, lib).rmin;
}
inline double maxRatio(DelayScope scope, RatioLib lib) const
{
return metaFor(scope, lib).rmax;
}
inline std::vector<int> choices(RatioLib lib) const
{
const LibMeta &meta = (lib == RatioLib::GIO) ? gio_meta : mgt_meta;
const LibModel &mod = (lib == RatioLib::GIO) ? gio : mgt;
if (!meta.choices.empty())
return meta.choices;
// 没给 choices,则根据 PWL 的 xs 自动生成(并加入 bypass 点)
std::vector<int> out;
if (mod.pwl.valid) {
out.reserve(mod.pwl.xs.size() + 1);
for (double r : mod.pwl.xs) out.push_back((int)std::llround(r));
}
if (mod.has_bypass)
out.push_back((int)std::llround(mod.bypass_ratio));
// 去重、排序、移除非正
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
out.erase(
std::remove_if(out.begin(), out.end(), [](int x) { return x <= 0; }),
out.end());
return out;
}
inline std::vector<int> choices(DelayScope scope, RatioLib lib) const
{
const LibMeta &meta = metaFor(scope, lib);
const LibModel &mod = modelFor(scope, lib);
if (!meta.choices.empty())
return meta.choices;
std::vector<int> out;
if (mod.pwl.valid) {
out.reserve(mod.pwl.xs.size() + 1);
for (double r : mod.pwl.xs) out.push_back((int)std::llround(r));
}
if (mod.has_bypass)
out.push_back((int)std::llround(mod.bypass_ratio));
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
out.erase(
std::remove_if(out.begin(), out.end(), [](int x) { return x <= 0; }),
out.end());
return out;
}
inline bool hasChoice(RatioLib lib, int r) const
{
auto cs = choices(lib);
return std::find(cs.begin(), cs.end(), r) != cs.end();
}
inline bool hasChoice(DelayScope scope, RatioLib lib, int r) const
{
auto cs = choices(scope, lib);
return std::find(cs.begin(), cs.end(), r) != cs.end();
}
inline std::optional<double> discreteDelay(RatioLib lib, double r) const
{
return discreteDelay(DelayScope::Default, lib, r);
}
inline std::optional<double> discreteDelay(DelayScope scope, RatioLib lib,
double r) const
{
const LibModel &mod = modelFor(scope, lib);
const double tol = 1e-9;
// 先查 points(更客观)
if (mod.pwl.valid) {
auto it = std::lower_bound(mod.pwl.xs.begin(), mod.pwl.xs.end(), r);
if (it != mod.pwl.xs.end() && std::abs(*it - r) < tol) {
size_t idx = size_t(it - mod.pwl.xs.begin());
return mod.pwl.ys[idx];
}
}
// 再查 bypass(避免覆盖 points 的同 ratio)
if (mod.has_bypass && std::abs(r - mod.bypass_ratio) < tol)
return mod.bypass_delay;
return std::nullopt;
}
};