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

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

@ -56,7 +56,7 @@ public:
Distribution m_distribution; Distribution m_distribution;
bool m_distributionInitialized; bool m_distributionInitialized;
double m_mean, m_stdDev, m_peak, m_distributionMin, m_distributionMax; 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(); VariableSampling();
// JSON 序列化 / 反序列化 // JSON 序列化 / 反序列化

@ -16,10 +16,9 @@ double normalCdf(double z)
return z < 0.0 ? tail : 1.0 - tail; 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) { for(int i = 0; i < 60; ++i) {
const double mid = (low + high) * 0.5; const double mid = (low + high) * 0.5;
if(normalCdf(mid) < probability) low = mid; 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; if(!qIsFinite(probability) || probability <= 0.0 || probability >= 1.0) return false;
const bool bounded = m_distribution >= TRUNCATED_NORMAL; const bool specifiedBounds = m_distribution >= TRUNCATED_NORMAL;
const double low = m_distributionMin, high = m_distributionMax; 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(bounded && (!qIsFinite(low) || !qIsFinite(high) || low >= high)) return false;
if(m_distribution == UNIFORM) value = low + probability * (high - low); if(m_distribution == UNIFORM) value = low + probability * (high - low);
else if(m_distribution == TRIANGULAR) { else if(m_distribution == TRIANGULAR) {
@ -436,14 +437,23 @@ bool nmDataSensitive::VariableSampling::distributionQuantile(double probability,
sigma = std::sqrt(variance); sigma = std::sqrt(variance);
} }
if(!qIsFinite(mean) || !qIsFinite(sigma) || sigma <= 0.0) return false; if(!qIsFinite(mean) || !qIsFinite(sigma) || sigma <= 0.0) return false;
double quantile = 0.0;
if(bounded) { if(bounded) {
const double a = logarithmic && low == 0.0 ? 0.0 // 在允许范围内重新分配概率后取样,保持拉丁超立方分层,不裁剪或重抽越界样本。
: normalCdf(((logarithmic ? std::log(low) : low) - mean) / sigma); double zLow = logarithmic && low == 0.0 ? -std::numeric_limits<double>::infinity()
const double b = normalCdf(((logarithmic ? std::log(high) : high) - mean) / sigma); : ((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; if(!(b > a)) return false;
probability = a + probability * (b - a); probability = a + (reflected ? 1.0 - probability : probability) * (b - a);
} // 超过 40 个标准差时双精度尾概率已下溢;缩小二分区间,同时保留实际边界。
value = mean + sigma * normalQuantile(probability); 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); if(logarithmic) value = std::exp(value);
} }
return qIsFinite(value); return qIsFinite(value);

@ -323,7 +323,9 @@ protected:
// 两端加密以平滑显示尾部,有界分布的横轴保留精确上下限。 // 两端加密以平滑显示尾部,有界分布的横轴保留精确上下限。
const double probability = (1.0 - std::cos(3.141592653589793 * i / 200.0)) * 0.5; const double probability = (1.0 - std::cos(3.141592653589793 * i / 200.0)) * 0.5;
cumulativeValues[i] = bounded ? probability : 0.0001 + 0.9998 * probability; 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 == 0) x[i] = m_variable.m_distributionMin;
if(bounded && i == 200) x[i] = m_variable.m_distributionMax; if(bounded && i == 200) x[i] = m_variable.m_distributionMax;
valid = valid && convertSamplingValue(m_variable.m_varName, x[i], samplingBaseUnit(m_variable.m_varName), 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); detailStack->setCurrentIndex(idx + 1);
return false; 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) if(valid && variable.m_distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL)
valid = validSamplingValue(name, variable.m_distributionMin) && valid = validSamplingValue(name, variable.m_distributionMin) &&
validSamplingValue(name, variable.m_distributionMax); validSamplingValue(name, variable.m_distributionMax);
@ -2119,28 +2119,7 @@ void nmWxSensitive::onPreviewClicked()
QMessageBox::warning(this, tr("Sensitivity"), error); QMessageBox::warning(this, tr("Sensitivity"), error);
return; 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 风格的连续运行列表,组合数再大也不显示分页控件。 // Saphir 风格的连续运行列表,组合数再大也不显示分页控件。
QDialog dialog(this); QDialog dialog(this);

Loading…
Cancel
Save