diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 4620b8f6..8acf26ad 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "nmDataAnalyzeManager.h" #include "nmDataReservoir.h" @@ -27,12 +28,23 @@ #include "nmSubWxs_global.h" +// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。 +struct nmSensitivityFitSettings +{ + nmSensitivityFitSettings() : confirmed(false) {} + bool confirmed; + nmDataAutomaticFitting parameters; + QString targetWell; + QVector initialValues; +}; + class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase { Q_OBJECT public: - nmWxAutomaticFitting(QWidget *parent = 0); + nmWxAutomaticFitting(QWidget *parent = 0, nmSensitivityFitSettings* settings = nullptr, + const QStringList& sampledParameters = QStringList()); ~nmWxAutomaticFitting(); virtual QSize sizeHint() const; @@ -57,6 +69,7 @@ private: void setupControlPanel(); void setupButtons(); void setAutomaticFittingValue(); + void loadSensitivitySettings(); void setParameterRowVisible(QTableWidget* table, int row, bool visible); void renumberVisibleParameterRows(QTableWidget* table); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); @@ -101,6 +114,8 @@ private: // 数据成员 nmDataReservoir reservoirData; nmDataAutomaticFitting automaticFittingData; + nmSensitivityFitSettings* m_sensitivitySettings; + QStringList m_sampledParameters; // 自动拟合相关成员 nmCalculationAutoFitPSO* m_autoFitterPSO; diff --git a/Include/nmNum/nmSubWxs/nmWxSensitive.h b/Include/nmNum/nmSubWxs/nmWxSensitive.h index 460979cf..e2838f3e 100644 --- a/Include/nmNum/nmSubWxs/nmWxSensitive.h +++ b/Include/nmNum/nmSubWxs/nmWxSensitive.h @@ -26,6 +26,7 @@ class SensitivityResultsModel; class iGuiPlot; class QMenu; class QAction; +struct nmSensitivityFitSettings; class NM_SUB_WXS_EXPORT nmWxSensitivityHistogramOptions : public iDlgBase { @@ -114,6 +115,7 @@ protected: void onGenerateClicked(); void onCancelClicked(); void onCalculationTypeChanged(int index); + void onImproveSettingsClicked(); void onTreeItemClicked(QTreeWidgetItem *item, int column); void onVariableParamChanged(); void onSamplingEditingFinished(); @@ -145,6 +147,8 @@ private: // UI 控件 QComboBox *calculationTypeCombo; + QPushButton *m_improveSettingsButton; + QSharedPointer m_improveSettings; QTreeWidget *variablesTree; QStackedWidget *detailStack; QLabel *modelCountLabel; diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 3ffa3c27..3a8c92aa 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -79,11 +79,14 @@ void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, QCheckBox* checkBox = qobject_cast(table->cellWidget(row, 1)); if(checkBox) { - checkBox->setEnabled(visible); - if(!visible) { + // 与当前采样重叠的参数不能参与拟合,切换目标井时也保持互斥。 + const QString samplingNames[] = {"k", "Skin", "C", QString(QChar(0x03c6))}; + const bool sampled = m_sensitivitySettings && row < 4 && m_sampledParameters.contains(samplingNames[row]); + checkBox->setEnabled(visible && !sampled); + if(!visible || sampled) { checkBox->setChecked(false); } else if(wasHidden) { - checkBox->setChecked(true); + checkBox->setChecked(!m_sensitivitySettings); } } } @@ -408,8 +411,11 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par return true; } -nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) +nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSettings* settings, + const QStringList& sampledParameters) : iDlgBase(parent) + , m_sensitivitySettings(settings) + , m_sampledParameters(sampledParameters) , m_autoFitterPSO(nullptr) , m_autoFitterLM(nullptr) , m_progressDialog(nullptr) @@ -429,6 +435,8 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) m_autoParameterRanges = true; setupUI(); + // 敏感性入口使用“确定”保存设置,普通入口仍直接开始拟合。 + if(m_sensitivitySettings) m_okBtn->setText(tr("OK")); setWindowTitle(tr("Automatic fitting")); setModal(true); // 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。 @@ -439,11 +447,39 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) } // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 initializeSuggestedParameterRanges(); + if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings(); m_updatingParameterRanges = false; DEBUG_UI("AutoFitting Constructor completed"); } +void nmWxAutomaticFitting::loadSensitivitySettings() +{ + // 先恢复目标井,再覆盖默认范围与初值,保留上次确认的完整设置。 + m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); + automaticFittingData = m_sensitivitySettings->parameters; + m_autoParameterRanges = false; + m_algorithmCombo->setCurrentIndex(automaticFittingData.getAlgorithmName() == "LM" ? 1 : 0); + m_iterationEdit->setText(QString::number(automaticFittingData.getIterationCount().getValue().toInt())); + m_errorLimitEdit->setText(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble(), 'g', 10)); + const int rows[] = {0, 1, 2, 3, 5, 6}; + QCheckBox* checks[] = {m_kCheckBox, m_sCheckBox, m_cCheckBox, m_phiCheckBox, m_dfcCheckBox, m_fractureHalfLengthCheckBox}; + const bool selected[] = {automaticFittingData.getPermeabilitySelected(), automaticFittingData.getSkinSelected(), + automaticFittingData.getWellboreStorageSelected(), automaticFittingData.getPorositySelected(), + automaticFittingData.getFractureConductivitySelected(), automaticFittingData.getFractureHalfLengthSelected()}; + nmDataAttribute* lower[] = {&automaticFittingData.getPermeabilityMin(), &automaticFittingData.getSkinMin(), + &automaticFittingData.getWellboreStorageMin(), &automaticFittingData.getPorosityMin(), + &automaticFittingData.getFractureConductivityMin(), &automaticFittingData.getFractureHalfLengthMin()}; + nmDataAttribute* upper[] = {&automaticFittingData.getPermeabilityMax(), &automaticFittingData.getSkinMax(), + &automaticFittingData.getWellboreStorageMax(), &automaticFittingData.getPorosityMax(), + &automaticFittingData.getFractureConductivityMax(), &automaticFittingData.getFractureHalfLengthMax()}; + for(int i = 0; i < 6; ++i) { + checks[i]->setChecked(selected[i] && checks[i]->isEnabled()); + setParameterRange(rows[i], lower[i]->getValue().toDouble(), upper[i]->getValue().toDouble()); + m_parameterTable->item(rows[i], 3)->setText(QString::number(m_sensitivitySettings->initialValues[i], 'g', 10)); + } +} + QSize nmWxAutomaticFitting::sizeHint() const { // 沿用项目的可用屏幕尺寸计算方式,优先取父窗口所在屏幕,支持多显示器。 @@ -525,7 +561,7 @@ void nmWxAutomaticFitting::setupUI() void nmWxAutomaticFitting::setupParameterTable() { - // 每次打开时默认勾选全部参数,再按井型隐藏不可用参数。 + // 敏感性设置首次打开时不预选参数,普通拟合仍默认全选,再按井型隐藏不可用参数。 m_parameterTable = new QTableWidget(7, 6, this); // 设置表头 @@ -564,7 +600,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 渗透率 (Permeability) m_parameterTable->setItem(0, 0, new QTableWidgetItem("1")); m_kCheckBox = new QCheckBox(tr("Permeability")); - m_kCheckBox->setChecked(true); + m_kCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(0, 1, m_kCheckBox); m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble()))); m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble()))); @@ -574,7 +610,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 表皮系数 (Skin) m_parameterTable->setItem(1, 0, new QTableWidgetItem("2")); m_sCheckBox = new QCheckBox(tr("Skin")); - m_sCheckBox->setChecked(true); + m_sCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(1, 1, m_sCheckBox); m_parameterTable->setItem(1, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMin().getValue().toDouble()))); m_parameterTable->setItem(1, 3, new QTableWidgetItem()); @@ -584,7 +620,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 井筒储集系数 (Wellbore storage) m_parameterTable->setItem(2, 0, new QTableWidgetItem("3")); m_cCheckBox = new QCheckBox(tr("Wellbore storage")); - m_cCheckBox->setChecked(true); + m_cCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(2, 1, m_cCheckBox); m_parameterTable->setItem(2, 2, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMin().getValue().toDouble()))); m_parameterTable->setItem(2, 3, new QTableWidgetItem()); @@ -594,7 +630,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 孔隙度 (Porosity) m_parameterTable->setItem(3, 0, new QTableWidgetItem("4")); m_phiCheckBox = new QCheckBox(tr("Porosity")); - m_phiCheckBox->setChecked(true); + m_phiCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(3, 1, m_phiCheckBox); m_parameterTable->setItem(3, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMin().getValue().toDouble()))); m_parameterTable->setItem(3, 3, new QTableWidgetItem(QString::number(reservoirData.getPorosity().getValue().toDouble()))); @@ -607,7 +643,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。 m_parameterTable->setItem(5, 0, new QTableWidgetItem("6")); m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity")); - m_dfcCheckBox->setChecked(true); + m_dfcCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox); m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble()))); m_parameterTable->setItem(5, 3, new QTableWidgetItem()); @@ -617,7 +653,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。 m_parameterTable->setItem(6, 0, new QTableWidgetItem("7")); m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length")); - m_fractureHalfLengthCheckBox->setChecked(true); + m_fractureHalfLengthCheckBox->setChecked(!m_sensitivitySettings); m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox); m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble()))); m_parameterTable->setItem(6, 3, new QTableWidgetItem()); @@ -799,17 +835,17 @@ void nmWxAutomaticFitting::onCellSelectionChanged(int currentRow, int currentCol void nmWxAutomaticFitting::onReverseSelection() { - // 更新反选逻辑,包含所有参数 + // 只反选可用参数,不能通过反选勾上已参与采样的参数。 if(!m_parameterTable) { return; } - if(!m_parameterTable->isRowHidden(0)) m_kCheckBox->setChecked(!m_kCheckBox->isChecked()); - if(!m_parameterTable->isRowHidden(1)) m_sCheckBox->setChecked(!m_sCheckBox->isChecked()); - if(!m_parameterTable->isRowHidden(2)) m_cCheckBox->setChecked(!m_cCheckBox->isChecked()); - if(!m_parameterTable->isRowHidden(3)) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked()); - if(!m_parameterTable->isRowHidden(5)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked()); - if(!m_parameterTable->isRowHidden(6)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked()); + if(m_kCheckBox->isEnabled()) m_kCheckBox->setChecked(!m_kCheckBox->isChecked()); + if(m_sCheckBox->isEnabled()) m_sCheckBox->setChecked(!m_sCheckBox->isChecked()); + if(m_cCheckBox->isEnabled()) m_cCheckBox->setChecked(!m_cCheckBox->isChecked()); + if(m_phiCheckBox->isEnabled()) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked()); + if(m_dfcCheckBox->isEnabled()) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked()); + if(m_fractureHalfLengthCheckBox->isEnabled()) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked()); } void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) @@ -843,6 +879,18 @@ void nmWxAutomaticFitting::onAccept() // 校验通过后再保存参数设置,非法输入不会被静默裁剪。 setAutomaticFittingValue(); + if(m_sensitivitySettings) { + // 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。 + m_sensitivitySettings->parameters = automaticFittingData; + m_sensitivitySettings->targetWell = m_targetWellCombo->currentText(); + m_sensitivitySettings->initialValues.clear(); + const int rows[] = {0, 1, 2, 3, 5, 6}; + for(int i = 0; i < 6; ++i) + m_sensitivitySettings->initialValues.append(m_parameterTable->item(rows[i], 3)->text().toDouble()); + m_sensitivitySettings->confirmed = true; + accept(); + return; + } // 更新完成后,通知参数界面刷新 nmWxParameterProperty::notifyUpdateTable(); @@ -1035,6 +1083,11 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() // 保存迭代参数 automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); + // 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。 + if(m_sensitivitySettings) { + automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO"); + return; + } // 只写回可拟合的渗透率、孔隙度,含水饱和度沿用模型当前值。 reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率 diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index 0a15d057..acf0c22d 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -1,6 +1,7 @@ #include "nmWxSensitive.h" #include "nmDataAnalyzeManager.h" +#include "nmWxAutomaticFitting.h" #include "nmDataSensitive.h" #include "nmDataReservoir.h" #include "nmDataWellBase.h" @@ -1467,7 +1468,7 @@ void nmWxSensitive::updateModelCount() // 勾选、数量和方法修改后立即更新总次数,不等待 Generate。 if (m_loading) return; m_sensitiveData.setCalculationType( - static_cast(calculationTypeCombo->currentIndex())); + static_cast(calculationTypeCombo->itemData(calculationTypeCombo->currentIndex()).toInt())); for (int group = 0; group < variablesTree->topLevelItemCount(); ++group) { QTreeWidgetItem* parent = variablesTree->topLevelItem(group); for (int child = 0; child < parent->childCount(); ++child) { @@ -1490,7 +1491,9 @@ void nmWxSensitive::updateModelCount() const bool monteCarlo = m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO; modelCountLabel->setVisible(!monteCarlo); m_monteCarloCount->setVisible(monteCarlo); - generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning); + // 第四种确认拟合设置后才开放生成,其他方法沿用原有条件。 + generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning && + (calculationTypeCombo->currentIndex() != 3 || !m_improveSettings.isNull())); } bool nmWxSensitive::saveDataFromUI() @@ -1589,13 +1592,19 @@ void nmWxSensitive::setupUI() QHBoxLayout *calcTypeLayout = new QHBoxLayout(); QLabel *calcTypeLabel = new QLabel(tr("Calculation type:"), this); calculationTypeCombo = new QComboBox(this); - calculationTypeCombo->addItem(tr("Deterministic")); - calculationTypeCombo->addItem(tr("Deterministic multivariate")); - calculationTypeCombo->addItem(tr("Monte-Carlo")); - //calculationTypeCombo->addItem(tr("Monte-Carlo + Improve")); + calculationTypeCombo->addItem(tr("Deterministic"), nmDataSensitive::CALC_DETERMINISTIC); + calculationTypeCombo->addItem(tr("Deterministic multivariate"), nmDataSensitive::CALC_DETERMINISTIC_MULTIVARIATE); + calculationTypeCombo->addItem(tr("Monte-Carlo"), nmDataSensitive::CALC_MONTE_CARLO); + // 第四种的采样界面和预览直接复用 Monte-Carlo,计算逻辑后续接入。 + calculationTypeCombo->addItem(tr("Monte-Carlo + Improve"), nmDataSensitive::CALC_MONTE_CARLO); + m_improveSettingsButton = new QPushButton(tr("Improve Settings"), this); + // 按钮与下拉框等高,显示或隐藏时不撑高顶部布局。 + m_improveSettingsButton->setFixedHeight(calculationTypeCombo->sizeHint().height()); + m_improveSettingsButton->hide(); calcTypeLayout->addWidget(calcTypeLabel); calcTypeLayout->addWidget(calculationTypeCombo); + calcTypeLayout->addWidget(m_improveSettingsButton); calcTypeLayout->addStretch(); mainLayout->addLayout(calcTypeLayout); @@ -1717,6 +1726,8 @@ void nmWxSensitive::setupUI() void nmWxSensitive::createConnections() { + // 复用已有拟合窗口作为第四种方法的设置入口。 + connect(m_improveSettingsButton, SIGNAL(clicked()), this, SLOT(onImproveSettingsClicked())); connect(m_monteCarloCount, SIGNAL(valueChanged(int)), this, SLOT(onVariableParamChanged())); connect(generateButton, SIGNAL(clicked()), this, SLOT(onGenerateClicked())); @@ -2108,7 +2119,7 @@ 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; + const bool monteCarlo = calculationTypeCombo->itemData(calculationTypeCombo->currentIndex()).toInt() == nmDataSensitive::CALC_MONTE_CARLO; m_modeCombos[idx]->setVisible(!monteCarlo); m_logChecks[idx]->setVisible(!monteCarlo); m_numLabels[idx]->setVisible(!monteCarlo); @@ -2312,6 +2323,12 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column) void nmWxSensitive::onGenerateClicked() { + // 第四种的批量拟合尚未接入,不能误用普通 Monte-Carlo 生成结果。 + if(calculationTypeCombo->currentIndex() == 3) { + QMessageBox::information(this, tr("Sensitivity"), + tr("Monte-Carlo + Improve calculation is not implemented yet.")); + return; + } if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return; QString error; QScopedPointer task; @@ -2427,13 +2444,30 @@ void nmWxSensitive::onResultsChanged() void nmWxSensitive::onCalculationTypeChanged(int index) { - Q_UNUSED(index); + m_improveSettingsButton->setVisible(index == 3); // 复用现有参数页,切换方法只改变可见控件,不覆盖另一种方法的设置。 if(m_loading) return; for(int i = 0; i < m_numSpins.size(); ++i) updateValuesForPage(i); updateModelCount(); } +void nmWxSensitive::onImproveSettingsClicked() +{ + // 在副本上编辑,只有确定才替换设置;取消保留上次确认的内容。 + nmSensitivityFitSettings settings; + if(!m_improveSettings.isNull()) settings = *m_improveSettings; + // 将分析窗口当前勾选的采样参数传给拟合窗口,用于禁用重叠选项。 + updateModelCount(); + QStringList sampledParameters; + foreach(const nmDataSensitive::VariableSampling& variable, m_sensitiveData.getVariables()) + if(variable.m_enabled) sampledParameters.append(variable.m_varName); + nmWxAutomaticFitting dialog(this, &settings, sampledParameters); + if(dialog.exec() == QDialog::Accepted) { + m_improveSettings = QSharedPointer(new nmSensitivityFitSettings(settings)); + updateModelCount(); + } +} + void nmWxSensitive::onTreeItemClicked(QTreeWidgetItem* item, int column) { Q_UNUSED(column);