|
|
|
|
@ -246,15 +246,22 @@ public:
|
|
|
|
|
|
|
|
|
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override
|
|
|
|
|
{
|
|
|
|
|
return parent.isValid() ? 0 : 3;
|
|
|
|
|
return parent.isValid() ? 0 : 3 + (m_statistics ? m_results->m_fitParameters.size() : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
|
|
|
|
{
|
|
|
|
|
// 表格仅保留显示选择、运行编号与冻结参数。
|
|
|
|
|
if(orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant();
|
|
|
|
|
if(m_statistics) return (QStringList() << "#" << nmWxSensitive::tr("Realization")
|
|
|
|
|
if(m_statistics) {
|
|
|
|
|
if(section >= 3 && section - 3 < m_results->m_fitParameters.size()) {
|
|
|
|
|
const nmDataAttribute& parameter = m_results->m_fitParameters[section - 3];
|
|
|
|
|
return parameter.getName() + (parameter.getUnit().isEmpty() ? QString()
|
|
|
|
|
: " [" + parameter.getUnit() + "]");
|
|
|
|
|
}
|
|
|
|
|
return (QStringList() << "#" << nmWxSensitive::tr("Realization")
|
|
|
|
|
<< nmWxSensitive::tr("Goodness of fit [MPa]")).value(section);
|
|
|
|
|
}
|
|
|
|
|
const QStringList titles = QStringList() << nmWxSensitive::tr("Show") << "#"
|
|
|
|
|
<< nmWxSensitive::tr("Parameters");
|
|
|
|
|
return titles.value(section);
|
|
|
|
|
@ -270,7 +277,8 @@ public:
|
|
|
|
|
if(role != Qt::DisplayRole && role != Qt::ToolTipRole) return QVariant();
|
|
|
|
|
if(index.column() == 0) return QString::number(run.m_nRunIndex + 1);
|
|
|
|
|
if(index.column() == 1) return sensitivityRunParameters(*m_results, run);
|
|
|
|
|
return hasSensitivityError(run) ? QString::number(run.m_dPressureRmse, 'g', 6)
|
|
|
|
|
double value;
|
|
|
|
|
return m_results->statisticValue(run, index.column() - 3, value) ? QString::number(value, 'g', 6)
|
|
|
|
|
: run.m_bCancelled ? nmWxSensitive::tr("Cancelled")
|
|
|
|
|
: !run.m_bSucceeded ? nmWxSensitive::tr("Failed") : nmWxSensitive::tr("N/A");
|
|
|
|
|
}
|
|
|
|
|
@ -420,7 +428,7 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
|
|
|
|
|
const QSharedPointer<nmDataSensitive::BatchResult>& results, int bins, QWidget* parent)
|
|
|
|
|
: iDlgBase(parent)
|
|
|
|
|
{
|
|
|
|
|
// 复用运行表格的参数展示,普通 Monte-Carlo 的统计变量只有误差。
|
|
|
|
|
// 复用运行表格;第四种在误差后追加拟合参数,选择变量后按原有 Apply/OK 流程生效。
|
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
|
setObjectName("nmHistogramOptions");
|
|
|
|
|
setWindowTitle(tr("Histogram options"));
|
|
|
|
|
@ -434,9 +442,12 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
|
|
|
|
|
options->addWidget(m_bins);
|
|
|
|
|
options->addStretch();
|
|
|
|
|
options->addWidget(new QLabel(tr("Variable:"), this));
|
|
|
|
|
QComboBox* variable = new QComboBox(this);
|
|
|
|
|
variable->addItem(tr("Goodness of fit"));
|
|
|
|
|
options->addWidget(variable);
|
|
|
|
|
m_variable = new QComboBox(this);
|
|
|
|
|
m_variable->addItem(tr("Goodness of fit"), -1);
|
|
|
|
|
for(int i = 0; i < results->m_fitParameters.size(); ++i)
|
|
|
|
|
m_variable->addItem(results->m_fitParameters[i].getName(), i);
|
|
|
|
|
m_variable->setCurrentIndex(m_variable->findData(results->m_nHistogramVariable));
|
|
|
|
|
options->addWidget(m_variable);
|
|
|
|
|
layout->addLayout(options);
|
|
|
|
|
QTableView* table = new QTableView(this);
|
|
|
|
|
table->setStyleSheet("QTableView { background-color: white; }");
|
|
|
|
|
@ -447,7 +458,8 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
|
|
|
|
|
table->verticalHeader()->hide();
|
|
|
|
|
table->horizontalHeader()->setResizeMode(0, QHeaderView::ResizeToContents);
|
|
|
|
|
table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
|
|
|
|
|
table->horizontalHeader()->setResizeMode(2, QHeaderView::ResizeToContents);
|
|
|
|
|
for(int i = 2; i < table->model()->columnCount(); ++i)
|
|
|
|
|
table->horizontalHeader()->setResizeMode(i, QHeaderView::ResizeToContents);
|
|
|
|
|
layout->addWidget(table);
|
|
|
|
|
QHBoxLayout* buttons = new QHBoxLayout;
|
|
|
|
|
buttons->addStretch();
|
|
|
|
|
@ -465,8 +477,8 @@ nmWxSensitivityHistogramOptions::nmWxSensitivityHistogramOptions(
|
|
|
|
|
|
|
|
|
|
void nmWxSensitivityHistogramOptions::onApply()
|
|
|
|
|
{
|
|
|
|
|
// 只提交分箱数,取消不会撤销已经应用的设置。
|
|
|
|
|
emit binsChanged(m_bins->value());
|
|
|
|
|
// 分箱数与横轴变量一起提交,取消不会撤销已经应用的设置。
|
|
|
|
|
emit settingsChanged(m_bins->value(), m_variable->itemData(m_variable->currentIndex()).toInt());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitivityHistogramOptions::onAccept()
|
|
|
|
|
@ -496,9 +508,11 @@ nmWxSensitivityScatter::nmWxSensitivityScatter(
|
|
|
|
|
options->addWidget(m_parameter, 1);
|
|
|
|
|
options->addSpacing(20);
|
|
|
|
|
options->addWidget(new QLabel(tr("Y Axis:"), this));
|
|
|
|
|
QComboBox* ordinate = new QComboBox(this);
|
|
|
|
|
ordinate->addItem(tr("Goodness of fit"));
|
|
|
|
|
options->addWidget(ordinate, 1);
|
|
|
|
|
m_ordinate = new QComboBox(this);
|
|
|
|
|
m_ordinate->addItem(tr("Goodness of fit"), -1);
|
|
|
|
|
for(int i = 0; i < results->m_fitParameters.size(); ++i)
|
|
|
|
|
m_ordinate->addItem(results->m_fitParameters[i].getName(), i);
|
|
|
|
|
options->addWidget(m_ordinate, 1);
|
|
|
|
|
for(size_t i = 0; i < results->m_runs.size(); ++i)
|
|
|
|
|
if(hasSensitivityError(results->m_runs[i])) m_defaultThreshold = qMax(m_defaultThreshold, results->m_runs[i].m_dPressureRmse);
|
|
|
|
|
if(qIsFinite(baseError) && baseError >= 0.0) m_defaultThreshold = baseError;
|
|
|
|
|
@ -526,13 +540,19 @@ nmWxSensitivityScatter::nmWxSensitivityScatter(
|
|
|
|
|
updateLegend();
|
|
|
|
|
m_plot->m_pPlotView->viewport()->installEventFilter(this);
|
|
|
|
|
connect(m_parameter, SIGNAL(currentIndexChanged(int)), this, SLOT(updateParameter()));
|
|
|
|
|
connect(m_ordinate, SIGNAL(currentIndexChanged(int)), this, SLOT(updateParameter()));
|
|
|
|
|
connect(m_plot->m_pPlotView, SIGNAL(sigAdjustContextMenu(QMenu*)), this, SLOT(adjustContextMenu(QMenu*)));
|
|
|
|
|
updateParameter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitivityScatter::updateParameter()
|
|
|
|
|
{
|
|
|
|
|
// 切换横轴时重建参数坐标,阈值在各参数之间共用。
|
|
|
|
|
// 两轴切换都从本批结果重建坐标;拟合参数轴不使用误差阈值。
|
|
|
|
|
m_draggingThreshold = false;
|
|
|
|
|
const int ordinate = m_ordinate->itemData(m_ordinate->currentIndex()).toInt();
|
|
|
|
|
const bool errorAxis = ordinate == -1;
|
|
|
|
|
const double modelY = errorAxis ? m_baseError : m_results->m_fitParameters[ordinate].getValue().toDouble();
|
|
|
|
|
m_plot->setAxisY(m_ordinate->currentText(), errorAxis ? "MPa" : m_results->m_fitParameters[ordinate].getUnit());
|
|
|
|
|
m_points.clear();
|
|
|
|
|
m_runRows.clear();
|
|
|
|
|
if(m_parameter->currentIndex() < 0) {
|
|
|
|
|
@ -545,17 +565,19 @@ void nmWxSensitivityScatter::updateParameter()
|
|
|
|
|
m_plot->setAxisX(variable.m_varName, unit);
|
|
|
|
|
double modelValue = variable.m_modelValue.getValue().toDouble();
|
|
|
|
|
const bool hasModel = convertSamplingValue(variable.m_varName, modelValue,
|
|
|
|
|
samplingBaseUnit(variable.m_varName), unit, modelValue) && m_baseError >= 0.0 && qIsFinite(m_baseError);
|
|
|
|
|
samplingBaseUnit(variable.m_varName), unit, modelValue) && qIsFinite(modelY) && (!errorAxis || modelY >= 0.0);
|
|
|
|
|
for(size_t i = 0; i < m_results->m_runs.size(); ++i) {
|
|
|
|
|
const nmDataSensitive::RunResult& run = m_results->m_runs[i];
|
|
|
|
|
if(!hasSensitivityError(run) || index >= run.m_vecParameters.size()) continue;
|
|
|
|
|
double ordinateValue;
|
|
|
|
|
if(!m_results->statisticValue(run, ordinate, ordinateValue) || index >= run.m_vecParameters.size()) continue;
|
|
|
|
|
double value = run.m_vecParameters[index];
|
|
|
|
|
if(!convertSamplingValue(variable.m_varName, value, samplingBaseUnit(variable.m_varName), unit, value)) continue;
|
|
|
|
|
m_points.append(QPointF(value, run.m_dPressureRmse));
|
|
|
|
|
m_points.append(QPointF(value, ordinateValue));
|
|
|
|
|
m_runRows.append(int(i));
|
|
|
|
|
}
|
|
|
|
|
QVector<iCurveDesc> curves;
|
|
|
|
|
for(int i = 0; i < 4; ++i) {
|
|
|
|
|
if(!errorAxis && (i == 1 || i == 3)) continue;
|
|
|
|
|
iCurveDesc curve;
|
|
|
|
|
curve.m_sCurveName = i == 0 ? tr("sensitivity data") : i == 1 ? tr("Below threshold")
|
|
|
|
|
: i == 2 ? tr("reference") : tr("Threshold");
|
|
|
|
|
@ -570,7 +592,7 @@ void nmWxSensitivityScatter::updateParameter()
|
|
|
|
|
}
|
|
|
|
|
if(i == 2 && hasModel) {
|
|
|
|
|
curve.m_vecX.append(modelValue);
|
|
|
|
|
curve.m_vecY.append(m_baseError);
|
|
|
|
|
curve.m_vecY.append(modelY);
|
|
|
|
|
}
|
|
|
|
|
if(i == 3) {
|
|
|
|
|
curve.m_bLineVisible = true;
|
|
|
|
|
@ -589,30 +611,41 @@ void nmWxSensitivityScatter::updateParameter()
|
|
|
|
|
m_plot->runUpdate();
|
|
|
|
|
m_plot->setReadOnly(true);
|
|
|
|
|
double left = hasModel ? modelValue : m_points.isEmpty() ? 0.0 : m_points.first().x();
|
|
|
|
|
double right = left, top = hasModel ? m_baseError : 0.0;
|
|
|
|
|
double right = left;
|
|
|
|
|
double bottom = hasModel ? modelY : m_points.isEmpty() ? 0.0 : m_points.first().y();
|
|
|
|
|
double top = bottom;
|
|
|
|
|
for(int i = 0; i < m_points.size(); ++i) {
|
|
|
|
|
left = qMin(left, m_points[i].x());
|
|
|
|
|
right = qMax(right, m_points[i].x());
|
|
|
|
|
bottom = qMin(bottom, m_points[i].y());
|
|
|
|
|
top = qMax(top, m_points[i].y());
|
|
|
|
|
}
|
|
|
|
|
const double padding = qMax(1e-6, (right > left ? right - left : qMax(1.0, qAbs(left))) * 0.05);
|
|
|
|
|
left -= padding;
|
|
|
|
|
right += padding;
|
|
|
|
|
top = qMax(1e-6, qMax(top, m_threshold) * 1.1);
|
|
|
|
|
if(errorAxis) {
|
|
|
|
|
bottom = 0.0;
|
|
|
|
|
top = qMax(1e-6, qMax(top, m_threshold) * 1.1);
|
|
|
|
|
} else {
|
|
|
|
|
const double margin = qMax(1e-6, (top > bottom ? top - bottom : qMax(1.0, qAbs(top))) * 0.05);
|
|
|
|
|
bottom -= margin;
|
|
|
|
|
top += margin;
|
|
|
|
|
}
|
|
|
|
|
VecDouble ticksX, ticksY;
|
|
|
|
|
for(int i = 0; i <= 5; ++i) {
|
|
|
|
|
ticksX.append(left + i * (right - left) / 5.0);
|
|
|
|
|
ticksY.append(i * top / 5.0);
|
|
|
|
|
ticksY.append(bottom + i * (top - bottom) / 5.0);
|
|
|
|
|
}
|
|
|
|
|
m_plot->setAxisScaleX(left, right, ticksX);
|
|
|
|
|
m_plot->setAxisScaleY(0.0, top, ticksY);
|
|
|
|
|
updateThreshold();
|
|
|
|
|
m_plot->setAxisScaleY(bottom, top, ticksY);
|
|
|
|
|
updateLegend();
|
|
|
|
|
if(errorAxis) updateThreshold();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmWxSensitivityScatter::updateThreshold()
|
|
|
|
|
{
|
|
|
|
|
// 阈值以下的点填黑,并同步图例中的阈值显示。
|
|
|
|
|
if(m_parameter->currentIndex() < 0) return;
|
|
|
|
|
if(m_parameter->currentIndex() < 0 || m_ordinate->currentIndex() != 0) return;
|
|
|
|
|
// 拖动时只更新图内反馈,松开后再保存阈值并刷新结果面板。
|
|
|
|
|
if(!m_draggingThreshold && m_results->m_dFilterThreshold != m_threshold) {
|
|
|
|
|
m_results->m_dFilterThreshold = m_threshold;
|
|
|
|
|
@ -669,6 +702,7 @@ void nmWxSensitivityScatter::resetThreshold()
|
|
|
|
|
void nmWxSensitivityScatter::adjustContextMenu(QMenu* menu)
|
|
|
|
|
{
|
|
|
|
|
// 复用绘图右键菜单,保留框架的缩放、复制与导出入口。
|
|
|
|
|
if(m_ordinate->currentIndex() != 0) return;
|
|
|
|
|
menu->addAction(m_resultsInPlot);
|
|
|
|
|
menu->addAction(tr("Reset line"), this, SLOT(resetThreshold()));
|
|
|
|
|
}
|
|
|
|
|
@ -678,7 +712,7 @@ void nmWxSensitivityScatter::updateLegend()
|
|
|
|
|
// 勾选后在原有两项图例下追加阈值,拖线和重置时实时更新。
|
|
|
|
|
QString text = "<table cellspacing='2'><tr><td>□</td><td>" + tr("sensitivity data")
|
|
|
|
|
+ "</td></tr><tr><td style='color:blue'>■</td><td>" + tr("reference") + "</td></tr></table>";
|
|
|
|
|
if(m_resultsInPlot->isChecked())
|
|
|
|
|
if(m_ordinate->currentIndex() == 0 && m_resultsInPlot->isChecked())
|
|
|
|
|
text += "<div>" + tr("Threshold: %1 MPa").arg(m_threshold, 0, 'g', 6) + "</div>";
|
|
|
|
|
m_legend->setText(text);
|
|
|
|
|
m_legend->adjustSize();
|
|
|
|
|
@ -693,7 +727,8 @@ bool nmWxSensitivityScatter::eventFilter(QObject* object, QEvent* event)
|
|
|
|
|
if(event->type() == QEvent::Paint)
|
|
|
|
|
m_legend->move(view->mapFromScene(m_plot->m_pPlot->getInnerRectF().topLeft()).toPoint() + QPoint(8, 8));
|
|
|
|
|
// 只接管阈值线附近的左键拖动,其余缩放、右键及导出仍由框架处理。
|
|
|
|
|
if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease) {
|
|
|
|
|
if(m_ordinate->currentIndex() == 0 && (event->type() == QEvent::MouseButtonPress ||
|
|
|
|
|
event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease)) {
|
|
|
|
|
QMouseEvent* mouse = static_cast<QMouseEvent*>(event);
|
|
|
|
|
const QPointF scene = view->mapToScene(QPointF(mouse->pos()));
|
|
|
|
|
const double lineY = view->mapFromScene(QPointF(scene.x(), axisY->getPosForValue(m_threshold))).y();
|
|
|
|
|
@ -713,9 +748,11 @@ bool nmWxSensitivityScatter::eventFilter(QObject* object, QEvent* event)
|
|
|
|
|
const QPointF position = view->mapFromScene(QPointF(m_plot->getAxisOf(0)->getPosForValue(m_points[i].x()), axisY->getPosForValue(m_points[i].y())));
|
|
|
|
|
if(qAbs(position.x() - help->pos().x()) > 6.0 || qAbs(position.y() - help->pos().y()) > 6.0) continue;
|
|
|
|
|
const nmDataSensitive::RunResult& run = m_results->m_runs[m_runRows[i]];
|
|
|
|
|
QToolTip::showText(help->globalPos(), tr("Run %1\n%2\nGoodness of fit: %3 MPa")
|
|
|
|
|
.arg(run.m_nRunIndex + 1).arg(sensitivityRunParameters(*m_results, run))
|
|
|
|
|
.arg(run.m_dPressureRmse, 0, 'g', 6), view->viewport());
|
|
|
|
|
const int ordinate = m_ordinate->itemData(m_ordinate->currentIndex()).toInt();
|
|
|
|
|
const QString unit = ordinate < 0 ? "MPa" : m_results->m_fitParameters[ordinate].getUnit();
|
|
|
|
|
QToolTip::showText(help->globalPos(), tr("Run %1\n%2").arg(run.m_nRunIndex + 1)
|
|
|
|
|
.arg(sensitivityRunParameters(*m_results, run)) + "\n" + m_ordinate->currentText()
|
|
|
|
|
+ ": " + QString::number(m_points[i].y(), 'g', 6) + " " + unit, view->viewport());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
QToolTip::hideText();
|
|
|
|
|
|