|
|
|
@ -342,8 +342,27 @@ protected:
|
|
|
|
const double scale = std::pow(10.0, std::floor(std::log10(roughStep)));
|
|
|
|
const double scale = std::pow(10.0, std::floor(std::log10(roughStep)));
|
|
|
|
const double fraction = roughStep / scale;
|
|
|
|
const double fraction = roughStep / scale;
|
|
|
|
const double step = (fraction <= 1.0 ? 1.0 : fraction <= 2.0 ? 2.0 : fraction <= 5.0 ? 5.0 : 10.0) * scale;
|
|
|
|
const double step = (fraction <= 1.0 ? 1.0 : fraction <= 2.0 ? 2.0 : fraction <= 5.0 ? 5.0 : 10.0) * scale;
|
|
|
|
const double axisMin = std::floor(x.first() / step) * step;
|
|
|
|
double axisMin = std::floor(x.first() / step) * step;
|
|
|
|
const double axisMax = std::ceil(x.last() / step) * step;
|
|
|
|
double axisMax = std::ceil(x.last() / step) * step;
|
|
|
|
|
|
|
|
// 除 Skin 外,绘图范围与系统允许范围取交集;只裁剪显示,不改变分布概率。
|
|
|
|
|
|
|
|
if(m_variable.m_varName != "Skin") {
|
|
|
|
|
|
|
|
double minimum = 0.0, maximum = 0.0;
|
|
|
|
|
|
|
|
const QString unit = m_variable.m_modelValue.getUnit();
|
|
|
|
|
|
|
|
if(samplingPhysicalRange(m_variable.m_varName, minimum, maximum) &&
|
|
|
|
|
|
|
|
convertSamplingValue(m_variable.m_varName, minimum, samplingBaseUnit(m_variable.m_varName), unit, minimum) &&
|
|
|
|
|
|
|
|
convertSamplingValue(m_variable.m_varName, maximum, samplingBaseUnit(m_variable.m_varName), unit, maximum)) {
|
|
|
|
|
|
|
|
axisMin = qMax(axisMin, minimum);
|
|
|
|
|
|
|
|
axisMax = qMin(axisMax, maximum);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if(axisMax <= axisMin) {
|
|
|
|
|
|
|
|
painter.drawText(plot, Qt::AlignCenter, nmWxSensitive::tr("Distribution outside the allowed range"));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 曲线保留真实边界,坐标刻度向外取整;压力下限 0.1 等情形也能显示 0 刻度。
|
|
|
|
|
|
|
|
const double curveMin = axisMin, curveMax = axisMax;
|
|
|
|
|
|
|
|
axisMin = std::floor(curveMin / step) * step;
|
|
|
|
|
|
|
|
axisMax = std::ceil(curveMax / step) * step;
|
|
|
|
QVector<double> density(201);
|
|
|
|
QVector<double> density(201);
|
|
|
|
double peak = 0.0;
|
|
|
|
double peak = 0.0;
|
|
|
|
for(int i = 0; i < x.size(); ++i) {
|
|
|
|
for(int i = 0; i < x.size(); ++i) {
|
|
|
|
@ -355,9 +374,13 @@ protected:
|
|
|
|
peak = qMax(peak, density[i]);
|
|
|
|
peak = qMax(peak, density[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 蓝色点状主网格配合黑色主、次刻度,概率轴按 0.1 标注。
|
|
|
|
// 蓝色点状主网格配合黑色主、次刻度,概率轴按 0.1 标注。
|
|
|
|
const int intervals = qRound((axisMax - axisMin) / step);
|
|
|
|
const double tickStart = std::floor(axisMin / step) * step;
|
|
|
|
|
|
|
|
const int intervals = int(std::ceil((axisMax - tickStart) / step));
|
|
|
|
|
|
|
|
// 仅容忍刻度计算的浮点舍入误差。
|
|
|
|
|
|
|
|
const double tickTolerance = DBL_EPSILON * 8.0 * qMax(qAbs(axisMin), qAbs(axisMax));
|
|
|
|
for(int i = 0; i <= intervals * 5; ++i) {
|
|
|
|
for(int i = 0; i <= intervals * 5; ++i) {
|
|
|
|
const double value = axisMin + i * step / 5.0;
|
|
|
|
const double value = tickStart + i * step / 5.0;
|
|
|
|
|
|
|
|
if(value < axisMin - tickTolerance || value > axisMax + tickTolerance) continue;
|
|
|
|
const double px = dataArea.left() + (value - axisMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
const double px = dataArea.left() + (value - axisMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
if(i % 5 == 0) {
|
|
|
|
if(i % 5 == 0) {
|
|
|
|
painter.setPen(QPen(QColor(100, 120, 255), 1, Qt::DotLine));
|
|
|
|
painter.setPen(QPen(QColor(100, 120, 255), 1, Qt::DotLine));
|
|
|
|
@ -388,10 +411,16 @@ protected:
|
|
|
|
cumulative << QPointF(px, dataArea.bottom() - cumulativeValues[i] * dataArea.height());
|
|
|
|
cumulative << QPointF(px, dataArea.bottom() - cumulativeValues[i] * dataArea.height());
|
|
|
|
probability << QPointF(px, dataArea.bottom() - (peak > 0.0 ? density[i] / peak : 0.0) * dataArea.height());
|
|
|
|
probability << QPointF(px, dataArea.bottom() - (peak > 0.0 ? density[i] / peak : 0.0) * dataArea.height());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 曲线也按允许的横轴区间裁剪,避免负值部分画进坐标留白或标签区域。
|
|
|
|
|
|
|
|
painter.save();
|
|
|
|
|
|
|
|
const double clipLeft = dataArea.left() + (curveMin - axisMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
|
|
|
|
const double clipWidth = (curveMax - curveMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
|
|
|
|
painter.setClipRect(QRectF(clipLeft, plot.top(), clipWidth, plot.height()));
|
|
|
|
painter.setPen(QPen(Qt::blue, 1.5));
|
|
|
|
painter.setPen(QPen(Qt::blue, 1.5));
|
|
|
|
painter.drawPolyline(cumulative);
|
|
|
|
painter.drawPolyline(cumulative);
|
|
|
|
painter.setPen(QPen(Qt::red, 1, Qt::DashLine));
|
|
|
|
painter.setPen(QPen(Qt::red, 1, Qt::DashLine));
|
|
|
|
painter.drawPolyline(probability);
|
|
|
|
painter.drawPolyline(probability);
|
|
|
|
|
|
|
|
painter.restore();
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
painter.drawRect(plot);
|
|
|
|
painter.drawRect(plot);
|
|
|
|
painter.save();
|
|
|
|
painter.save();
|
|
|
|
@ -891,11 +920,6 @@ void nmWxSensitive::refreshModelValues()
|
|
|
|
variable.m_stdDev = model == 0.0 ? 5.0 / 3.0 : std::fabs(model) / 3.0;
|
|
|
|
variable.m_stdDev = model == 0.0 ? 5.0 / 3.0 : std::fabs(model) / 3.0;
|
|
|
|
variable.m_distributionMin = minimum;
|
|
|
|
variable.m_distributionMin = minimum;
|
|
|
|
variable.m_distributionMax = maximum;
|
|
|
|
variable.m_distributionMax = maximum;
|
|
|
|
// Monte-Carlo 的 Skin 分布允许负值,不能沿用求解器截断后的范围。
|
|
|
|
|
|
|
|
if(name == "Skin") {
|
|
|
|
|
|
|
|
variable.m_distributionMin = model == 0.0 ? -1.0 : qMin(model * 0.9, model * 1.1);
|
|
|
|
|
|
|
|
variable.m_distributionMax = model == 0.0 ? 1.0 : qMax(model * 0.9, model * 1.1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
variable.m_peak = qBound(variable.m_distributionMin, model * 1.01, variable.m_distributionMax);
|
|
|
|
variable.m_peak = qBound(variable.m_distributionMin, model * 1.01, variable.m_distributionMax);
|
|
|
|
variable.m_distributionInitialized = true;
|
|
|
|
variable.m_distributionInitialized = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1051,21 +1075,28 @@ bool nmWxSensitive::saveDataFromUI()
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
const int idx = findVariableIndex(name);
|
|
|
|
const int idx = findVariableIndex(name);
|
|
|
|
if (idx < 0 || !isSupportedSamplingVariable(name)) continue;
|
|
|
|
if (idx < 0 || !isSupportedSamplingVariable(name)) continue;
|
|
|
|
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
|
|
|
const QString baseUnit = samplingBaseUnit(name);
|
|
|
|
|
|
|
|
bool valid = true;
|
|
|
|
if(candidate.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
|
if(candidate.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
|
double median = 0.0;
|
|
|
|
double median = 0.0;
|
|
|
|
const bool valid = readDistributionPage(idx, variable) && variable.distributionQuantile(0.5, median);
|
|
|
|
valid = readDistributionPage(idx, variable) && variable.distributionQuantile(0.5, median);
|
|
|
|
if(!valid && variable.getEnabled()) {
|
|
|
|
if(!valid && variable.getEnabled()) {
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"), tr("%1: invalid distribution parameters.").arg(name));
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"), tr("%1: invalid distribution parameters.").arg(name));
|
|
|
|
detailStack->setCurrentIndex(idx + 1);
|
|
|
|
detailStack->setCurrentIndex(idx + 1);
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
// 均值、众数及有界分布的上下限沿用确定性方法的系统允许范围。
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
|
|
|
if(valid && variable.m_distribution == nmDataSensitive::VariableSampling::TRIANGULAR)
|
|
|
|
|
|
|
|
valid = validSamplingValue(name, variable.m_peak);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
variable.setMode(static_cast<nmDataSensitive::VariableSampling::Mode>(m_modeCombos[idx]->currentIndex()));
|
|
|
|
variable.setMode(static_cast<nmDataSensitive::VariableSampling::Mode>(m_modeCombos[idx]->currentIndex()));
|
|
|
|
variable.setLog(m_logChecks[idx]->isChecked());
|
|
|
|
variable.setLog(m_logChecks[idx]->isChecked());
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
|
|
|
const QString baseUnit = samplingBaseUnit(name);
|
|
|
|
|
|
|
|
bool valid = true;
|
|
|
|
|
|
|
|
if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC) {
|
|
|
|
if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC) {
|
|
|
|
bool minOk = false, maxOk = false;
|
|
|
|
bool minOk = false, maxOk = false;
|
|
|
|
double minimum = samplingEditValue(m_minSpins[idx], &minOk);
|
|
|
|
double minimum = samplingEditValue(m_minSpins[idx], &minOk);
|
|
|
|
@ -1099,13 +1130,14 @@ bool nmWxSensitive::saveDataFromUI()
|
|
|
|
valid = valid && values.size() == variable.getNumber();
|
|
|
|
valid = valid && values.size() == variable.getNumber();
|
|
|
|
if (valid) variable.setValues(values);
|
|
|
|
if (valid) variable.setValues(values);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if (!valid && variable.getEnabled()) {
|
|
|
|
if (!valid && variable.getEnabled()) {
|
|
|
|
double lower = 0.0, upper = 0.0;
|
|
|
|
double lower = 0.0, upper = 0.0;
|
|
|
|
const bool hasRange = samplingPhysicalRange(name, lower, upper) &&
|
|
|
|
const bool hasRange = samplingPhysicalRange(name, lower, upper) &&
|
|
|
|
convertSamplingValue(name, lower, baseUnit, unit, lower) &&
|
|
|
|
convertSamplingValue(name, lower, baseUnit, unit, lower) &&
|
|
|
|
convertSamplingValue(name, upper, baseUnit, unit, upper);
|
|
|
|
convertSamplingValue(name, upper, baseUnit, unit, upper);
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"),
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"),
|
|
|
|
hasRange ? tr("%1: invalid sampling values. Allowed range: [%2, %3] %4. Check the range and every manual row.")
|
|
|
|
hasRange ? tr("%1: invalid sampling values. Allowed range: [%2, %3] %4. Check the sampling settings.")
|
|
|
|
.arg(name).arg(formatSamplingValue(lower, 6)).arg(formatSamplingValue(upper, 6)).arg(unit)
|
|
|
|
.arg(name).arg(formatSamplingValue(lower, 6)).arg(formatSamplingValue(upper, 6)).arg(unit)
|
|
|
|
: tr("%1: the physical range is unavailable.").arg(name));
|
|
|
|
: tr("%1: the physical range is unavailable.").arg(name));
|
|
|
|
detailStack->setCurrentIndex(idx + 1);
|
|
|
|
detailStack->setCurrentIndex(idx + 1);
|
|
|
|
@ -2087,6 +2119,28 @@ 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);
|
|
|
|
|