|
|
|
|
@ -24,6 +24,7 @@
|
|
|
|
|
#include <QDialog>
|
|
|
|
|
#include <QDialogButtonBox>
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QDesktopWidget>
|
|
|
|
|
#include <QTableWidget>
|
|
|
|
|
#include <QTableView>
|
|
|
|
|
#include <QAbstractTableModel>
|
|
|
|
|
@ -66,6 +67,8 @@
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
const char* distributionFields[] = {"mean", "stdDev", "peak", "minimum", "maximum"};
|
|
|
|
|
|
|
|
|
|
QString formatSamplingValue(double value, int digits)
|
|
|
|
|
{
|
|
|
|
|
// 仅对显示值取有效数字,较大或较小的数自动使用科学计数法。
|
|
|
|
|
@ -292,6 +295,119 @@ private:
|
|
|
|
|
QMap<int, bool> m_pendingVisibility;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SensitivityDistributionPlot : public QWidget
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
explicit SensitivityDistributionPlot(QWidget* parent) : QWidget(parent), m_valid(false)
|
|
|
|
|
{
|
|
|
|
|
// 分布图复用参数分位数计算,避免绘图与实际采样使用两套分布定义。
|
|
|
|
|
setMinimumSize(340, 300);
|
|
|
|
|
}
|
|
|
|
|
void setVariable(const nmDataSensitive::VariableSampling& variable, bool valid)
|
|
|
|
|
{
|
|
|
|
|
m_variable = variable;
|
|
|
|
|
m_valid = valid;
|
|
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
protected:
|
|
|
|
|
void paintEvent(QPaintEvent*) override
|
|
|
|
|
{
|
|
|
|
|
// 蓝线为累积概率,红色虚线为按峰值归一化的概率密度,与 Saphir 的显示一致。
|
|
|
|
|
QPainter painter(this);
|
|
|
|
|
painter.fillRect(rect(), palette().color(QPalette::Window));
|
|
|
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
|
|
|
QVector<double> x(201), cumulativeValues(201);
|
|
|
|
|
const bool bounded = m_variable.m_distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL;
|
|
|
|
|
bool valid = m_valid;
|
|
|
|
|
for(int i = 0; valid && i < x.size(); ++i) {
|
|
|
|
|
// 两端加密以平滑显示尾部,有界分布的横轴保留精确上下限。
|
|
|
|
|
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]);
|
|
|
|
|
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),
|
|
|
|
|
m_variable.m_modelValue.getUnit(), x[i]);
|
|
|
|
|
}
|
|
|
|
|
if(!valid || x.last() <= x.first()) {
|
|
|
|
|
painter.drawText(rect(), Qt::AlignCenter, nmWxSensitive::tr("Invalid distribution parameters"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 坐标外侧沿用窗口底色,绘图区留白,标题放在纵轴左侧。
|
|
|
|
|
const QRectF plot(76, 12, width() - 96, height() - 66);
|
|
|
|
|
painter.fillRect(plot, Qt::white);
|
|
|
|
|
const QRectF dataArea = plot.adjusted(12, 16, -12, -16);
|
|
|
|
|
// 横轴采用 1、2、5 的整齐步长,按分布范围自适应,不限制实际采样值。
|
|
|
|
|
const double roughStep = (x.last() - x.first()) / 5.0;
|
|
|
|
|
const double scale = std::pow(10.0, std::floor(std::log10(roughStep)));
|
|
|
|
|
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 axisMin = std::floor(x.first() / step) * step;
|
|
|
|
|
const double axisMax = std::ceil(x.last() / step) * step;
|
|
|
|
|
QVector<double> density(201);
|
|
|
|
|
double peak = 0.0;
|
|
|
|
|
for(int i = 0; i < x.size(); ++i) {
|
|
|
|
|
const int a = qMax(0, i - 1), b = qMin(200, i + 1);
|
|
|
|
|
density[i] = x[b] > x[a] ? (cumulativeValues[b] - cumulativeValues[a]) / (x[b] - x[a]) : 0.0;
|
|
|
|
|
if(m_variable.m_distribution == nmDataSensitive::VariableSampling::TRIANGULAR &&
|
|
|
|
|
((i == 0 && m_variable.m_peak > m_variable.m_distributionMin) ||
|
|
|
|
|
(i == 200 && m_variable.m_peak < m_variable.m_distributionMax))) density[i] = 0.0;
|
|
|
|
|
peak = qMax(peak, density[i]);
|
|
|
|
|
}
|
|
|
|
|
// 蓝色点状主网格配合黑色主、次刻度,概率轴按 0.1 标注。
|
|
|
|
|
const int intervals = qRound((axisMax - axisMin) / step);
|
|
|
|
|
for(int i = 0; i <= intervals * 5; ++i) {
|
|
|
|
|
const double value = axisMin + i * step / 5.0;
|
|
|
|
|
const double px = dataArea.left() + (value - axisMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
|
if(i % 5 == 0) {
|
|
|
|
|
painter.setPen(QPen(QColor(100, 120, 255), 1, Qt::DotLine));
|
|
|
|
|
painter.drawLine(QPointF(px, plot.top()), QPointF(px, plot.bottom()));
|
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
|
painter.drawText(QRectF(px - 36, plot.bottom() + 8, 72, 20), Qt::AlignCenter,
|
|
|
|
|
formatSamplingValue(value, 4));
|
|
|
|
|
}
|
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
|
painter.drawLine(QPointF(px, plot.bottom()), QPointF(px, plot.bottom() + (i % 5 == 0 ? 7 : 4)));
|
|
|
|
|
}
|
|
|
|
|
for(int i = 0; i <= 50; ++i) {
|
|
|
|
|
const double ratio = i / 50.0;
|
|
|
|
|
const double py = dataArea.bottom() - ratio * dataArea.height();
|
|
|
|
|
if(i % 10 == 0) {
|
|
|
|
|
painter.setPen(QPen(QColor(100, 120, 255), 1, Qt::DotLine));
|
|
|
|
|
painter.drawLine(QPointF(plot.left(), py), QPointF(plot.right(), py));
|
|
|
|
|
}
|
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
|
painter.drawLine(QPointF(plot.left(), py), QPointF(plot.left() - (i % 5 == 0 ? 7 : 4), py));
|
|
|
|
|
if(i % 5 == 0)
|
|
|
|
|
painter.drawText(QRectF(28, py - 10, plot.left() - 40, 20), Qt::AlignRight | Qt::AlignVCenter,
|
|
|
|
|
QString::number(ratio));
|
|
|
|
|
}
|
|
|
|
|
QPolygonF cumulative, probability;
|
|
|
|
|
for(int i = 0; i < x.size(); ++i) {
|
|
|
|
|
const double px = dataArea.left() + (x[i] - axisMin) / (axisMax - axisMin) * dataArea.width();
|
|
|
|
|
cumulative << QPointF(px, dataArea.bottom() - cumulativeValues[i] * dataArea.height());
|
|
|
|
|
probability << QPointF(px, dataArea.bottom() - (peak > 0.0 ? density[i] / peak : 0.0) * dataArea.height());
|
|
|
|
|
}
|
|
|
|
|
painter.setPen(QPen(Qt::blue, 1.5));
|
|
|
|
|
painter.drawPolyline(cumulative);
|
|
|
|
|
painter.setPen(QPen(Qt::red, 1, Qt::DashLine));
|
|
|
|
|
painter.drawPolyline(probability);
|
|
|
|
|
painter.setPen(Qt::black);
|
|
|
|
|
painter.drawRect(plot);
|
|
|
|
|
painter.save();
|
|
|
|
|
painter.translate(17, plot.center().y());
|
|
|
|
|
painter.rotate(-90);
|
|
|
|
|
painter.drawText(QRectF(-plot.height() / 2, -10, plot.height(), 20), Qt::AlignCenter,
|
|
|
|
|
nmWxSensitive::tr("Probability distribution function"));
|
|
|
|
|
painter.restore();
|
|
|
|
|
painter.drawText(QRectF(plot.left(), height() - 22, plot.width(), 20), Qt::AlignCenter,
|
|
|
|
|
m_variable.m_varName + " [" + m_variable.m_modelValue.getUnit() + "]");
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
nmDataSensitive::VariableSampling m_variable;
|
|
|
|
|
bool m_valid;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ========= 右对齐委托:保证显示和编辑时都是右对齐 =========
|
|
|
|
|
class RightAlignDelegate : public QStyledItemDelegate
|
|
|
|
|
{
|
|
|
|
|
@ -768,6 +884,21 @@ void nmWxSensitive::refreshModelValues()
|
|
|
|
|
variable.getMinValue().setValue(minimum);
|
|
|
|
|
variable.getMaxValue().setValue(maximum);
|
|
|
|
|
variable.setValues(values);
|
|
|
|
|
if(!variable.m_distributionInitialized || resetRanges) {
|
|
|
|
|
// Saphir 默认正态均值为模型值、标准差为其三分之一;Skin=0 时使用 5/3。
|
|
|
|
|
const double model = attribute->getValue().toDouble();
|
|
|
|
|
variable.m_mean = model;
|
|
|
|
|
variable.m_stdDev = model == 0.0 ? 5.0 / 3.0 : std::fabs(model) / 3.0;
|
|
|
|
|
variable.m_distributionMin = minimum;
|
|
|
|
|
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_distributionInitialized = true;
|
|
|
|
|
}
|
|
|
|
|
// Saphir 在重新打开对话框时同步无效 Log 范围的实际状态。
|
|
|
|
|
if (variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC &&
|
|
|
|
|
(minimum <= 0.0 || maximum <= 0.0)) variable.setLog(false);
|
|
|
|
|
@ -796,6 +927,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
|
|
|
|
|
// 加载过程中阻止联动刷新,先恢复设置,再一次生成各参数表格。
|
|
|
|
|
if (!refreshRangesOnly)
|
|
|
|
|
calculationTypeCombo->setCurrentIndex(int(m_sensitiveData.getCalculationType()));
|
|
|
|
|
m_monteCarloCount->setValue(m_sensitiveData.getMonteCarloCount());
|
|
|
|
|
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
|
|
|
|
|
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
|
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
|
@ -833,6 +965,14 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
|
|
|
|
|
setSamplingEditValue(m_modelSpins[idx], model);
|
|
|
|
|
setSamplingEditValue(m_minSpins[idx], minimum);
|
|
|
|
|
setSamplingEditValue(m_maxSpins[idx], maximum);
|
|
|
|
|
m_distributionCombos[idx]->setCurrentIndex(int(variable.m_distribution));
|
|
|
|
|
const double distributionValues[] = {variable.m_mean, variable.m_stdDev, variable.m_peak,
|
|
|
|
|
variable.m_distributionMin, variable.m_distributionMax};
|
|
|
|
|
for(int field = 0; field < 5; ++field) {
|
|
|
|
|
double display = distributionValues[field];
|
|
|
|
|
convertSamplingValue(name, display, samplingBaseUnit(name), unit, display);
|
|
|
|
|
setSamplingEditValue(m_distributionPanels[idx]->findChild<QLineEdit*>(distributionFields[field]), display);
|
|
|
|
|
}
|
|
|
|
|
double displayMin = variable.getModelValue().getMinValue().toDouble();
|
|
|
|
|
double displayMax = variable.getModelValue().getMaxValue().toDouble();
|
|
|
|
|
convertSamplingValue(name, displayMin, samplingBaseUnit(name), unit, displayMin);
|
|
|
|
|
@ -888,11 +1028,16 @@ void nmWxSensitive::updateModelCount()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool overflow = false;
|
|
|
|
|
m_sensitiveData.setMonteCarloCount(m_monteCarloCount->value());
|
|
|
|
|
const quint64 count = m_sensitiveData.calculateModelCount(&overflow);
|
|
|
|
|
m_sensitiveData.setTotalModelCount(count);
|
|
|
|
|
modelCountLabel->setText(overflow ? tr("Too many models") : QString::number(count));
|
|
|
|
|
previewButton->setEnabled(!overflow && count > 0);
|
|
|
|
|
generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning);
|
|
|
|
|
const bool monteCarlo = m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO;
|
|
|
|
|
modelCountLabel->setVisible(!monteCarlo);
|
|
|
|
|
m_monteCarloCount->setVisible(monteCarlo);
|
|
|
|
|
generateButton->setEnabled(!monteCarlo && m_pDataManager && !overflow && count > 0 && !m_calculationRunning);
|
|
|
|
|
generateButton->setToolTip(monteCarlo ? tr("Monte-Carlo sampling preview is available; calculation is not yet available.") : QString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmWxSensitive::saveDataFromUI()
|
|
|
|
|
@ -906,6 +1051,16 @@ bool nmWxSensitive::saveDataFromUI()
|
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
|
const int idx = findVariableIndex(name);
|
|
|
|
|
if (idx < 0 || !isSupportedSamplingVariable(name)) continue;
|
|
|
|
|
if(candidate.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
|
|
double median = 0.0;
|
|
|
|
|
const bool valid = readDistributionPage(idx, variable) && variable.distributionQuantile(0.5, median);
|
|
|
|
|
if(!valid && variable.getEnabled()) {
|
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"), tr("%1: invalid distribution parameters.").arg(name));
|
|
|
|
|
detailStack->setCurrentIndex(idx + 1);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
variable.setMode(static_cast<nmDataSensitive::VariableSampling::Mode>(m_modeCombos[idx]->currentIndex()));
|
|
|
|
|
variable.setLog(m_logChecks[idx]->isChecked());
|
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
@ -965,6 +1120,9 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
{
|
|
|
|
|
setWindowTitle(tr("Sensitivity"));
|
|
|
|
|
setMinimumSize(900, 550);
|
|
|
|
|
// 默认放大窗口,同时为屏幕边缘和窗口标题栏留出空间。
|
|
|
|
|
const QSize available = QApplication::desktop()->availableGeometry(this).size() - QSize(60, 80);
|
|
|
|
|
resize(QSize(1250, 760).boundedTo(available));
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
|
|
|
|
|
@ -974,7 +1132,7 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
calculationTypeCombo = new QComboBox(this);
|
|
|
|
|
calculationTypeCombo->addItem(tr("Deterministic"));
|
|
|
|
|
calculationTypeCombo->addItem(tr("Deterministic multivariate"));
|
|
|
|
|
//calculationTypeCombo->addItem(tr("Monte-Carlo"));
|
|
|
|
|
calculationTypeCombo->addItem(tr("Monte-Carlo"));
|
|
|
|
|
//calculationTypeCombo->addItem(tr("Monte-Carlo + Improve"));
|
|
|
|
|
|
|
|
|
|
calcTypeLayout->addWidget(calcTypeLabel);
|
|
|
|
|
@ -1061,6 +1219,7 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
// 左边宽度基本固定,右边自适应
|
|
|
|
|
centerSplitter->setStretchFactor(0, 0);
|
|
|
|
|
centerSplitter->setStretchFactor(1, 1);
|
|
|
|
|
centerSplitter->setSizes(QList<int>() << 260 << 960);
|
|
|
|
|
|
|
|
|
|
mainLayout->addWidget(centerSplitter, 1);
|
|
|
|
|
|
|
|
|
|
@ -1069,9 +1228,16 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
|
|
|
|
|
QLabel *totalLabel = new QLabel(tr("Total number of models:"), this);
|
|
|
|
|
modelCountLabel = new QLabel(tr("0"), this);
|
|
|
|
|
m_monteCarloCount = new QSpinBox(this);
|
|
|
|
|
m_monteCarloCount->setObjectName("sensitivityMonteCarloCount");
|
|
|
|
|
m_monteCarloCount->setRange(1, 100000);
|
|
|
|
|
m_monteCarloCount->setValue(m_sensitiveData.getMonteCarloCount());
|
|
|
|
|
m_monteCarloCount->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
|
|
m_monteCarloCount->setVisible(false);
|
|
|
|
|
|
|
|
|
|
bottomLayout->addWidget(totalLabel);
|
|
|
|
|
bottomLayout->addWidget(modelCountLabel);
|
|
|
|
|
bottomLayout->addWidget(m_monteCarloCount);
|
|
|
|
|
previewButton = new QPushButton(this);
|
|
|
|
|
previewButton->setIcon(QIcon(QCoreApplication::applicationDirPath().section('/', 0, -2)
|
|
|
|
|
+ "/Res/Icon/Eyes.png"));
|
|
|
|
|
@ -1094,6 +1260,7 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::createConnections()
|
|
|
|
|
{
|
|
|
|
|
connect(m_monteCarloCount, SIGNAL(valueChanged(int)), this, SLOT(onVariableParamChanged()));
|
|
|
|
|
connect(generateButton, SIGNAL(clicked()),
|
|
|
|
|
this, SLOT(onGenerateClicked()));
|
|
|
|
|
connect(cancelButton, SIGNAL(clicked()),
|
|
|
|
|
@ -1267,6 +1434,12 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
QHBoxLayout *modeRowLayout = new QHBoxLayout(modeRowWidget);
|
|
|
|
|
modeRowLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
modeRowLayout->addWidget(modeCombo);
|
|
|
|
|
QComboBox* distributionCombo = new QComboBox(content);
|
|
|
|
|
distributionCombo->setObjectName("sensitivityDistribution_" + varName);
|
|
|
|
|
distributionCombo->addItems(QStringList() << tr("Normal") << tr("Lognormal")
|
|
|
|
|
<< tr("Truncated normal") << tr("Truncated lognormal") << tr("Uniform") << tr("Triangular"));
|
|
|
|
|
distributionCombo->setFixedWidth(180);
|
|
|
|
|
modeRowLayout->addWidget(distributionCombo);
|
|
|
|
|
modeRowLayout->addWidget(logCheck);
|
|
|
|
|
modeRowLayout->addStretch();
|
|
|
|
|
|
|
|
|
|
@ -1285,9 +1458,9 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
paramLayout->addWidget(numSpin, row, 1);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// 空行(Number 和 Model value 之间)
|
|
|
|
|
// 模式、数量与下方采样参数之间留出约三行空白。
|
|
|
|
|
int gapRow = row;
|
|
|
|
|
paramLayout->setRowMinimumHeight(gapRow, 12);
|
|
|
|
|
paramLayout->setRowMinimumHeight(gapRow, numSpin->sizeHint().height() * 3);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// Model value + 单位
|
|
|
|
|
@ -1310,6 +1483,34 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
paramLayout->addWidget(modelRowWidget, row, 1);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// Monte-Carlo 使用独立分布字段,切回确定性方法时保留原范围与手动表格。
|
|
|
|
|
QWidget* distributionPanel = new QWidget(content);
|
|
|
|
|
QGridLayout* distributionLayout = new QGridLayout(distributionPanel);
|
|
|
|
|
distributionLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
distributionLayout->setHorizontalSpacing(6);
|
|
|
|
|
distributionLayout->setVerticalSpacing(0);
|
|
|
|
|
distributionLayout->setColumnMinimumWidth(0, modelLabel->sizeHint().width());
|
|
|
|
|
const int fieldHeight = modelSpin->sizeHint().height();
|
|
|
|
|
// Mean 与三角分布的 Mode 共用一行,其余字段保持固定位置。
|
|
|
|
|
distributionPanel->setFixedHeight(fieldHeight * 4 + 6 * 3);
|
|
|
|
|
const QStringList distributionLabels = QStringList() << tr("Mean:") << tr("Std. dev.:")
|
|
|
|
|
<< tr("Mode:") << tr("Minimum:") << tr("Maximum:");
|
|
|
|
|
for(int field = 0; field < 5; ++field) {
|
|
|
|
|
QLineEdit* edit = new QLineEdit(distributionPanel);
|
|
|
|
|
edit->setObjectName(distributionFields[field]);
|
|
|
|
|
edit->setFixedSize(qMax(150, modelSpin->sizeHint().width()), fieldHeight);
|
|
|
|
|
QLabel* label = new QLabel(distributionLabels[field], distributionPanel);
|
|
|
|
|
label->setObjectName(QString(distributionFields[field]) + "Label");
|
|
|
|
|
label->setFixedHeight(fieldHeight);
|
|
|
|
|
const int fieldRow = field == 2 ? 0 : field > 2 ? field - 1 : field;
|
|
|
|
|
distributionLayout->setRowMinimumHeight(fieldRow, fieldHeight + (fieldRow < 3 ? 6 : 0));
|
|
|
|
|
distributionLayout->addWidget(label, fieldRow, 0, Qt::AlignTop);
|
|
|
|
|
distributionLayout->addWidget(edit, fieldRow, 1, Qt::AlignTop);
|
|
|
|
|
connect(edit, SIGNAL(textChanged(QString)), this, SLOT(onVariableParamChanged()));
|
|
|
|
|
}
|
|
|
|
|
distributionLayout->setColumnStretch(2, 1);
|
|
|
|
|
paramLayout->addWidget(distributionPanel, row++, 0, 1, 2);
|
|
|
|
|
|
|
|
|
|
// Minimum
|
|
|
|
|
QLabel *minLabel = new QLabel(tr("Minimum:"), content);
|
|
|
|
|
QLineEdit *minSpin = new QLineEdit(content);
|
|
|
|
|
@ -1359,12 +1560,11 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
valuesTable->setHorizontalHeaderLabels(headers);
|
|
|
|
|
|
|
|
|
|
valuesTable->verticalHeader()->setVisible(false);
|
|
|
|
|
valuesTable->setColumnWidth(0, 15);
|
|
|
|
|
valuesTable->setColumnWidth(1, 130);
|
|
|
|
|
|
|
|
|
|
QHeaderView *hh = valuesTable->horizontalHeader();
|
|
|
|
|
hh->setResizeMode(QHeaderView::Fixed);
|
|
|
|
|
hh->setHighlightSections(false);
|
|
|
|
|
// 箭头列固定,数值列在下方设置为填满加宽后的表格。
|
|
|
|
|
valuesTable->setColumnWidth(0, 15);
|
|
|
|
|
valuesTable->setStyleSheet(
|
|
|
|
|
"QHeaderView::section {"
|
|
|
|
|
" border-top: 0px;"
|
|
|
|
|
@ -1380,7 +1580,7 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
"}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
valuesTable->horizontalHeader()->setStretchLastSection(false);
|
|
|
|
|
valuesTable->horizontalHeader()->setStretchLastSection(true);
|
|
|
|
|
valuesTable->setEditTriggers(QAbstractItemView::DoubleClicked);
|
|
|
|
|
valuesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
|
|
|
valuesTable->setSelectionMode(QAbstractItemView::NoSelection);
|
|
|
|
|
@ -1390,10 +1590,8 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
|
|
|
|
|
valuesTable->setItemDelegateForColumn(1, new RightAlignDelegate(valuesTable));
|
|
|
|
|
|
|
|
|
|
int tableWidth = valuesTable->frameWidth() * 2
|
|
|
|
|
+ valuesTable->verticalHeader()->sizeHint().width()
|
|
|
|
|
+ valuesTable->columnWidth(0)
|
|
|
|
|
+ valuesTable->columnWidth(1);
|
|
|
|
|
// 初始空表的列宽尚未稳定,直接固定容器宽度,不从未显示的表头反算。
|
|
|
|
|
const int tableWidth = 190;
|
|
|
|
|
valuesTable->setFixedWidth(tableWidth);
|
|
|
|
|
|
|
|
|
|
QWidget *tableContainer = new QWidget(content);
|
|
|
|
|
@ -1406,6 +1604,8 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
contentLayout->addWidget(paramWidget, 1);
|
|
|
|
|
contentLayout->addStretch();
|
|
|
|
|
contentLayout->addWidget(tableContainer, 0, Qt::AlignRight);
|
|
|
|
|
SensitivityDistributionPlot* distributionPlot = new SensitivityDistributionPlot(content);
|
|
|
|
|
contentLayout->addWidget(distributionPlot, 2);
|
|
|
|
|
|
|
|
|
|
panelLayout->addWidget(content);
|
|
|
|
|
pageLayout->addWidget(samplingPanel);
|
|
|
|
|
@ -1420,6 +1620,13 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
m_maxSpins.append(maxSpin);
|
|
|
|
|
m_valueTables.append(valuesTable);
|
|
|
|
|
m_unitLabels.append(unitLabel);
|
|
|
|
|
m_numLabels.append(numLabel);
|
|
|
|
|
m_minLabels.append(minLabel);
|
|
|
|
|
m_maxLabels.append(maxLabel);
|
|
|
|
|
m_distributionCombos.append(distributionCombo);
|
|
|
|
|
m_distributionPanels.append(distributionPanel);
|
|
|
|
|
m_distributionPlots.append(distributionPlot);
|
|
|
|
|
connect(distributionCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onVariableParamChanged()));
|
|
|
|
|
|
|
|
|
|
connect(numSpin, SIGNAL(valueChanged(int)),
|
|
|
|
|
this, SLOT(onVariableParamChanged()));
|
|
|
|
|
@ -1445,6 +1652,23 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
void nmWxSensitive::updateValuesForPage(int idx)
|
|
|
|
|
{
|
|
|
|
|
if (idx < 0 || idx >= m_numSpins.size()) return;
|
|
|
|
|
const bool monteCarlo = calculationTypeCombo->currentIndex() == nmDataSensitive::CALC_MONTE_CARLO;
|
|
|
|
|
m_modeCombos[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_logChecks[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_numLabels[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_numSpins[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_minLabels[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_maxLabels[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_minSpins[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_maxSpins[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_valueTables[idx]->parentWidget()->setVisible(!monteCarlo);
|
|
|
|
|
m_distributionCombos[idx]->setVisible(monteCarlo);
|
|
|
|
|
m_distributionPanels[idx]->setVisible(monteCarlo);
|
|
|
|
|
m_distributionPlots[idx]->setVisible(monteCarlo);
|
|
|
|
|
// Monte-Carlo 参数区只占所需宽度,窗口增宽时优先扩展分布图。
|
|
|
|
|
QHBoxLayout* contentLayout = static_cast<QHBoxLayout*>(m_distributionPlots[idx]->parentWidget()->layout());
|
|
|
|
|
contentLayout->setStretch(0, monteCarlo ? 0 : 1);
|
|
|
|
|
if(monteCarlo) { updateDistributionPage(idx); return; }
|
|
|
|
|
// 自动模式由范围生成只读表格;手动模式保留每行值,不因选中行而重算。
|
|
|
|
|
QTableWidget* table = m_valueTables[idx];
|
|
|
|
|
const bool automatic = m_modeCombos[idx]->currentIndex() == 0;
|
|
|
|
|
@ -1498,6 +1722,49 @@ void nmWxSensitive::updateValuesForPage(int idx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmWxSensitive::readDistributionPage(int idx, nmDataSensitive::VariableSampling& variable) const
|
|
|
|
|
{
|
|
|
|
|
// 字段转换沿用现有参数单位,仅校验当前分布实际使用的字段。
|
|
|
|
|
variable.m_distribution = static_cast<nmDataSensitive::VariableSampling::Distribution>(
|
|
|
|
|
m_distributionCombos[idx]->currentIndex());
|
|
|
|
|
variable.m_modelValue.setUnit(m_unitLabels[idx]->text());
|
|
|
|
|
double* fields[] = {&variable.m_mean, &variable.m_stdDev, &variable.m_peak,
|
|
|
|
|
&variable.m_distributionMin, &variable.m_distributionMax};
|
|
|
|
|
const int distribution = variable.m_distribution;
|
|
|
|
|
for(int field = 0; field < 5; ++field) {
|
|
|
|
|
const bool used = field < 2 ? distribution < nmDataSensitive::VariableSampling::UNIFORM
|
|
|
|
|
: field == 2 ? distribution == nmDataSensitive::VariableSampling::TRIANGULAR
|
|
|
|
|
: distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL;
|
|
|
|
|
if(!used) continue;
|
|
|
|
|
bool ok = false;
|
|
|
|
|
double value = samplingEditValue(m_distributionPanels[idx]->findChild<QLineEdit*>(distributionFields[field]), &ok);
|
|
|
|
|
if(!ok || !qIsFinite(value) || !convertSamplingValue(variable.m_varName, value,
|
|
|
|
|
m_unitLabels[idx]->text(), samplingBaseUnit(variable.m_varName), *fields[field])) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::updateDistributionPage(int idx)
|
|
|
|
|
{
|
|
|
|
|
// 只隐藏所选分布不用的字段,保留各行位置;编辑时更新曲线,不提前抽样。
|
|
|
|
|
const int distribution = m_distributionCombos[idx]->currentIndex();
|
|
|
|
|
for(int field = 0; field < 5; ++field) {
|
|
|
|
|
QWidget* edit = m_distributionPanels[idx]->findChild<QLineEdit*>(distributionFields[field]);
|
|
|
|
|
const bool used = field < 2 ? distribution < nmDataSensitive::VariableSampling::UNIFORM
|
|
|
|
|
: field == 2 ? distribution == nmDataSensitive::VariableSampling::TRIANGULAR
|
|
|
|
|
: distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL;
|
|
|
|
|
edit->setVisible(used);
|
|
|
|
|
m_distributionPanels[idx]->findChild<QLabel*>(QString(distributionFields[field]) + "Label")->setVisible(used);
|
|
|
|
|
}
|
|
|
|
|
for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
|
|
|
|
|
nmDataSensitive::VariableSampling variable = m_sensitiveData.getVariable(i);
|
|
|
|
|
if(findVariableIndex(variable.m_varName) != idx) continue;
|
|
|
|
|
const bool valid = readDistributionPage(idx, variable);
|
|
|
|
|
m_distributionPlots[idx]->setVariable(variable, valid);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::onSamplingEditingFinished()
|
|
|
|
|
{
|
|
|
|
|
// 输入结束后截断越界范围,再按六位有效数字显示,计算仍使用原始精度。
|
|
|
|
|
@ -1532,7 +1799,8 @@ void nmWxSensitive::onVariableParamChanged()
|
|
|
|
|
for (int i = 0; i < m_numSpins.size(); ++i) {
|
|
|
|
|
if (sender() == m_numSpins[i] || sender() == m_minSpins[i] ||
|
|
|
|
|
sender() == m_maxSpins[i] || sender() == m_modeCombos[i] ||
|
|
|
|
|
sender() == m_logChecks[i]) {
|
|
|
|
|
sender() == m_logChecks[i] || sender() == m_distributionCombos[i] ||
|
|
|
|
|
sender()->parent() == m_distributionPanels[i]) {
|
|
|
|
|
m_selectedValueIndex = qMin(m_selectedValueIndex, m_numSpins[i]->value() - 1);
|
|
|
|
|
updateValuesForPage(i);
|
|
|
|
|
break;
|
|
|
|
|
@ -1584,6 +1852,8 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column)
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::onGenerateClicked()
|
|
|
|
|
{
|
|
|
|
|
// 本阶段只开放 Monte-Carlo 参数采样与预览,尚不启动数值计算。
|
|
|
|
|
if(calculationTypeCombo->currentIndex() == nmDataSensitive::CALC_MONTE_CARLO) return;
|
|
|
|
|
if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return;
|
|
|
|
|
QString error;
|
|
|
|
|
QScopedPointer<nmCalculationDllPebiSolverTask> task;
|
|
|
|
|
@ -1787,7 +2057,9 @@ void nmWxSensitive::onHideAllResults()
|
|
|
|
|
void nmWxSensitive::onCalculationTypeChanged(int index)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(index);
|
|
|
|
|
// 两种确定性方法共用参数设置,仅切换总次数及组合规则。
|
|
|
|
|
// 复用现有参数页,切换方法只改变可见控件,不覆盖另一种方法的设置。
|
|
|
|
|
if(m_loading) return;
|
|
|
|
|
for(int i = 0; i < m_numSpins.size(); ++i) updateValuesForPage(i);
|
|
|
|
|
updateModelCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1809,6 +2081,13 @@ void nmWxSensitive::onTreeItemClicked(QTreeWidgetItem* item, int column)
|
|
|
|
|
void nmWxSensitive::onPreviewClicked()
|
|
|
|
|
{
|
|
|
|
|
if (!saveDataFromUI()) return;
|
|
|
|
|
if(m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
|
|
QString error;
|
|
|
|
|
if(!m_sensitiveData.resampleMonteCarlo(&error)) {
|
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"), error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Saphir 风格的连续运行列表,组合数再大也不显示分页控件。
|
|
|
|
|
QDialog dialog(this);
|
|
|
|
|
dialog.setWindowTitle(tr("Sensitivity Runs"));
|
|
|
|
|
|