fix(nmNum): 在允许范围内生成采样参数

- 正态、对数正态按允许范围直接采样,保留拉丁超立方分层。
- 移除样本越界拦截,保留显式上下限校验。
- 分布图同步受限分布,保留 Skin 绘图例外。
feature/Sensitivity-20261012
lvjunjie 1 week ago
parent 64009462e5
commit 98d779931c

@ -56,7 +56,7 @@ public:
Distribution m_distribution;
bool m_distributionInitialized;
double m_mean, m_stdDev, m_peak, m_distributionMin, m_distributionMax;
bool distributionQuantile(double probability, double& value) const;
bool distributionQuantile(double probability, double& value, bool limitToPhysicalRange = true) const;
VariableSampling();
// JSON 序列化 / 反序列化

@ -16,10 +16,9 @@ double normalCdf(double z)
return z < 0.0 ? tail : 1.0 - tail;
}
double normalQuantile(double probability)
double normalQuantile(double probability, double low = -10.0, double high = 10.0)
{
// 在累积分布上二分求逆,避免引入新的数学库。
double low = -10.0, high = 10.0;
for(int i = 0; i < 60; ++i) {
const double mid = (low + high) * 0.5;
if(normalCdf(mid) < probability) low = mid;
@ -409,12 +408,14 @@ QVector<double> nmDataSensitive::VariableSampling::samplingValues() const
}
bool nmDataSensitive::VariableSampling::distributionQuantile(double probability, double& value) const
bool nmDataSensitive::VariableSampling::distributionQuantile(double probability, double& value, bool limitToPhysicalRange) const
{
// 六种分布都由分位数取值;对数正态的均值、标准差仍使用参数原单位。
if(!qIsFinite(probability) || probability <= 0.0 || probability >= 1.0) return false;
const bool bounded = m_distribution >= TRUNCATED_NORMAL;
const double low = m_distributionMin, high = m_distributionMax;
const bool specifiedBounds = m_distribution >= TRUNCATED_NORMAL;
const bool bounded = specifiedBounds || limitToPhysicalRange;
const double low = specifiedBounds ? m_distributionMin : m_modelValue.getMinValue().toDouble();
const double high = specifiedBounds ? m_distributionMax : m_modelValue.getMaxValue().toDouble();
if(bounded && (!qIsFinite(low) || !qIsFinite(high) || low >= high)) return false;
if(m_distribution == UNIFORM) value = low + probability * (high - low);
else if(m_distribution == TRIANGULAR) {
@ -436,14 +437,23 @@ bool nmDataSensitive::VariableSampling::distributionQuantile(double probability,
sigma = std::sqrt(variance);
}
if(!qIsFinite(mean) || !qIsFinite(sigma) || sigma <= 0.0) return false;
double quantile = 0.0;
if(bounded) {
const double a = logarithmic && low == 0.0 ? 0.0
: normalCdf(((logarithmic ? std::log(low) : low) - mean) / sigma);
const double b = normalCdf(((logarithmic ? std::log(high) : high) - mean) / sigma);
// 在允许范围内重新分配概率后取样,保持拉丁超立方分层,不裁剪或重抽越界样本。
double zLow = logarithmic && low == 0.0 ? -std::numeric_limits<double>::infinity()
: ((logarithmic ? std::log(low) : low) - mean) / sigma;
double zHigh = ((logarithmic ? std::log(high) : high) - mean) / sigma;
// 右尾利用正态分布对称性转到左尾,避免两个接近 1 的累积概率相减。
const bool reflected = zLow > 0.0;
if(reflected) { const double previousLow = zLow; zLow = -zHigh; zHigh = -previousLow; }
const double a = normalCdf(zLow), b = normalCdf(zHigh);
if(!(b > a)) return false;
probability = a + probability * (b - a);
}
value = mean + sigma * normalQuantile(probability);
probability = a + (reflected ? 1.0 - probability : probability) * (b - a);
// 超过 40 个标准差时双精度尾概率已下溢;缩小二分区间,同时保留实际边界。
quantile = normalQuantile(probability, qMax(zLow, -40.0), qMin(zHigh, 40.0));
if(reflected) quantile = -quantile;
} else quantile = normalQuantile(probability);
value = mean + sigma * quantile;
if(logarithmic) value = std::exp(value);
}
return qIsFinite(value);

@ -323,7 +323,9 @@ protected:
// 两端加密以平滑显示尾部,有界分布的横轴保留精确上下限。
const double probability = (1.0 - std::cos(3.141592653589793 * i / 200.0)) * 0.5;
cumulativeValues[i] = bounded ? probability : 0.0001 + 0.9998 * probability;
valid = m_variable.distributionQuantile(qBound(1e-10, cumulativeValues[i], 1.0 - 1e-10), x[i]);
// Skin 保留原分布绘图,其余参数显示与实际采样一致的受限分布。
valid = m_variable.distributionQuantile(qBound(1e-10, cumulativeValues[i], 1.0 - 1e-10), x[i],
m_variable.m_varName != "Skin");
if(bounded && i == 0) x[i] = m_variable.m_distributionMin;
if(bounded && i == 200) x[i] = m_variable.m_distributionMax;
valid = valid && convertSamplingValue(m_variable.m_varName, x[i], samplingBaseUnit(m_variable.m_varName),
@ -1086,9 +1088,7 @@ bool nmWxSensitive::saveDataFromUI()
detailStack->setCurrentIndex(idx + 1);
return false;
}
// 均值、众数及有界分布的上下限沿用确定性方法的系统允许范围。
if(valid && variable.m_distribution < nmDataSensitive::VariableSampling::UNIFORM)
valid = validSamplingValue(name, variable.m_mean);
// 均值描述原分布,可以在截断区间之外;显式上下限及众数仍需符合系统范围。
if(valid && variable.m_distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL)
valid = validSamplingValue(name, variable.m_distributionMin) &&
validSamplingValue(name, variable.m_distributionMax);
@ -2119,28 +2119,7 @@ void nmWxSensitive::onPreviewClicked()
QMessageBox::warning(this, tr("Sensitivity"), error);
return;
}
// 普通正态、对数正态仍可能抽到越界值;整批检查通过后才显示预览,不裁剪或重抽。
QVector<double> values;
for(int run = 0; run < m_sensitiveData.getMonteCarloCount(); ++run) {
if(!m_sensitiveData.getRunValues(run, values)) return;
for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
const nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
if(!variable.getEnabled() || validSamplingValue(variable.m_varName, values[i])) continue;
const QString name = variable.m_varName;
const QString unit = variable.m_modelValue.getUnit();
double lower = 0.0, upper = 0.0, value = values[i];
samplingPhysicalRange(name, lower, upper);
convertSamplingValue(name, lower, samplingBaseUnit(name), unit, lower);
convertSamplingValue(name, upper, samplingBaseUnit(name), unit, upper);
convertSamplingValue(name, value, samplingBaseUnit(name), unit, value);
QMessageBox::warning(this, tr("Sensitivity"),
tr("%1: run %2 sampled %3 %4 outside the allowed range [%5, %6]. Adjust the distribution settings or use a bounded distribution.")
.arg(name).arg(run + 1).arg(formatSamplingValue(value, 6)).arg(unit)
.arg(formatSamplingValue(lower, 6)).arg(formatSamplingValue(upper, 6)));
detailStack->setCurrentIndex(findVariableIndex(name) + 1);
return;
}
}
// 样本已在允许范围内生成,直接显示预览。
}
// Saphir 风格的连续运行列表,组合数再大也不显示分页控件。
QDialog dialog(this);

Loading…
Cancel
Save