|
|
|
|
@ -2,6 +2,9 @@
|
|
|
|
|
|
|
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
#include "nmWxAutomaticFitting.h"
|
|
|
|
|
#include "nmParameterField.h"
|
|
|
|
|
#include "ZxBaseUtil.h"
|
|
|
|
|
#include <QGroupBox>
|
|
|
|
|
#include "nmDataSensitive.h"
|
|
|
|
|
#include "nmDataReservoir.h"
|
|
|
|
|
#include "nmDataWellBase.h"
|
|
|
|
|
@ -122,21 +125,33 @@ QString samplingBaseUnit(const QString& name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool convertSamplingValue(const QString& name, double value,
|
|
|
|
|
QString from, QString to, double& converted)
|
|
|
|
|
{
|
|
|
|
|
// 优先复用项目单位组;旧配置的 psi 按绝对压力单位迁移。
|
|
|
|
|
if (from == "psi") from = "psia";
|
|
|
|
|
if (to == "psi") to = "psia";
|
|
|
|
|
QString from, QString to, double& converted, bool difference = false)
|
|
|
|
|
{
|
|
|
|
|
// 标准差是差值,单位换算只应用比例,不带入表压和绝压之间的偏移。
|
|
|
|
|
if (difference) {
|
|
|
|
|
double zero = 0.0;
|
|
|
|
|
if (!convertSamplingValue(name, 0.0, from, to, zero) ||
|
|
|
|
|
!convertSamplingValue(name, value, from, to, converted)) return false;
|
|
|
|
|
converted -= zero;
|
|
|
|
|
return qIsFinite(converted);
|
|
|
|
|
}
|
|
|
|
|
// 优先使用框架单位组中的原始名称,兼容下拉框内的全部单位。
|
|
|
|
|
if (from == "dimensionless") from.clear();
|
|
|
|
|
if (to == "dimensionless") to.clear();
|
|
|
|
|
if (from == to || (from.isEmpty() && to.isEmpty())) {
|
|
|
|
|
converted = value;
|
|
|
|
|
return qIsFinite(converted);
|
|
|
|
|
}
|
|
|
|
|
// 与井参数编辑共用名称解析,使 m^3/MPa 等旧写法也能参与框架换算。
|
|
|
|
|
from = nmParameterField::resolveUnitSpelling(from);
|
|
|
|
|
to = nmParameterField::resolveUnitSpelling(to);
|
|
|
|
|
iUnitGroup* group = iUnitHelper::getUnitGroupByUnit(from);
|
|
|
|
|
int digit = 0;
|
|
|
|
|
if (group && group->indexOf(to) >= 0 &&
|
|
|
|
|
group->convert(from, value, to, converted, digit)) return qIsFinite(converted);
|
|
|
|
|
// 旧配置未被框架识别时,沿用业务层的绝对压力单位换算。
|
|
|
|
|
if (from == "psi") from = "psia";
|
|
|
|
|
if (to == "psi") to = "psia";
|
|
|
|
|
if (name == "h") converted = nmDataAttribute::convertLengthUnit(value, from, to);
|
|
|
|
|
else if (name == "Pi") converted = nmDataAttribute::convertPressureUnit(value, from, to);
|
|
|
|
|
else if (name == "k") converted = nmDataAttribute::convertPermeabilityUnit(value, from, to);
|
|
|
|
|
@ -984,7 +999,7 @@ protected:
|
|
|
|
|
{
|
|
|
|
|
// 蓝线为累积概率,红色虚线为按峰值归一化的概率密度,与 Saphir 的显示一致。
|
|
|
|
|
QPainter painter(this);
|
|
|
|
|
painter.fillRect(rect(), palette().color(QPalette::Window));
|
|
|
|
|
painter.fillRect(rect(), Qt::white);
|
|
|
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
|
|
|
QVector<double> x(201), cumulativeValues(201);
|
|
|
|
|
const bool bounded = m_variable.m_distribution >= nmDataSensitive::VariableSampling::TRUNCATED_NORMAL;
|
|
|
|
|
@ -1005,7 +1020,7 @@ protected:
|
|
|
|
|
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);
|
|
|
|
|
@ -1526,7 +1541,9 @@ void nmWxSensitive::refreshModelValues()
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const QString baseUnit = samplingBaseUnit(name);
|
|
|
|
|
QString displayUnit = attribute->getUnit();
|
|
|
|
|
// 已保存的采样显示单位独立于模型单位,重开及读取项目时继续沿用。
|
|
|
|
|
QString displayUnit = !fresh && m_sensitiveData.usesBaseUnits()
|
|
|
|
|
? variable.getModelValue().getUnit() : attribute->getUnit();
|
|
|
|
|
if (displayUnit == "dimensionless") displayUnit.clear();
|
|
|
|
|
double converted = 0.0;
|
|
|
|
|
if (!convertSamplingValue(name, attribute->getValue().toDouble(), baseUnit, displayUnit, converted))
|
|
|
|
|
@ -1630,7 +1647,7 @@ void nmWxSensitive::reloadInputData()
|
|
|
|
|
while(detailStack->count() > 1) delete detailStack->widget(1);
|
|
|
|
|
m_modeCombos.clear(); m_logChecks.clear(); m_numSpins.clear();
|
|
|
|
|
m_modelSpins.clear(); m_minSpins.clear(); m_maxSpins.clear();
|
|
|
|
|
m_valueTables.clear(); m_unitLabels.clear();
|
|
|
|
|
m_valueTables.clear(); m_unitCombos.clear();
|
|
|
|
|
m_numLabels.clear(); m_minLabels.clear(); m_maxLabels.clear();
|
|
|
|
|
m_distributionCombos.clear(); m_distributionPanels.clear(); m_distributionPlots.clear();
|
|
|
|
|
populateTreeWidget();
|
|
|
|
|
@ -1672,9 +1689,17 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
|
|
|
|
|
m_numSpins[idx]->setValue(qMax(1, variable.getNumber()));
|
|
|
|
|
}
|
|
|
|
|
// 已打开窗口沿用原显示单位,避免手动表格的数值被换成另一种单位解释。
|
|
|
|
|
const QString unit = refreshRangesOnly ? m_unitLabels[idx]->text() : variable.getModelValue().getUnit();
|
|
|
|
|
const QString unit = nmParameterField::resolveUnitSpelling(
|
|
|
|
|
refreshRangesOnly ? m_unitCombos[idx]->currentText() : variable.getModelValue().getUnit());
|
|
|
|
|
variable.getModelValue().setUnit(unit);
|
|
|
|
|
m_unitLabels[idx]->setText(unit);
|
|
|
|
|
QComboBox* unitCombo = m_unitCombos[idx];
|
|
|
|
|
unitCombo->clear();
|
|
|
|
|
iUnitGroup* unitGroup = iUnitHelper::getUnitGroupByUnit(
|
|
|
|
|
nmParameterField::resolveUnitSpelling(samplingBaseUnit(name)));
|
|
|
|
|
if (unitGroup) unitCombo->addItems(unitGroup->getAllUnitNames());
|
|
|
|
|
if (unitCombo->findText(unit) < 0) unitCombo->addItem(unit);
|
|
|
|
|
unitCombo->setCurrentIndex(unitCombo->findText(unit));
|
|
|
|
|
unitCombo->setVisible(!unit.isEmpty());
|
|
|
|
|
double model = variable.getModelValue().getValue().toDouble();
|
|
|
|
|
double minimum = variable.getMinValue().getValue().toDouble();
|
|
|
|
|
double maximum = variable.getMaxValue().getValue().toDouble();
|
|
|
|
|
@ -1689,7 +1714,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
|
|
|
|
|
variable.m_distributionMin, variable.m_distributionMax};
|
|
|
|
|
for(int field = 0; field < 5; ++field) {
|
|
|
|
|
double display = distributionValues[field];
|
|
|
|
|
convertSamplingValue(name, display, samplingBaseUnit(name), unit, display);
|
|
|
|
|
convertSamplingValue(name, display, samplingBaseUnit(name), unit, display, field == 1);
|
|
|
|
|
setSamplingEditValue(m_distributionPanels[idx]->findChild<QLineEdit*>(distributionFields[field]), display);
|
|
|
|
|
}
|
|
|
|
|
double displayMin = variable.getModelValue().getMinValue().toDouble();
|
|
|
|
|
@ -1795,8 +1820,9 @@ bool nmWxSensitive::saveDataFromUI()
|
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
|
const int idx = findVariableIndex(variable);
|
|
|
|
|
if (idx < 0 || !isSupportedSamplingVariable(name)) continue;
|
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
const QString unit = m_unitCombos[idx]->currentText();
|
|
|
|
|
const QString baseUnit = samplingBaseUnit(name);
|
|
|
|
|
variable.getModelValue().setUnit(unit);
|
|
|
|
|
bool valid = true;
|
|
|
|
|
if(candidate.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
|
|
double median = 0.0;
|
|
|
|
|
@ -1870,15 +1896,21 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
{
|
|
|
|
|
setWindowTitle(tr("Sensitivity"));
|
|
|
|
|
setMinimumSize(900, 550);
|
|
|
|
|
// 默认放大窗口,同时为屏幕边缘和窗口标题栏留出空间。
|
|
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
|
// 适当加宽窗口,为整页单位下拉框和分布图保留空间。
|
|
|
|
|
const QSize available = QApplication::desktop()->availableGeometry(this).size() - QSize(60, 80);
|
|
|
|
|
resize(QSize(1250, 760).boundedTo(available));
|
|
|
|
|
resize(QSize(1120, 660).boundedTo(available));
|
|
|
|
|
|
|
|
|
|
// 沿用储层参数窗口的分组框、内容边距和底部按钮间距。
|
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
mainLayout->setContentsMargins(12, 10, 12, 0);
|
|
|
|
|
mainLayout->setSpacing(8);
|
|
|
|
|
|
|
|
|
|
// ===== 顶部:Calculation type =====
|
|
|
|
|
QHBoxLayout *calcTypeLayout = new QHBoxLayout();
|
|
|
|
|
QLabel *calcTypeLabel = new QLabel(tr("Calculation type:"), this);
|
|
|
|
|
QGroupBox* calcTypeGroup = new QGroupBox(tr("Calculation type:"), this);
|
|
|
|
|
QHBoxLayout *calcTypeLayout = new QHBoxLayout(calcTypeGroup);
|
|
|
|
|
calcTypeLayout->setContentsMargins(10, 10, 10, 10);
|
|
|
|
|
calcTypeLayout->setSpacing(8);
|
|
|
|
|
calculationTypeCombo = new QComboBox(this);
|
|
|
|
|
calculationTypeCombo->addItem(tr("Deterministic"), nmDataSensitive::CALC_DETERMINISTIC);
|
|
|
|
|
calculationTypeCombo->addItem(tr("Deterministic multivariate"), nmDataSensitive::CALC_DETERMINISTIC_MULTIVARIATE);
|
|
|
|
|
@ -1890,58 +1922,20 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
m_improveSettingsButton->setFixedHeight(calculationTypeCombo->sizeHint().height());
|
|
|
|
|
m_improveSettingsButton->hide();
|
|
|
|
|
|
|
|
|
|
calcTypeLayout->addWidget(calcTypeLabel);
|
|
|
|
|
calcTypeLayout->addWidget(calculationTypeCombo);
|
|
|
|
|
calcTypeLayout->addWidget(m_improveSettingsButton);
|
|
|
|
|
calcTypeLayout->addStretch();
|
|
|
|
|
mainLayout->addLayout(calcTypeLayout);
|
|
|
|
|
mainLayout->addWidget(calcTypeGroup);
|
|
|
|
|
|
|
|
|
|
// ===== 中间:左 Variables 面板 + 右 detailStack =====
|
|
|
|
|
QSplitter *centerSplitter = new QSplitter(Qt::Horizontal, this);
|
|
|
|
|
centerSplitter->setChildrenCollapsible(false); // 不允许把某一侧完全折叠
|
|
|
|
|
centerSplitter->setHandleWidth(4); // 中间拖动条宽度
|
|
|
|
|
centerSplitter->setStyleSheet(
|
|
|
|
|
"QSplitter::handle:horizontal {"
|
|
|
|
|
" background-color: #C0C0C0;"
|
|
|
|
|
"}"
|
|
|
|
|
"QSplitter::handle:horizontal:hover {"
|
|
|
|
|
" background-color: #A0A0A0;"
|
|
|
|
|
"}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// --- 左侧:Variables 面板(QFrame + 标题栏 + Tree)---
|
|
|
|
|
QFrame *variablesPanel = new QFrame(this);
|
|
|
|
|
variablesPanel->setFrameShape(QFrame::StyledPanel);
|
|
|
|
|
variablesPanel->setFrameShadow(QFrame::Raised);
|
|
|
|
|
variablesPanel->setMinimumWidth(230);
|
|
|
|
|
|
|
|
|
|
// 左侧参数树使用标准分组框,样式交给主软件统一处理。
|
|
|
|
|
QGroupBox *variablesPanel = new QGroupBox(tr("Variables"), this);
|
|
|
|
|
variablesPanel->setMinimumWidth(200);
|
|
|
|
|
QVBoxLayout *variablesPanelLayout = new QVBoxLayout(variablesPanel);
|
|
|
|
|
variablesPanelLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
variablesPanelLayout->setSpacing(0);
|
|
|
|
|
|
|
|
|
|
// 标题栏
|
|
|
|
|
QWidget *varHeader = new QWidget(variablesPanel);
|
|
|
|
|
varHeader->setFixedHeight(24);
|
|
|
|
|
varHeader->setAutoFillBackground(true);
|
|
|
|
|
{
|
|
|
|
|
QPalette pal = varHeader->palette();
|
|
|
|
|
pal.setColor(QPalette::Window,
|
|
|
|
|
palette().color(QPalette::Midlight));
|
|
|
|
|
varHeader->setPalette(pal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QHBoxLayout *varHeaderLayout = new QHBoxLayout(varHeader);
|
|
|
|
|
varHeaderLayout->setContentsMargins(6, 0, 6, 0);
|
|
|
|
|
varHeaderLayout->setSpacing(0);
|
|
|
|
|
|
|
|
|
|
QLabel *varTitle = new QLabel(tr("Variables"), varHeader);
|
|
|
|
|
varTitle->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
|
|
|
|
|
QFont f = varTitle->font();
|
|
|
|
|
f.setBold(true);
|
|
|
|
|
varTitle->setFont(f);
|
|
|
|
|
|
|
|
|
|
varHeaderLayout->addWidget(varTitle);
|
|
|
|
|
varHeaderLayout->addStretch();
|
|
|
|
|
variablesPanelLayout->setContentsMargins(10, 10, 10, 10);
|
|
|
|
|
|
|
|
|
|
// Tree 区域
|
|
|
|
|
variablesTree = new QTreeWidget(variablesPanel);
|
|
|
|
|
@ -1949,7 +1943,6 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
variablesTree->setFrameShape(QFrame::NoFrame); // 外层已经有 frame
|
|
|
|
|
variablesTree->setMinimumHeight(350);
|
|
|
|
|
|
|
|
|
|
variablesPanelLayout->addWidget(varHeader);
|
|
|
|
|
variablesPanelLayout->addWidget(variablesTree, 1);
|
|
|
|
|
|
|
|
|
|
// 把左侧 panel 加进 splitter
|
|
|
|
|
@ -1975,12 +1968,14 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
// 左边宽度基本固定,右边自适应
|
|
|
|
|
centerSplitter->setStretchFactor(0, 0);
|
|
|
|
|
centerSplitter->setStretchFactor(1, 1);
|
|
|
|
|
centerSplitter->setSizes(QList<int>() << 260 << 960);
|
|
|
|
|
centerSplitter->setSizes(QList<int>() << 220 << 860);
|
|
|
|
|
|
|
|
|
|
mainLayout->addWidget(centerSplitter, 1);
|
|
|
|
|
|
|
|
|
|
// ===== 底部:Total number of models + 按钮 =====
|
|
|
|
|
QHBoxLayout *bottomLayout = new QHBoxLayout();
|
|
|
|
|
bottomLayout->setContentsMargins(4, 5, 4, 5);
|
|
|
|
|
bottomLayout->setSpacing(8);
|
|
|
|
|
|
|
|
|
|
QLabel *totalLabel = new QLabel(tr("Total number of models:"), this);
|
|
|
|
|
modelCountLabel = new QLabel(tr("0"), this);
|
|
|
|
|
@ -2004,6 +1999,8 @@ void nmWxSensitive::setupUI()
|
|
|
|
|
|
|
|
|
|
generateButton = new QPushButton(tr("Generate"), this);
|
|
|
|
|
cancelButton = new QPushButton(tr("Cancel"), this);
|
|
|
|
|
generateButton->setIcon(zxLoadIcon("OK"));
|
|
|
|
|
cancelButton->setIcon(zxLoadIcon("Cancel"));
|
|
|
|
|
|
|
|
|
|
// 查看已有计算结果,不改变采样配置,也不重新求解。
|
|
|
|
|
m_resultPlotsButton = new QPushButton(tr("Result plots"), this);
|
|
|
|
|
@ -2149,48 +2146,22 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
pageLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
pageLayout->setSpacing(0);
|
|
|
|
|
|
|
|
|
|
// ===== 整个 Sampling 面板:QFrame + 标题栏 + 内容 =====
|
|
|
|
|
QFrame *samplingPanel = new QFrame(page);
|
|
|
|
|
samplingPanel->setFrameShape(QFrame::StyledPanel);
|
|
|
|
|
samplingPanel->setFrameShadow(QFrame::Raised);
|
|
|
|
|
|
|
|
|
|
// 采样页与左侧参数树统一使用标准分组框。
|
|
|
|
|
QGroupBox *samplingPanel = new QGroupBox(tr("Sampling %1").arg(varName), page);
|
|
|
|
|
QVBoxLayout *panelLayout = new QVBoxLayout(samplingPanel);
|
|
|
|
|
panelLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
panelLayout->setSpacing(0);
|
|
|
|
|
|
|
|
|
|
// ---- 标题栏:Sampling Xxx ----
|
|
|
|
|
QWidget *header = new QWidget(samplingPanel);
|
|
|
|
|
header->setFixedHeight(24);
|
|
|
|
|
header->setAutoFillBackground(true);
|
|
|
|
|
{
|
|
|
|
|
QPalette pal = header->palette();
|
|
|
|
|
pal.setColor(QPalette::Window,
|
|
|
|
|
palette().color(QPalette::Midlight));
|
|
|
|
|
header->setPalette(pal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QHBoxLayout *headerLayout = new QHBoxLayout(header);
|
|
|
|
|
headerLayout->setContentsMargins(6, 0, 6, 0);
|
|
|
|
|
headerLayout->setSpacing(0);
|
|
|
|
|
|
|
|
|
|
QLabel *title = new QLabel(tr("Sampling %1").arg(varName), header);
|
|
|
|
|
QFont tf = title->font();
|
|
|
|
|
tf.setBold(true);
|
|
|
|
|
title->setFont(tf);
|
|
|
|
|
headerLayout->addWidget(title);
|
|
|
|
|
headerLayout->addStretch();
|
|
|
|
|
|
|
|
|
|
panelLayout->addWidget(header);
|
|
|
|
|
panelLayout->setContentsMargins(10, 10, 10, 10);
|
|
|
|
|
panelLayout->setSpacing(8);
|
|
|
|
|
|
|
|
|
|
// ---- 内容区:左参数 + 右表格 ----
|
|
|
|
|
QWidget *content = new QWidget(samplingPanel);
|
|
|
|
|
QHBoxLayout *contentLayout = new QHBoxLayout(content);
|
|
|
|
|
contentLayout->setContentsMargins(6, 0, 6, 6);
|
|
|
|
|
contentLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
contentLayout->setSpacing(12);
|
|
|
|
|
|
|
|
|
|
// 左:参数区(GridLayout)
|
|
|
|
|
QGridLayout *paramLayout = new QGridLayout();
|
|
|
|
|
paramLayout->setVerticalSpacing(6);
|
|
|
|
|
paramLayout->setVerticalSpacing(8);
|
|
|
|
|
paramLayout->setHorizontalSpacing(8);
|
|
|
|
|
int row = 0;
|
|
|
|
|
|
|
|
|
|
// Mode + Log?
|
|
|
|
|
@ -2208,7 +2179,6 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
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();
|
|
|
|
|
@ -2228,11 +2198,6 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
paramLayout->addWidget(numSpin, row, 1);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// 模式、数量与下方采样参数之间留出约三行空白。
|
|
|
|
|
int gapRow = row;
|
|
|
|
|
paramLayout->setRowMinimumHeight(gapRow, numSpin->sizeHint().height() * 3);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// Model value + 单位
|
|
|
|
|
QLabel *modelLabel = new QLabel(tr("Model value:"), content);
|
|
|
|
|
QLineEdit *modelSpin = new QLineEdit(content);
|
|
|
|
|
@ -2240,13 +2205,18 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
// 基准值来自真实模型,只作参照;输入框支持科学计数法和完整精度。
|
|
|
|
|
modelSpin->setReadOnly(true);
|
|
|
|
|
|
|
|
|
|
QLabel *unitLabel = new QLabel(tr(""), content); // ⭐ 单位也从数据加载
|
|
|
|
|
// 当前参数整页共用一个单位,下拉选项和换算均复用框架单位组。
|
|
|
|
|
QComboBox *unitCombo = new QComboBox(content);
|
|
|
|
|
unitCombo->setObjectName("sensitivityUnit_" + varName);
|
|
|
|
|
unitCombo->setMinimumWidth(86);
|
|
|
|
|
unitCombo->setMaximumWidth(120);
|
|
|
|
|
unitCombo->setFixedHeight(25);
|
|
|
|
|
|
|
|
|
|
QWidget *modelRowWidget = new QWidget(content);
|
|
|
|
|
QHBoxLayout *modelRowLayout = new QHBoxLayout(modelRowWidget);
|
|
|
|
|
modelRowLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
modelRowLayout->addWidget(modelSpin);
|
|
|
|
|
modelRowLayout->addWidget(unitLabel);
|
|
|
|
|
modelRowLayout->addWidget(unitCombo);
|
|
|
|
|
modelRowLayout->addStretch();
|
|
|
|
|
|
|
|
|
|
paramLayout->addWidget(modelLabel, row, 0);
|
|
|
|
|
@ -2257,9 +2227,8 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
QWidget* distributionPanel = new QWidget(content);
|
|
|
|
|
QGridLayout* distributionLayout = new QGridLayout(distributionPanel);
|
|
|
|
|
distributionLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
distributionLayout->setHorizontalSpacing(6);
|
|
|
|
|
distributionLayout->setHorizontalSpacing(8);
|
|
|
|
|
distributionLayout->setVerticalSpacing(0);
|
|
|
|
|
distributionLayout->setColumnMinimumWidth(0, modelLabel->sizeHint().width());
|
|
|
|
|
const int fieldHeight = modelSpin->sizeHint().height();
|
|
|
|
|
// Mean 与三角分布的 Mode 共用一行,其余字段保持固定位置。
|
|
|
|
|
distributionPanel->setFixedHeight(fieldHeight * 4 + 6 * 3);
|
|
|
|
|
@ -2299,9 +2268,17 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
paramLayout->addWidget(maxSpin, row, 1);
|
|
|
|
|
++row;
|
|
|
|
|
|
|
|
|
|
// 统一编辑框宽度
|
|
|
|
|
int fullWidth = modelSpin->sizeHint().width();
|
|
|
|
|
int editWidth = qMax(150, fullWidth);
|
|
|
|
|
// 基本参数与分布参数使用相同标签列宽、列间距和输入宽度,保证上下对齐。
|
|
|
|
|
QList<QLabel*> labels;
|
|
|
|
|
labels << modeLabel << numLabel << modelLabel << minLabel << maxLabel;
|
|
|
|
|
labels.append(distributionPanel->findChildren<QLabel*>());
|
|
|
|
|
int labelWidth = 0;
|
|
|
|
|
foreach(QLabel* label, labels) labelWidth = qMax(labelWidth, label->sizeHint().width());
|
|
|
|
|
foreach(QLabel* label, labels) label->setFixedWidth(labelWidth);
|
|
|
|
|
const int editWidth = qMax(180, modelSpin->sizeHint().width());
|
|
|
|
|
distributionCombo->setFixedWidth(editWidth);
|
|
|
|
|
foreach(QLineEdit* edit, distributionPanel->findChildren<QLineEdit*>())
|
|
|
|
|
edit->setFixedWidth(editWidth);
|
|
|
|
|
|
|
|
|
|
modeCombo->setFixedWidth(editWidth);
|
|
|
|
|
numSpin->setFixedWidth(editWidth);
|
|
|
|
|
@ -2317,7 +2294,7 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
|
|
|
|
|
QWidget *paramWidget = new QWidget(content);
|
|
|
|
|
QVBoxLayout *leftVBox = new QVBoxLayout(paramWidget);
|
|
|
|
|
leftVBox->setContentsMargins(0, 6, 0, 0);
|
|
|
|
|
leftVBox->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
leftVBox->addLayout(paramLayout);
|
|
|
|
|
leftVBox->addStretch();
|
|
|
|
|
|
|
|
|
|
@ -2329,27 +2306,40 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
headers << "" << tr("Values");
|
|
|
|
|
valuesTable->setHorizontalHeaderLabels(headers);
|
|
|
|
|
|
|
|
|
|
valuesTable->verticalHeader()->setVisible(false);
|
|
|
|
|
// 沿用编辑井的行号、隔行底色和网格样式,保留采样行的箭头标记。
|
|
|
|
|
valuesTable->verticalHeader()->setVisible(true);
|
|
|
|
|
valuesTable->verticalHeader()->setMovable(false);
|
|
|
|
|
valuesTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
|
|
|
|
|
valuesTable->verticalHeader()->setMinimumWidth(18);
|
|
|
|
|
QHeaderView *hh = valuesTable->horizontalHeader();
|
|
|
|
|
hh->setResizeMode(QHeaderView::Fixed);
|
|
|
|
|
hh->setHighlightSections(false);
|
|
|
|
|
// 箭头列固定,数值列在下方设置为填满加宽后的表格。
|
|
|
|
|
valuesTable->setColumnWidth(0, 15);
|
|
|
|
|
valuesTable->setStyleSheet(
|
|
|
|
|
"QHeaderView::section {"
|
|
|
|
|
" border-top: 0px;"
|
|
|
|
|
" border-left: 0px;"
|
|
|
|
|
" border-right: 1px solid lightgray;"
|
|
|
|
|
" border-bottom: 1px solid lightgray;"
|
|
|
|
|
"}"
|
|
|
|
|
"QTableCornerButton::section {"
|
|
|
|
|
" border-top: 0px;"
|
|
|
|
|
" border-left: 0px;"
|
|
|
|
|
" border-right: 1px solid lightgray;"
|
|
|
|
|
" border-bottom: 1px solid lightgray;"
|
|
|
|
|
"}"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
valuesTable->setAlternatingRowColors(true);
|
|
|
|
|
valuesTable->setStyleSheet(
|
|
|
|
|
"QTableWidget {"
|
|
|
|
|
" background-color: palette(base);"
|
|
|
|
|
" alternate-background-color: rgb(232, 232, 232);"
|
|
|
|
|
" gridline-color: palette(midlight);"
|
|
|
|
|
" border: 1px solid palette(mid);"
|
|
|
|
|
"}"
|
|
|
|
|
"QHeaderView {"
|
|
|
|
|
" background-color: palette(button);"
|
|
|
|
|
" color: palette(button-text);"
|
|
|
|
|
"}"
|
|
|
|
|
"QHeaderView::section {"
|
|
|
|
|
" background-color: palette(button);"
|
|
|
|
|
" color: palette(button-text);"
|
|
|
|
|
" border: none;"
|
|
|
|
|
" border-right: 1px solid palette(mid);"
|
|
|
|
|
" border-bottom: 1px solid palette(mid);"
|
|
|
|
|
"}"
|
|
|
|
|
"QTableCornerButton::section {"
|
|
|
|
|
" background-color: palette(base);"
|
|
|
|
|
" border: none;"
|
|
|
|
|
" border-right: 1px solid palette(mid);"
|
|
|
|
|
" border-bottom: 1px solid palette(mid);"
|
|
|
|
|
"}");
|
|
|
|
|
valuesTable->horizontalHeader()->setStretchLastSection(true);
|
|
|
|
|
valuesTable->setEditTriggers(QAbstractItemView::DoubleClicked);
|
|
|
|
|
valuesTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
|
|
|
@ -2360,22 +2350,27 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
|
|
|
|
|
valuesTable->setItemDelegateForColumn(1, new RightAlignDelegate(valuesTable));
|
|
|
|
|
|
|
|
|
|
// 初始空表的列宽尚未稳定,直接固定容器宽度,不从未显示的表头反算。
|
|
|
|
|
const int tableWidth = 190;
|
|
|
|
|
valuesTable->setFixedWidth(tableWidth);
|
|
|
|
|
// 与编辑井一样使用可拖动分栏,表格和分布图共用右侧空间。
|
|
|
|
|
QSplitter* samplingSplitter = new QSplitter(Qt::Horizontal, content);
|
|
|
|
|
samplingSplitter->setChildrenCollapsible(false);
|
|
|
|
|
samplingSplitter->setHandleWidth(6);
|
|
|
|
|
// 分栏仍可拖动,但不绘制参数与图表之间的灰色竖线。
|
|
|
|
|
samplingSplitter->setStyleSheet("QSplitter::handle { background: transparent; border: none; }");
|
|
|
|
|
samplingSplitter->addWidget(paramWidget);
|
|
|
|
|
|
|
|
|
|
QWidget *tableContainer = new QWidget(content);
|
|
|
|
|
QWidget *tableContainer = new QWidget(samplingSplitter);
|
|
|
|
|
QVBoxLayout *tableLayout = new QVBoxLayout(tableContainer);
|
|
|
|
|
tableLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
tableLayout->setSpacing(0);
|
|
|
|
|
tableLayout->addWidget(valuesTable);
|
|
|
|
|
tableContainer->setFixedWidth(tableWidth);
|
|
|
|
|
|
|
|
|
|
contentLayout->addWidget(paramWidget, 1);
|
|
|
|
|
contentLayout->addStretch();
|
|
|
|
|
contentLayout->addWidget(tableContainer, 0, Qt::AlignRight);
|
|
|
|
|
SensitivityDistributionPlot* distributionPlot = new SensitivityDistributionPlot(content);
|
|
|
|
|
contentLayout->addWidget(distributionPlot, 2);
|
|
|
|
|
valuesTable->setMinimumWidth(190);
|
|
|
|
|
SensitivityDistributionPlot* distributionPlot = new SensitivityDistributionPlot(tableContainer);
|
|
|
|
|
tableLayout->addWidget(distributionPlot);
|
|
|
|
|
samplingSplitter->addWidget(tableContainer);
|
|
|
|
|
samplingSplitter->setStretchFactor(0, 1);
|
|
|
|
|
samplingSplitter->setStretchFactor(1, 0);
|
|
|
|
|
samplingSplitter->setSizes(QList<int>() << 630 << 210);
|
|
|
|
|
contentLayout->addWidget(samplingSplitter);
|
|
|
|
|
|
|
|
|
|
panelLayout->addWidget(content);
|
|
|
|
|
pageLayout->addWidget(samplingPanel);
|
|
|
|
|
@ -2389,7 +2384,8 @@ QWidget *nmWxSensitive::createVariablePage(const QString &varName)
|
|
|
|
|
m_minSpins.append(minSpin);
|
|
|
|
|
m_maxSpins.append(maxSpin);
|
|
|
|
|
m_valueTables.append(valuesTable);
|
|
|
|
|
m_unitLabels.append(unitLabel);
|
|
|
|
|
m_unitCombos.append(unitCombo);
|
|
|
|
|
connect(unitCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onSamplingUnitChanged()));
|
|
|
|
|
m_numLabels.append(numLabel);
|
|
|
|
|
m_minLabels.append(minLabel);
|
|
|
|
|
m_maxLabels.append(maxLabel);
|
|
|
|
|
@ -2431,13 +2427,23 @@ void nmWxSensitive::updateValuesForPage(int idx)
|
|
|
|
|
m_maxLabels[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_minSpins[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_maxSpins[idx]->setVisible(!monteCarlo);
|
|
|
|
|
m_valueTables[idx]->parentWidget()->setVisible(!monteCarlo);
|
|
|
|
|
m_valueTables[idx]->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);
|
|
|
|
|
// 表格采用较窄的默认宽度;分布图让参数区仅占所需宽度,避免沿用表格分栏。
|
|
|
|
|
// 只在布局类型切换时调整,编辑参数时保留用户拖动后的宽度。
|
|
|
|
|
QSplitter* splitter = static_cast<QSplitter*>(m_valueTables[idx]->parentWidget()->parentWidget());
|
|
|
|
|
const QVariant previousLayout = splitter->property("monteCarloLayout");
|
|
|
|
|
if (!previousLayout.isValid() || previousLayout.toBool() != monteCarlo) {
|
|
|
|
|
splitter->setProperty("monteCarloLayout", monteCarlo);
|
|
|
|
|
splitter->setStretchFactor(0, monteCarlo ? 0 : 1);
|
|
|
|
|
splitter->setStretchFactor(1, monteCarlo ? 1 : 0);
|
|
|
|
|
const int availableWidth = qMax(840, splitter->width() - splitter->handleWidth());
|
|
|
|
|
const int parameterWidth = monteCarlo ? splitter->widget(0)->minimumSizeHint().width()
|
|
|
|
|
: availableWidth - 210;
|
|
|
|
|
splitter->setSizes(QList<int>() << parameterWidth << availableWidth - parameterWidth);
|
|
|
|
|
}
|
|
|
|
|
if(monteCarlo) { updateDistributionPage(idx); return; }
|
|
|
|
|
// 自动模式由范围生成只读表格;手动模式保留每行值,不因选中行而重算。
|
|
|
|
|
QTableWidget* table = m_valueTables[idx];
|
|
|
|
|
@ -2456,7 +2462,7 @@ void nmWxSensitive::updateValuesForPage(int idx)
|
|
|
|
|
}
|
|
|
|
|
QVector<double> samples;
|
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
const QString unit = m_unitCombos[idx]->currentText();
|
|
|
|
|
if (automatic) {
|
|
|
|
|
bool minOk = false, maxOk = false;
|
|
|
|
|
double minimum = samplingEditValue(m_minSpins[idx], &minOk);
|
|
|
|
|
@ -2497,7 +2503,7 @@ bool nmWxSensitive::readDistributionPage(int idx, nmDataSensitive::VariableSampl
|
|
|
|
|
// 字段转换沿用现有参数单位,仅校验当前分布实际使用的字段。
|
|
|
|
|
variable.m_distribution = static_cast<nmDataSensitive::VariableSampling::Distribution>(
|
|
|
|
|
m_distributionCombos[idx]->currentIndex());
|
|
|
|
|
variable.m_modelValue.setUnit(m_unitLabels[idx]->text());
|
|
|
|
|
variable.m_modelValue.setUnit(m_unitCombos[idx]->currentText());
|
|
|
|
|
double* fields[] = {&variable.m_mean, &variable.m_stdDev, &variable.m_peak,
|
|
|
|
|
&variable.m_distributionMin, &variable.m_distributionMax};
|
|
|
|
|
const int distribution = variable.m_distribution;
|
|
|
|
|
@ -2509,7 +2515,7 @@ bool nmWxSensitive::readDistributionPage(int idx, nmDataSensitive::VariableSampl
|
|
|
|
|
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;
|
|
|
|
|
m_unitCombos[idx]->currentText(), samplingBaseUnit(variable.m_varName), *fields[field], field == 1)) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
@ -2535,6 +2541,81 @@ void nmWxSensitive::updateDistributionPage(int idx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::onSamplingUnitChanged()
|
|
|
|
|
{
|
|
|
|
|
if (m_loading) return;
|
|
|
|
|
QComboBox* combo = qobject_cast<QComboBox*>(sender());
|
|
|
|
|
const int idx = m_unitCombos.indexOf(combo);
|
|
|
|
|
if (idx < 0) return;
|
|
|
|
|
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
|
|
|
|
|
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
|
|
|
|
|
if (findVariableIndex(variable) != idx) continue;
|
|
|
|
|
const QString from = variable.getModelValue().getUnit();
|
|
|
|
|
const QString to = combo->currentText();
|
|
|
|
|
if (from == to) return;
|
|
|
|
|
|
|
|
|
|
// 先换算当前界面草稿,全部成功后再更新,避免覆盖未提交的手动输入。
|
|
|
|
|
QList<QLineEdit*> edits;
|
|
|
|
|
edits << m_modelSpins[idx] << m_minSpins[idx] << m_maxSpins[idx];
|
|
|
|
|
for (int field = 0; field < 5; ++field)
|
|
|
|
|
edits << m_distributionPanels[idx]->findChild<QLineEdit*>(distributionFields[field]);
|
|
|
|
|
QVector<double> values(edits.size());
|
|
|
|
|
bool valid = true;
|
|
|
|
|
for (int field = 0; valid && field < edits.size(); ++field) {
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const double value = samplingEditValue(edits[field], &ok);
|
|
|
|
|
valid = ok && convertSamplingValue(variable.m_varName, value, from, to, values[field], field == 4);
|
|
|
|
|
}
|
|
|
|
|
QTableWidget* table = m_valueTables[idx];
|
|
|
|
|
QVector<double> tableValues(table->rowCount());
|
|
|
|
|
for (int row = 0; valid && row < table->rowCount(); ++row) {
|
|
|
|
|
QTableWidgetItem* item = table->item(row, 1);
|
|
|
|
|
if (!item || item->text().isEmpty()) continue;
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const double value = item->data(Qt::UserRole).isValid()
|
|
|
|
|
? item->data(Qt::UserRole).toDouble(&ok) : item->text().trimmed().toDouble(&ok);
|
|
|
|
|
valid = ok && convertSamplingValue(variable.m_varName, value, from, to, tableValues[row]);
|
|
|
|
|
}
|
|
|
|
|
if (!valid) {
|
|
|
|
|
combo->blockSignals(true);
|
|
|
|
|
combo->setCurrentIndex(combo->findText(from));
|
|
|
|
|
combo->blockSignals(false);
|
|
|
|
|
double lower = variable.getModelValue().getMinValue().toDouble();
|
|
|
|
|
double upper = variable.getModelValue().getMaxValue().toDouble();
|
|
|
|
|
convertSamplingValue(variable.m_varName, lower, samplingBaseUnit(variable.m_varName), from, lower);
|
|
|
|
|
convertSamplingValue(variable.m_varName, upper, samplingBaseUnit(variable.m_varName), from, upper);
|
|
|
|
|
QMessageBox::warning(this, tr("Sensitivity"),
|
|
|
|
|
tr("%1: invalid sampling values. Allowed range: [%2, %3] %4. Check the sampling settings.")
|
|
|
|
|
.arg(variable.m_varName).arg(formatSamplingValue(lower, 6))
|
|
|
|
|
.arg(formatSamplingValue(upper, 6)).arg(from));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 仅更新显示单位和显示值,基准采样数据及已生成的随机样本保持不变。
|
|
|
|
|
m_loading = true;
|
|
|
|
|
variable.getModelValue().setUnit(to);
|
|
|
|
|
for (int field = 0; field < edits.size(); ++field)
|
|
|
|
|
setSamplingEditValue(edits[field], values[field]);
|
|
|
|
|
for (int row = 0; row < table->rowCount(); ++row) {
|
|
|
|
|
QTableWidgetItem* item = table->item(row, 1);
|
|
|
|
|
if (!item || item->text().isEmpty()) continue;
|
|
|
|
|
item->setData(Qt::UserRole, tableValues[row]);
|
|
|
|
|
item->setText(formatSamplingValue(tableValues[row], 3));
|
|
|
|
|
}
|
|
|
|
|
double lower = variable.getModelValue().getMinValue().toDouble();
|
|
|
|
|
double upper = variable.getModelValue().getMaxValue().toDouble();
|
|
|
|
|
convertSamplingValue(variable.m_varName, lower, samplingBaseUnit(variable.m_varName), to, lower);
|
|
|
|
|
convertSamplingValue(variable.m_varName, upper, samplingBaseUnit(variable.m_varName), to, upper);
|
|
|
|
|
const QString rangeHint = tr("Allowed range: [%1, %2] %3")
|
|
|
|
|
.arg(formatSamplingValue(lower, 6)).arg(formatSamplingValue(upper, 6)).arg(to);
|
|
|
|
|
m_minSpins[idx]->setToolTip(rangeHint);
|
|
|
|
|
m_maxSpins[idx]->setToolTip(rangeHint);
|
|
|
|
|
updateValuesForPage(idx);
|
|
|
|
|
m_loading = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitive::onSamplingEditingFinished()
|
|
|
|
|
{
|
|
|
|
|
// 输入结束后截断越界范围,再按六位有效数字显示,计算仍使用原始精度。
|
|
|
|
|
@ -2550,7 +2631,7 @@ void nmWxSensitive::onSamplingEditingFinished()
|
|
|
|
|
const QString name = variable.getVarName();
|
|
|
|
|
if (findVariableIndex(variable) != idx) continue;
|
|
|
|
|
double lower = 0.0, upper = 0.0;
|
|
|
|
|
const QString unit = m_unitLabels[idx]->text();
|
|
|
|
|
const QString unit = m_unitCombos[idx]->currentText();
|
|
|
|
|
const QString baseUnit = samplingBaseUnit(name);
|
|
|
|
|
if (!samplingPhysicalRange(name, lower, upper) ||
|
|
|
|
|
!convertSamplingValue(name, value, unit, baseUnit, value)) return;
|
|
|
|
|
|