From ca59dfcd97cb7b63703b484cbb41a587c9118758 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Mon, 19 Oct 2026 16:31:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(nmNum):=20=E5=AE=8C=E5=96=84=E5=A4=9A?= =?UTF-8?q?=E4=BA=95=E5=8F=82=E6=95=B0=E7=95=8C=E9=9D=A2=E4=B8=8E=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 参数按储层和井分组,采样与拟合同井参数互斥。 - 结果面板按井汇总筛选范围,补齐分组中文翻译。 - 调整拟合窗口字号、行高和输入框,支持全选及取消全选。 --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 183214 -> 183354 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 12 +- Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 37 +- Include/nmNum/nmSubWxs/nmWxSensitive.h | 2 +- Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 1073 ++++++----------- Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp | 21 +- Src/nmNum/nmSubWxs/nmWxSensitive.cpp | 128 +- 7 files changed, 504 insertions(+), 769 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index b99a381433081857ac11d0c7ba49b0c0861dde6f..317704631996df21e361dac3f42db3f7499efaf9 100644 GIT binary patch delta 10103 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 diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 373a9d94..ce42c261 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -4444,6 +4444,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We nmWxAutomaticFitting + + Reservoir parameters + 储层参数 + + + Well parameters + 井参数 + LM sampling: LM 采样方式: @@ -4545,8 +4553,8 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We 目标井 - Reverse Selection - 反选 + Select All + 全选 OK diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 3563d2ac..511eebe0 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -5,11 +5,11 @@ #include #include #include -#include -#include +#include #include #include #include +#include #include #include #include @@ -39,13 +39,11 @@ public: virtual QSize sizeHint() const; private slots: - void onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); - void onReverseSelection(); + void onSelectAll(bool checked); void onAccept(); void onReject(); - void onWellSelected(int index); void onAlgorithmChanged(int index); - void onParameterTableItemChanged(QTableWidgetItem* item); + void onParameterTableItemChanged(QTreeWidgetItem* item, int column); // 自动拟合相关槽函数 void runAutoFitting(); @@ -58,12 +56,13 @@ private: void setupParameterTable(); void setupControlPanel(); void setupButtons(); + void updateSelectAllState(); 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); - void initializeSuggestedParameterRanges(); + void saveModelParameters(); + void initializeModelParameters(bool restoreRanges = true); + void addParameter(QTreeWidgetItem* group, int type, nmDataWellBase* well, double initial); + QVector primaryInitialValues() const; + QStringList selectedParameterLabels(bool units) const; void updateRangeForParameter(int parameterIndex, double centerValue); void setParameterRange(int parameterIndex, double minValue, double maxValue); bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue); @@ -79,25 +78,18 @@ private: // UI组件 QVBoxLayout* m_mainLayout; QGridLayout* m_controlLayout; - QTableWidget* m_parameterTable; + QTreeWidget* m_parameterTable; + QVector m_parameterItems; // 控制面板组件 QLineEdit* m_iterationEdit; QLabel* m_iterationLabel; QLineEdit* m_errorLimitEdit; - QComboBox* m_targetWellCombo; + QString m_targetWellName; QComboBox* m_algorithmCombo; - // 参数复选框 - QCheckBox* m_kCheckBox; // 渗透率 - QCheckBox* m_sCheckBox; // 表皮系数 - QCheckBox* m_cCheckBox; // 井筒储集系数 - QCheckBox* m_phiCheckBox; // 孔隙度 - QCheckBox* m_dfcCheckBox; // 裂缝导流能力 - QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长 - // 按钮 - QPushButton* m_reverseBtn; + QCheckBox* m_selectAllCheckBox; QPushButton* m_okBtn; QPushButton* m_cancelBtn; @@ -112,7 +104,6 @@ private: nmCalculationAutoFitLM* m_autoFitterLM; QProgressDialog* m_progressDialog; QTimer* m_progressTimer; - bool m_autoParameterRanges; bool m_updatingParameterRanges; // 拟合开始界面 diff --git a/Include/nmNum/nmSubWxs/nmWxSensitive.h b/Include/nmNum/nmSubWxs/nmWxSensitive.h index e43c7831..a75774ac 100644 --- a/Include/nmNum/nmSubWxs/nmWxSensitive.h +++ b/Include/nmNum/nmSubWxs/nmWxSensitive.h @@ -147,7 +147,7 @@ private: void refreshModelValues(); void updateModelCount(); void setCalculationRunning(bool running); - int findVariableIndex(const QString& varName); // 查找变量索引 + int findVariableIndex(const nmDataSensitive::VariableSampling& variable); // 查找变量索引 // UI 控件 QComboBox *calculationTypeCombo; diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 471e16e7..2ae046aa 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,29 @@ namespace { +// 树节点和名称、单位列只读,仅允许编辑参数的三个数值列。 +class nmFitParameterDelegate : public QStyledItemDelegate +{ +public: + explicit nmFitParameterDelegate(QObject* parent) : QStyledItemDelegate(parent) {} + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const + { + // 分组和参数统一增高,字号变化时也保留上下留白。 + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), qMax(32, option.fontMetrics.height() + 12))); + return size; + } + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, + const QModelIndex& index) const + { + if(index.column() < 1 || index.column() > 3 + || !index.sibling(index.row(), 0).data(Qt::UserRole).isValid()) + return nullptr; + return QStyledItemDelegate::createEditor(parent, option, index); + } +}; + bool nmAutoFitUiIsFinite(double value) { #ifdef Q_OS_WIN @@ -67,166 +91,28 @@ bool nmAutoFitReadPhysicalRange(const char* parameterName, } -// 隐藏参数取消勾选并禁用;新显示的参数默认勾选,保留已显示参数的手工选择。 -void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, bool visible) -{ - if(!table || row < 0 || row >= table->rowCount()) { - return; - } - - const bool wasHidden = table->isRowHidden(row); - table->setRowHidden(row, !visible); - - QCheckBox* checkBox = qobject_cast(table->cellWidget(row, 1)); - if(checkBox) { - // 与当前采样重叠的参数不能参与拟合,切换目标井时也保持互斥。 - 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(!m_sensitivitySettings); - } - } -} - -// 按当前模型和井型可用的参数行重新整理显示序号。 -void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table) -{ - if(!table) { - return; - } - - int visibleIndex = 1; - for(int row = 0; row < table->rowCount(); ++row) { - if(table->isRowHidden(row)) { - continue; - } - - QTableWidgetItem* item = table->item(row, 0); - if(!item) { - item = new QTableWidgetItem(); - table->setItem(row, 0, item); - } - - item->setText(QString::number(visibleIndex++)); - item->setTextAlignment(Qt::AlignCenter); - } -} - -// 拟合表仅包含可优化参数,按模型和目标井类型控制显示。 -void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType) -{ - if(!table) { - return; - } - - // 裂缝参数直接按井型设置,避免先显示再隐藏造成选择状态重置。 - for(int row = 0; row < 4; ++row) { - setParameterRowVisible(table, row, true); - } - - setParameterRowVisible(table, 4, false); // 原含水饱和度索引保留,不再参与拟合。 - - // Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选, - // 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。 - bool showFractureConductivity = false; - nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); - if(manager && m_targetWellCombo) { - nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->currentText()); - if(targetWell) { - NM_WELL_MODEL wellType = targetWell->getWellType(); - showFractureConductivity = - wellType == NM_WELL_MODEL::Vertical_Fractured_Well || - wellType == NM_WELL_MODEL::Horizontal_Fractured_Well; - } - } - setParameterRowVisible(table, 5, showFractureConductivity); // Dfc - setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长 - - renumberVisibleParameterRows(table); -} - // 获取可拟合参数的系统物理边界,索引 4 不再作为拟合参数。 bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex, - double& minValue, double& maxValue) + double& minValue, double& maxValue) { - static const char* parameterNames[] = { - "Result_K", "Result_W_Skin", "Result_W_C", "Result_phi", - "", "Result_W_Dfc", - "W_FractureHalfLength" - }; - if(parameterIndex < 0 || parameterIndex >= 7 || parameterIndex == 4) { - return false; - } - - if(!nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], minValue, maxValue)) { - return false; - } - - // 自动拟合的表皮搜索下界固定为 0,自动范围和手工输入校验使用同一边界。 - if(parameterIndex == 1) { - minValue = 0.0; - } - - - return true; + // 参数行按实际类型查询边界,不依赖它位于哪口井的分组。 + static const char* names[] = {"Result_K", "Result_W_Skin", "Result_W_C", + "Result_phi", "Result_W_Dfc", "W_FractureHalfLength"}; + const int type = automaticFittingData.getModelParameters()[parameterIndex].type; + if(!nmAutoFitReadPhysicalRange(names[type], minValue, maxValue)) return false; + if(type == 1) minValue = 0.0; + return true; } // 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。 -void nmWxAutomaticFitting::setParameterRange(int parameterIndex, - double minValue, double maxValue) +void nmWxAutomaticFitting::setParameterRange(int parameterIndex, double minValue, double maxValue) { - if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterTable->rowCount() - || !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(maxValue)) { - return; - } - - if(maxValue < minValue) { - qSwap(minValue, maxValue); - } - - const bool wasUpdatingRanges = m_updatingParameterRanges; - m_updatingParameterRanges = true; - - if(m_parameterTable->item(parameterIndex, 2)) { - m_parameterTable->item(parameterIndex, 2)->setText(QString::number(minValue, 'g', 10)); - } - if(m_parameterTable->item(parameterIndex, 4)) { - m_parameterTable->item(parameterIndex, 4)->setText(QString::number(maxValue, 'g', 10)); - } - - switch(parameterIndex) { - case 0: - automaticFittingData.getPermeabilityMin().setValue(minValue); - automaticFittingData.getPermeabilityMax().setValue(maxValue); - break; - case 1: - automaticFittingData.getSkinMin().setValue(minValue); - automaticFittingData.getSkinMax().setValue(maxValue); - break; - case 2: - automaticFittingData.getWellboreStorageMin().setValue(minValue); - automaticFittingData.getWellboreStorageMax().setValue(maxValue); - break; - case 3: - automaticFittingData.getPorosityMin().setValue(minValue); - automaticFittingData.getPorosityMax().setValue(maxValue); - break; - case 5: - automaticFittingData.getFractureConductivityMin().setValue(minValue); - automaticFittingData.getFractureConductivityMax().setValue(maxValue); - break; - case 6: - automaticFittingData.getFractureHalfLengthMin().setValue(minValue); - automaticFittingData.getFractureHalfLengthMax().setValue(maxValue); - break; - default: - break; - } - - m_updatingParameterRanges = wasUpdatingRanges; + // 自动范围写入时不触发尚未完成的行校验。 + const bool updating = m_updatingParameterRanges; + m_updatingParameterRanges = true; + m_parameterItems[parameterIndex]->setText(2, QString::number(minValue, 'g', 10)); + m_parameterItems[parameterIndex]->setText(3, QString::number(maxValue, 'g', 10)); + m_updatingParameterRanges = updating; } // 根据当前初值生成建议搜索范围,并始终截断在拟合边界内。skin 下界为 0, @@ -234,7 +120,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double centerValue) { - if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 7 + if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterItems.size() || !nmAutoFitUiIsFinite(centerValue)) { return; } @@ -246,7 +132,8 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, } double reference = centerValue; - const bool positiveParameter = parameterIndex != 1; + const int type = automaticFittingData.getModelParameters()[parameterIndex].type; + const bool positiveParameter = type != 1; // 正值参数初值为 0 时不再借用旧的界面范围,直接从物理边界选取搜索尺度。 if(positiveParameter && reference <= 0.0) { @@ -265,17 +152,17 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double newMin = physicalMin; double newMax = physicalMax; - if(parameterIndex == 1) { + if(type == 1) { const double skinHalfRange = 10.0; newMin = physicalMin; newMax = qMin(physicalMax, reference + skinHalfRange); } else if(reference > 0.0 - && !(parameterIndex == 5 && centerValue <= 0.0)) { + && !(type == 4 && centerValue <= 0.0)) { const double lowerFactor = 0.1; const double upperFactor = 10.0; newMin = qMax(physicalMin, reference * lowerFactor); newMax = qMin(physicalMax, reference * upperFactor); - } else if(parameterIndex == 5) { + } else if(type == 4) { // Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。 newMin = physicalMin; newMax = physicalMax; @@ -292,81 +179,40 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, } } -// 按当前表格中的初值为所有参数建立建议范围,每次打开窗口和拟合完成后共用。 -void nmWxAutomaticFitting::initializeSuggestedParameterRanges() -{ - if(!m_parameterTable) { - return; - } - - for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) { - QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); - if(initialItem) { - bool initialOk = false; - const double initialValue = initialItem->text().toDouble(&initialOk); - if(initialOk && nmAutoFitUiIsFinite(initialValue)) { - updateRangeForParameter(parameterIndex, initialValue); - } else { - // 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。 - double physicalMin = 0.0; - double physicalMax = 0.0; - if(getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax) - && physicalMax >= physicalMin) { - setParameterRange(parameterIndex, physicalMin, physicalMax); - } - } - } - } -} - -// 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。 +// 校验参数范围;parameterIndex 为 -1 时检查所有可用参数,包括折叠分组。 bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex) { if(!m_parameterTable) { errorMessage = tr("The parameter table is unavailable."); return false; } - if(parameterIndex < -1 || parameterIndex >= 7) { + if(parameterIndex < -1 || parameterIndex >= m_parameterItems.size()) { errorMessage = tr("The parameter row is invalid."); return false; } - static const char* parameterNames[] = { - "Permeability", "Skin", "Wellbore storage", "Porosity", - "", "Fracture conductivity", - "Fracture half length" - }; const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex; - const int lastParameterIndex = parameterIndex < 0 ? 7 : parameterIndex + 1; + const int lastParameterIndex = parameterIndex < 0 ? m_parameterItems.size() : parameterIndex + 1; for(int currentParameterIndex = firstParameterIndex; currentParameterIndex < lastParameterIndex; ++currentParameterIndex) { - // 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。 - if(m_parameterTable->isRowHidden(currentParameterIndex)) { - continue; - } - - QTableWidgetItem* minItem = m_parameterTable->item(currentParameterIndex, 2); - QTableWidgetItem* initialItem = m_parameterTable->item(currentParameterIndex, 3); - QTableWidgetItem* maxItem = m_parameterTable->item(currentParameterIndex, 4); - if(!minItem || !initialItem || !maxItem) { - errorMessage = tr("The range values for %1 are incomplete.") - .arg(tr(parameterNames[currentParameterIndex])); - return false; - } + QTreeWidgetItem* item = m_parameterItems[currentParameterIndex]; + // 已参与采样或当前算法不可用的参数不阻塞设置保存;折叠的分组仍须校验。 + if(item->isDisabled()) continue; + const nmFitParameter& parameter = automaticFittingData.getModelParameters()[currentParameterIndex]; bool minOk = false; bool initialOk = false; bool maxOk = false; - const double minValue = minItem->text().toDouble(&minOk); - const double initialValue = initialItem->text().toDouble(&initialOk); - const double maxValue = maxItem->text().toDouble(&maxOk); + const double minValue = item->text(2).toDouble(&minOk); + const double initialValue = item->text(1).toDouble(&initialOk); + const double maxValue = item->text(3).toDouble(&maxOk); if(!minOk || !initialOk || !maxOk || !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(initialValue) || !nmAutoFitUiIsFinite(maxValue)) { errorMessage = tr("The minimum value, initial value, and maximum value of %1 must be finite numbers.") - .arg(tr(parameterNames[currentParameterIndex])); + .arg(parameter.name()); return false; } @@ -376,7 +222,7 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par || !nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax) || physicalMax < physicalMin) { errorMessage = tr("The physical range of %1 is invalid.") - .arg(tr(parameterNames[currentParameterIndex])); + .arg(parameter.name()); return false; } @@ -387,7 +233,7 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par || (maxValue < physicalMin && !nmAutoFitUiNearlyEqual(maxValue, physicalMin)) || (maxValue > physicalMax && !nmAutoFitUiNearlyEqual(maxValue, physicalMax))) { errorMessage = tr("The values of %1 exceed the physical range [%2, %3].") - .arg(tr(parameterNames[currentParameterIndex])) + .arg(parameter.name()) .arg(QString::number(physicalMin, 'g', 10)) .arg(QString::number(physicalMax, 'g', 10)); return false; @@ -395,15 +241,15 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par if(minValue > maxValue || minValue > initialValue || initialValue > maxValue) { errorMessage = tr("The values of %1 must satisfy: minimum <= initial value <= maximum.") - .arg(tr(parameterNames[currentParameterIndex])); + .arg(parameter.name()); return false; } // 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。 - if(currentParameterIndex == 5 && m_dfcCheckBox->isChecked() + if(parameter.type == 4 && item->checkState(0) == Qt::Checked && (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) { errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.") - .arg(tr(parameterNames[currentParameterIndex])); + .arg(parameter.name()); return false; } } @@ -421,7 +267,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett , m_progressDialog(nullptr) , m_progressTimer(nullptr) , m_progressMonitor(nullptr) - , m_autoParameterRanges(true) // 构造期间先禁止即时校验,避免初始值写入和范围生成之间出现短暂的不一致。 , m_updatingParameterRanges(true) { @@ -431,8 +276,12 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); reservoirData = pManager->getReservoirDataCopy(); automaticFittingData = pManager->getAutomaticFittingDataCopy(); - // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 - m_autoParameterRanges = true; + if(m_sensitivitySettings) { + if(m_sensitivitySettings->confirmed) automaticFittingData = m_sensitivitySettings->parameters; + else automaticFittingData.getModelParameters().clear(); + } + nmDataWellBase* primary = pManager->findWellByCode(pManager->getPrimaryWellCode()); + m_targetWellName = primary ? primary->getWellName() : QString(); setupUI(); // 敏感性入口使用“确定”保存设置,普通入口仍直接开始拟合。 @@ -442,49 +291,13 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett // 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。 resize(sizeHint()); - if(m_targetWellCombo->count() > 0) { - onWellSelected(0); // 默认选中第一口井 - } - // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 - initializeSuggestedParameterRanges(); - if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings(); + initializeModelParameters(); + onAlgorithmChanged(m_algorithmCombo->currentIndex()); m_updatingParameterRanges = false; DEBUG_UI("AutoFitting Constructor completed"); } -void nmWxAutomaticFitting::loadSensitivitySettings() -{ - // 恢复已确认的勾选和拟合设置,初值仍读取当前模型。 - const int targetIndex = m_targetWellCombo->findText(m_sensitivitySettings->targetWell); - if(targetIndex < 0) return; - m_targetWellCombo->setCurrentIndex(targetIndex); - automaticFittingData = m_sensitivitySettings->parameters; - m_autoParameterRanges = false; - // 敏感性设置恢复时也固定使用 LM。 - m_algorithmCombo->setCurrentIndex(1); - 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()); - const double currentValue = m_parameterTable->item(rows[i], 3)->text().toDouble(); - if(currentValue < lower[i]->getValue().toDouble() || currentValue > upper[i]->getValue().toDouble()) - updateRangeForParameter(rows[i], currentValue); - } -} - QSize nmWxAutomaticFitting::sizeHint() const { // 沿用项目的可用屏幕尺寸计算方式,优先取父窗口所在屏幕,支持多显示器。 @@ -529,10 +342,9 @@ void nmWxAutomaticFitting::setupUI() "#autoFittingSettings QLineEdit:focus, #autoFittingSettings QComboBox:focus { border-color: #3978c6; }" "#autoFittingSettings QComboBox::drop-down { border: none; width: 24px; }" "#autoFittingSettings QComboBox QAbstractItemView { background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }" - "#autoFittingSettings QTableWidget { border: none; background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }" + "#autoFittingSettings QTreeWidget { border: none; background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }" "#autoFittingSettings QHeaderView::section { background: #f5f7fa; color: #667487; border: none; border-bottom: 1px solid #e5eaf1; padding: 7px; }" - "#autoFittingSettings QTableWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 8px; }" - "#autoFittingSettings QCheckBox { color: #273449; background: transparent; padding-left: 10px; spacing: 8px; }" + "#autoFittingSettings QTreeWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 8px; }" "#autoFittingSettings QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 4px; padding: 6px 18px; min-width: 64px; min-height: 20px; }" "#autoFittingSettings QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }" "#autoFittingSettings QPushButton:pressed { background: #dce8f8; }" @@ -556,7 +368,7 @@ void nmWxAutomaticFitting::setupUI() tableLayout->setSpacing(10); QLabel* tableHeading = new QLabel(tr("Fitting parameters"), tablePanel); tableHeading->setObjectName("parameterHeading"); - tableLayout->addWidget(tableHeading); + tableLayout->addWidget(tableHeading); tableLayout->addWidget(m_parameterTable, 1); m_mainLayout->addWidget(tablePanel, 1); setupButtons(); @@ -566,146 +378,31 @@ void nmWxAutomaticFitting::setupUI() void nmWxAutomaticFitting::setupParameterTable() { - // 敏感性设置首次打开时不预选参数,普通拟合仍默认全选,再按井型隐藏不可用参数。 - m_parameterTable = new QTableWidget(7, 6, this); - - // 设置表头 - QStringList headers; - headers << "" << tr("Parameter") << tr("Min") << tr("Initial value") << tr("Max") << tr("Unit"); - m_parameterTable->setHorizontalHeaderLabels(headers); - - // 设置表格属性 - m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems); - m_parameterTable->setAlternatingRowColors(false); - m_parameterTable->setShowGrid(false); - m_parameterTable->verticalHeader()->setDefaultSectionSize(36); - m_parameterTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); - m_parameterTable->verticalHeader()->setVisible(false); - - // 设置列宽 - QHeaderView* header = m_parameterTable->horizontalHeader(); - header->setResizeMode(0, QHeaderView::Fixed); // 序号列固定宽度 - header->setResizeMode(1, QHeaderView::Fixed); // 参数列固定宽度 - header->setResizeMode(2, QHeaderView::Stretch); // 其他列自适应 - header->setResizeMode(3, QHeaderView::Stretch); - header->setResizeMode(4, QHeaderView::Stretch); - header->setResizeMode(5, QHeaderView::Fixed); // 单位列固定宽度 - - // 设置固定列的宽度 - m_parameterTable->setColumnWidth(0, 40); // 序号列宽度 - m_parameterTable->setColumnWidth(1, 180); // 参数名称与复选框保留足够空间 - m_parameterTable->setColumnWidth(5, 80); // 单位列宽度 - // 只改变视觉顺序,保留数据读写使用的原列索引。 - m_parameterTable->setColumnHidden(0, true); - header->moveSection(header->visualIndex(3), header->visualIndex(2)); - - // 设置表格的大小策略 - m_parameterTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - // 渗透率 (Permeability) - m_parameterTable->setItem(0, 0, new QTableWidgetItem("1")); - m_kCheckBox = new QCheckBox(tr("Permeability")); - 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()))); - m_parameterTable->setItem(0, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMax().getValue().toDouble()))); - m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("mD"))); - - // 表皮系数 (Skin) - m_parameterTable->setItem(1, 0, new QTableWidgetItem("2")); - m_sCheckBox = new QCheckBox(tr("Skin")); - 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()); - m_parameterTable->setItem(1, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMax().getValue().toDouble()))); - m_parameterTable->setItem(1, 5, new QTableWidgetItem("")); - - // 井筒储集系数 (Wellbore storage) - m_parameterTable->setItem(2, 0, new QTableWidgetItem("3")); - m_cCheckBox = new QCheckBox(tr("Wellbore storage")); - 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()); - m_parameterTable->setItem(2, 4, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMax().getValue().toDouble()))); - m_parameterTable->setItem(2, 5, new QTableWidgetItem(tr("m^3/MPa"))); - - // 孔隙度 (Porosity) - m_parameterTable->setItem(3, 0, new QTableWidgetItem("4")); - m_phiCheckBox = new QCheckBox(tr("Porosity")); - 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()))); - m_parameterTable->setItem(3, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMax().getValue().toDouble()))); - m_parameterTable->setItem(3, 5, new QTableWidgetItem("")); - - // 索引 4 留空,保持裂缝参数与优化器的现有索引一致。 - - // 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中 - // 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。 - m_parameterTable->setItem(5, 0, new QTableWidgetItem("6")); - m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity")); - 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()); - m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble()))); - m_parameterTable->setItem(5, 5, new QTableWidgetItem(tr("md.m"))); - - // 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。 - m_parameterTable->setItem(6, 0, new QTableWidgetItem("7")); - m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length")); - 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()); - m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble()))); - m_parameterTable->setItem(6, 5, new QTableWidgetItem(tr("m"))); - - // 设置表格行为 - for(int i = 0; i < m_parameterTable->rowCount(); ++i) { - for(int j = 0; j < 6; ++j) { - QTableWidgetItem* item = m_parameterTable->item(i, j); - - if(item) { - if(j == 0 || j == 5) { // 序号列和单位列只读 - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - } else if(j >= 2) { - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable); - } else { // 参数列(复选框)不可选择 - item->setFlags(Qt::NoItemFlags); - } - } - } - } - - // 序号列居中对齐 - for(int i = 0; i < m_parameterTable->rowCount(); ++i) { - QTableWidgetItem* item = m_parameterTable->item(i, 0); - - if(item) { - item->setTextAlignment(Qt::AlignCenter); - } - } - - nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); - if(pManager) { - updateParameterVisibility(m_parameterTable, pManager->getSolverModelType()); - } - - // 连接选择改变信号 - connect(m_parameterTable, SIGNAL(currentCellChanged(int, int, int, int)), - this, SLOT(onCellSelectionChanged(int, int, int, int))); - connect(m_parameterTable, SIGNAL(itemChanged(QTableWidgetItem*)), - this, SLOT(onParameterTableItemChanged(QTableWidgetItem*))); + // 用可折叠分组直接展示各井参数,数值列沿用原来的编辑方式。 + m_parameterTable = new QTreeWidget(this); + m_parameterTable->setFont(QFont("Microsoft YaHei", 10)); + m_parameterTable->setColumnCount(5); + m_parameterTable->setHeaderLabels(QStringList() << tr("Parameter") << tr("Initial value") + << tr("Min") << tr("Max") << tr("Unit")); + m_parameterTable->setItemDelegate(new nmFitParameterDelegate(m_parameterTable)); + m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems); + m_parameterTable->setUniformRowHeights(true); + m_parameterTable->setIndentation(18); + QHeaderView* header = m_parameterTable->header(); + header->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + header->setStretchLastSection(false); + header->setResizeMode(0, QHeaderView::Interactive); + m_parameterTable->setColumnWidth(0, 230); + for(int column = 1; column <= 3; ++column) header->setResizeMode(column, QHeaderView::Stretch); + header->setResizeMode(4, QHeaderView::Fixed); + m_parameterTable->setColumnWidth(4, 120); + connect(m_parameterTable, SIGNAL(itemChanged(QTreeWidgetItem*, int)), + this, SLOT(onParameterTableItemChanged(QTreeWidgetItem*, int))); } void nmWxAutomaticFitting::setupControlPanel() { - // 四列设置:目标井、算法、目标误差、迭代上限。 + // 上方仅保留算法、目标误差和迭代上限。 m_controlLayout = new QGridLayout(); m_controlLayout->setHorizontalSpacing(16); m_controlLayout->setVerticalSpacing(6); @@ -716,7 +413,8 @@ void nmWxAutomaticFitting::setupControlPanel() m_algorithmCombo->addItem(tr("PSO (Particle Swarm)")); m_algorithmCombo->addItem(tr("LM")); // 从敏感性分析打开时仅使用 LM,普通自动拟合仍可选择算法。 - m_algorithmCombo->setCurrentIndex(m_sensitivitySettings ? 1 : 0); + m_algorithmCombo->setCurrentIndex(m_sensitivitySettings + || nmDataAnalyzeManager::getCurrentInstance()->getEffectiveCalculationWells().size() > 1 ? 1 : 0); m_algorithmCombo->setEnabled(!m_sensitivitySettings); connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int))); @@ -729,74 +427,56 @@ void nmWxAutomaticFitting::setupControlPanel() QLabel* errorLabel = new QLabel(tr("Target error:")); m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble())); - // 目标井选择 - QLabel* wellLabel = new QLabel(tr("Target Well:")); - m_targetWellCombo = new QComboBox(); - - // 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。 - nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); - if(pManager != nullptr) { - const QString sPrimaryWellCode = pManager->getPrimaryWellCode(); - nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode); - if(pPrimaryWell != nullptr) { - m_targetWellCombo->addItem(pPrimaryWell->getWellName()); - } - - if(pManager->getIncludeOtherWells()) { - const QVector vecIncludedWells = - pManager->getIncludedCalculationWells(); - for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) { - const QString& sWellCode = vecIncludedWells[nIndex].m_sWellCode; - nmDataWellBase* pWellData = pManager->findWellByCode(sWellCode); - if(pWellData == nullptr || sWellCode == sPrimaryWellCode || - pWellData->getFlowSegmentCount() <= 0 || - pWellData->getPressurePoints().size() < 2) { - continue; - } - m_targetWellCombo->addItem(pWellData->getWellName()); - } - } - } - connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int))); - m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); - m_targetWellCombo->setMinimumContentsLength(10); - m_controlLayout->addWidget(wellLabel, 0, 0); - m_controlLayout->addWidget(m_targetWellCombo, 1, 0); - m_controlLayout->addWidget(algorithmLabel, 0, 1); - m_controlLayout->addWidget(m_algorithmCombo, 1, 1); - m_controlLayout->addWidget(errorLabel, 0, 2); - m_controlLayout->addWidget(m_errorLimitEdit, 1, 2); - m_controlLayout->addWidget(m_iterationLabel, 0, 3); - m_controlLayout->addWidget(m_iterationEdit, 1, 3); - for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1); - onAlgorithmChanged(m_algorithmCombo->currentIndex()); + m_controlLayout->addWidget(algorithmLabel, 0, 0); + m_controlLayout->addWidget(m_algorithmCombo, 1, 0); + m_controlLayout->addWidget(errorLabel, 0, 1); + m_controlLayout->addWidget(m_errorLimitEdit, 1, 1); + m_controlLayout->addWidget(m_iterationLabel, 0, 2); + m_controlLayout->addWidget(m_iterationEdit, 1, 2); + // 右侧预留一列空白,三个输入框不再铺满整行。 + for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1); } void nmWxAutomaticFitting::onAlgorithmChanged(int index) { - // 两种算法统一显示最大迭代次数,提示仍说明 LM 共用次数的含义。 - const bool usePSO = (index == 0); - m_iterationLabel->setText(tr("Maximum iterations:")); - m_iterationEdit->setToolTip(usePSO ? QString() : tr("Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times.")); + // 多井联合拟合使用 LM;PSO 仍只开放主井和共享储层参数。 + const bool usePSO = index == 0; + const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode(); + const bool updating = m_updatingParameterRanges; + m_updatingParameterRanges = true; + const QVector& parameters = automaticFittingData.getModelParameters(); + for(int i = 0; i < parameters.size(); ++i) { + const nmFitParameter& p = parameters[i]; + nmDataWellBase* well = p.wellCode.isEmpty() ? nullptr + : nmDataAnalyzeManager::getCurrentInstance()->findWellByCode(p.wellCode); + const bool unavailable = (m_sensitivitySettings && m_sampledParameters.contains(p.samplingKey())) + || (usePSO && !p.wellCode.isEmpty() && p.wellCode != primaryCode) + || (p.type == 1 && (!well || well->getPerforationCount() == 0)); + m_parameterItems[i]->setDisabled(unavailable); + if(unavailable) m_parameterItems[i]->setCheckState(0, Qt::Unchecked); + } + m_updatingParameterRanges = updating; + updateSelectAllState(); + m_iterationLabel->setText(tr("Maximum iterations:")); + m_iterationEdit->setToolTip(usePSO ? QString() : tr("Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times.")); } void nmWxAutomaticFitting::setupButtons() { QHBoxLayout* buttonLayout = new QHBoxLayout(); - m_reverseBtn = new QPushButton(tr("Reverse Selection")); + m_selectAllCheckBox = new QCheckBox(tr("Select All")); m_okBtn = new QPushButton(tr("Start fitting")); m_okBtn->setObjectName("startFitting"); m_okBtn->setDefault(true); m_cancelBtn = new QPushButton(tr("Cancel")); // 设置按钮大小 - m_reverseBtn->setMinimumWidth(120); m_okBtn->setMinimumWidth(80); m_cancelBtn->setMinimumWidth(80); - buttonLayout->addWidget(m_reverseBtn); + buttonLayout->addWidget(m_selectAllCheckBox); buttonLayout->addStretch(); buttonLayout->addWidget(m_cancelBtn); buttonLayout->addWidget(m_okBtn); @@ -804,76 +484,48 @@ void nmWxAutomaticFitting::setupButtons() m_mainLayout->addLayout(buttonLayout); // 连接信号槽 - connect(m_reverseBtn, SIGNAL(clicked()), this, SLOT(onReverseSelection())); + connect(m_selectAllCheckBox, SIGNAL(clicked(bool)), this, SLOT(onSelectAll(bool))); connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onAccept())); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onReject())); } -void nmWxAutomaticFitting::onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) +void nmWxAutomaticFitting::onSelectAll(bool checked) { - if(!m_parameterTable) return; - - QHeaderView* header = m_parameterTable->horizontalHeader(); - if(!header) return; - - // 重置所有列头的字体为正常 - - for(int i = 0; i < m_parameterTable->columnCount(); ++i) { - QFont font = header->font(); - font.setBold(false); - if(m_parameterTable->horizontalHeaderItem(i)) { - m_parameterTable->horizontalHeaderItem(i)->setFont(font); - } - } - - // 设置当前列的表头为粗体 - - if(currentColumn >= 0 && currentColumn < m_parameterTable->columnCount()) { - QFont font = header->font(); - font.setBold(true); - - if(m_parameterTable->horizontalHeaderItem(currentColumn)) { - m_parameterTable->horizontalHeaderItem(currentColumn)->setFont(font); - } - } - - header->update(); + // 批量切换可用参数,完成后统一同步全选框,避免逐项更新时改变点击状态。 + const bool updating = m_updatingParameterRanges; + m_updatingParameterRanges = true; + for(int i = 0; i < m_parameterItems.size(); ++i) { + QTreeWidgetItem* item = m_parameterItems[i]; + if(!item->isDisabled()) + item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + } + m_updatingParameterRanges = updating; + updateSelectAllState(); } -void nmWxAutomaticFitting::onReverseSelection() +void nmWxAutomaticFitting::updateSelectAllState() { - // 只反选可用参数,不能通过反选勾上已参与采样的参数。 - if(!m_parameterTable) { - return; - } - - 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()); + // 仅统计可用参数;单项勾选和算法切换后,全选框都与当前列表保持一致。 + int available = 0; + int selected = 0; + for(int i = 0; i < m_parameterItems.size(); ++i) { + if(m_parameterItems[i]->isDisabled()) continue; + ++available; + if(m_parameterItems[i]->checkState(0) == Qt::Checked) ++selected; + } + m_selectAllCheckBox->setEnabled(available > 0); + m_selectAllCheckBox->setChecked(available > 0 && selected == available); } -void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) +void nmWxAutomaticFitting::onParameterTableItemChanged(QTreeWidgetItem* item, int column) { - if(!item || m_updatingParameterRanges) { - return; - } - - // 手工范围用于本轮拟合,切井时保留;拟合完成后再按最新参数自动生成范围。 - if(item->column() == 2 || item->column() == 4) { - m_autoParameterRanges = false; - } - - if(item->column() >= 2 && item->column() <= 4 - && !m_parameterTable->isRowHidden(item->row())) { - QString validationError; - if(!validateParameterTable(validationError, item->row())) { - // 表格编辑提交后立即提示,用户不需要先点击“确定”才发现错误。 - QMessageBox::warning(this, tr("Invalid parameter range"), validationError); - } - } + // 勾选时同步全选框,数值编辑仍沿用原来的范围校验。 + if(!m_updatingParameterRanges && column == 0) updateSelectAllState(); + if(m_updatingParameterRanges || column < 1 || column > 3 + || !item->data(0, Qt::UserRole).isValid()) return; + QString error; + if(!validateParameterTable(error, item->data(0, Qt::UserRole).toInt())) + QMessageBox::warning(this, tr("Invalid parameter range"), error); } void nmWxAutomaticFitting::onAccept() @@ -885,15 +537,13 @@ void nmWxAutomaticFitting::onAccept() } // 校验通过后再保存参数设置,非法输入不会被静默裁剪。 - setAutomaticFittingValue(); + saveModelParameters(); + 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->targetWell = m_targetWellName; + m_sensitivitySettings->initialValues = primaryInitialValues(); m_sensitivitySettings->confirmed = true; accept(); return; @@ -902,7 +552,7 @@ void nmWxAutomaticFitting::onAccept() nmWxParameterProperty::notifyUpdateTable(); // 获取目标井的双对数历史数据 - QString selectedWellName = m_targetWellCombo->currentText(); + QString selectedWellName = m_targetWellName; if(selectedWellName.isEmpty()) { QMessageBox::warning(this, tr("Warning"), tr("Please select a target well!")); @@ -970,25 +620,14 @@ void nmWxAutomaticFitting::onAccept() } // 检查是否有参数被选中 - bool hasSelectedParams = m_kCheckBox->isChecked() || m_sCheckBox->isChecked() || - m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() || - m_dfcCheckBox->isChecked() || - m_fractureHalfLengthCheckBox->isChecked(); + bool hasSelectedParams = automaticFittingData.hasSelectedParameters(); if(!hasSelectedParams) { QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!")); return; } - // 收集选中的参数名称 - QStringList selectedParameterNames; - - if(m_kCheckBox->isChecked()) selectedParameterNames << tr("Permeability"); - if(m_sCheckBox->isChecked()) selectedParameterNames << tr("Skin"); - if(m_cCheckBox->isChecked()) selectedParameterNames << tr("Wellbore storage"); - if(m_phiCheckBox->isChecked()) selectedParameterNames << tr("Porosity"); - if(m_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity"); - if(m_fractureHalfLengthCheckBox->isChecked()) selectedParameterNames << tr("Fracture half length"); + const QStringList selectedParameterNames = selectedParameterLabels(false); // 启动自动拟合 - 传递双对数历史数据 startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName); @@ -998,121 +637,83 @@ void nmWxAutomaticFitting::onReject() reject(); } -void nmWxAutomaticFitting::onWellSelected(int index) -{ - // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。 - nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); - if(!manager) { - return; - } - nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index)); - if(!targetWell) { - return; - } - - const bool wasUpdatingRanges = m_updatingParameterRanges; - m_updatingParameterRanges = true; - updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); - - nmDataPerforation* perforation = targetWell->getPerforation(0); - if(perforation) { - const double skin = perforation->getSkin().getValue().toDouble(); - m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10)); - if(m_autoParameterRanges) { - updateRangeForParameter(1, skin); - } - } - const double storage = targetWell->getWellboreStorage().getValue().toDouble(); - m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10)); - if(m_autoParameterRanges) { - updateRangeForParameter(2, storage); - } - - bool fracturedWell = false; - double conductivity = 0.0; - double halfLength = 0.0; - if(nmDataVerticalFracturedWell* well = dynamic_cast(targetWell)) { - conductivity = well->getDfc().getValue().toDouble(); - halfLength = well->getFractureHalfLength().getValue().toDouble(); - fracturedWell = true; - } else if(nmDataHorizontalFracturedWell* well = dynamic_cast(targetWell)) { - conductivity = well->getDfc().getValue().toDouble(); - halfLength = well->getFractureHalfLength().getValue().toDouble(); - fracturedWell = true; - } - if(fracturedWell) { - m_parameterTable->item(5, 3)->setText(QString::number(conductivity, 'g', 10)); - m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10)); - if(m_autoParameterRanges) { - updateRangeForParameter(5, conductivity); - updateRangeForParameter(6, halfLength); - } - } - m_updatingParameterRanges = wasUpdatingRanges; -} - void nmWxAutomaticFitting::setAutomaticFittingValue() { - // 保存剩余拟合参数的选择状态,LM 和 PSO 共用此配置。 - automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked()); - automaticFittingData.setSkinSelected(m_sCheckBox->isChecked()); - automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked()); - automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked()); - automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked()); - automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked()); - // 界面不再提供 PSO 加速选项,同时关闭旧配置中可能保留的加速开关。 - automaticFittingData.setSurrogateScreeningEnabled(false); - - // 保存渗透率的最小值和最大值 - automaticFittingData.getPermeabilityMin().setValue(m_parameterTable->item(0, 2)->text().toDouble()); - automaticFittingData.getPermeabilityMax().setValue(m_parameterTable->item(0, 4)->text().toDouble()); - - // 保存表皮系数的最小值和最大值 - automaticFittingData.getSkinMin().setValue(m_parameterTable->item(1, 2)->text().toDouble()); - automaticFittingData.getSkinMax().setValue(m_parameterTable->item(1, 4)->text().toDouble()); - - // 保存井筒储集系数的最小值和最大值 - automaticFittingData.getWellboreStorageMin().setValue(m_parameterTable->item(2, 2)->text().toDouble()); - automaticFittingData.getWellboreStorageMax().setValue(m_parameterTable->item(2, 4)->text().toDouble()); - - // 保存孔隙度的最小值和最大值 - automaticFittingData.getPorosityMin().setValue(m_parameterTable->item(3, 2)->text().toDouble()); - automaticFittingData.getPorosityMax().setValue(m_parameterTable->item(3, 4)->text().toDouble()); - - // 保存裂缝导流能力的最小值和最大值 - automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(5, 2)->text().toDouble()); - automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(5, 4)->text().toDouble()); - - // 保存裂缝半长的最小值和最大值 - automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(6, 2)->text().toDouble()); - automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(6, 4)->text().toDouble()); - - // 保存迭代参数 - automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); - automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); - // 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。 - if(m_sensitivitySettings) { - automaticFittingData.setAlgorithmName("LM"); + // 单井旧字段继续供 PSO 和旧配置读取,LM 直接使用完整分组参数列表。 + automaticFittingData.setPermeabilitySelected(false); + automaticFittingData.setSkinSelected(false); + automaticFittingData.setWellboreStorageSelected(false); + automaticFittingData.setPorositySelected(false); + automaticFittingData.setFractureConductivitySelected(false); + automaticFittingData.setFractureHalfLengthSelected(false); + const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode(); + const QVector& parameters = automaticFittingData.getModelParameters(); + for(int i = 0; i < parameters.size(); ++i) { + const nmFitParameter& p = parameters[i]; + if(!p.wellCode.isEmpty() && p.wellCode != primaryCode) continue; + switch(p.type) { + case 0: + automaticFittingData.setPermeabilitySelected(p.selected); + automaticFittingData.getPermeabilityMin().setValue(p.lower); + automaticFittingData.getPermeabilityMax().setValue(p.upper); + break; + case 1: + automaticFittingData.setSkinSelected(p.selected); + automaticFittingData.getSkinMin().setValue(p.lower); + automaticFittingData.getSkinMax().setValue(p.upper); + break; + case 2: + automaticFittingData.setWellboreStorageSelected(p.selected); + automaticFittingData.getWellboreStorageMin().setValue(p.lower); + automaticFittingData.getWellboreStorageMax().setValue(p.upper); + break; + case 3: + automaticFittingData.setPorositySelected(p.selected); + automaticFittingData.getPorosityMin().setValue(p.lower); + automaticFittingData.getPorosityMax().setValue(p.upper); + break; + case 4: + automaticFittingData.setFractureConductivitySelected(p.selected); + automaticFittingData.getFractureConductivityMin().setValue(p.lower); + automaticFittingData.getFractureConductivityMax().setValue(p.upper); + break; + case 5: + automaticFittingData.setFractureHalfLengthSelected(p.selected); + automaticFittingData.getFractureHalfLengthMin().setValue(p.lower); + automaticFittingData.getFractureHalfLengthMax().setValue(p.upper); + break; + } + } + automaticFittingData.setSurrogateScreeningEnabled(false); + automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); + automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); + automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO"); + // 敏感性入口只保存设置;普通 LM 在启动时按井应用初值。 + if(m_sensitivitySettings) return; + if(m_algorithmCombo->currentIndex() == 1) { + nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData); return; } - + const QVector initial = primaryInitialValues(); // 只写回可拟合的渗透率、孔隙度,含水饱和度沿用模型当前值。 - reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率 - reservoirData.getPorosity().setValue(m_parameterTable->item(3, 3)->text().toDouble()); // 孔隙度 + reservoirData.getPermeability().setValue(initial[0]); // 渗透率 + reservoirData.getPorosity().setValue(initial[3]); // 孔隙度 // 更新储层数据(全局) nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData); // 保存自动拟合数据 - nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData); + nmDataAutomaticFitting psoSettings = automaticFittingData; + psoSettings.getModelParameters().clear(); + nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(psoSettings); // 只更新目标井的参数 - QString selectedWellName = m_targetWellCombo->currentText(); + QString selectedWellName = m_targetWellName; if(!selectedWellName.isEmpty()) { - double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble(); - double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble(); - double newFractureConductivityValue = m_parameterTable->item(5, 3)->text().toDouble(); - double newFractureHalfLengthValue = m_parameterTable->item(6, 3)->text().toDouble(); + double newSkinValue = initial[1]; + double newWellboreStorageValue = initial[2]; + double newFractureConductivityValue = initial[4]; + double newFractureHalfLengthValue = initial[5]; // 直接从数据管理器获取目标井 nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1185,39 +786,6 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector>& targ m_autoFitterPSO->setPSOTargetWellName(targetWellName); } - //// 特定井名时使用快速路径 - //if (targetWellName == "VerticalWell1") { - - // m_autoFitterPSO->setSimulationMode(true); - - // // 构建目标参数向量(按启用参数的顺序) - // QVector targetParams; - - // if(m_kCheckBox->isChecked()) targetParams.append(0.0033); // 渗透率 - // if(m_sCheckBox->isChecked()) targetParams.append(1.75); // 表皮系数 - // if(m_cCheckBox->isChecked()) targetParams.append(0.23); // 井筒储集系数 - // if(m_phiCheckBox->isChecked()) targetParams.append(0.189); // 孔隙度 - - // double targetError = 0.02291; - // m_autoFitterPSO->setSimulationTargetParams(targetParams, targetError); - //} - //else if (targetWellName == "W0009_1") { - - // m_autoFitterPSO->setSimulationMode(true); - - // // 构建目标参数向量(按启用参数的顺序) - // QVector targetParams; - - // if(m_kCheckBox->isChecked()) targetParams.append(0.23); // 渗透率 - // if(m_sCheckBox->isChecked()) targetParams.append(0.75); // 表皮系数 - // if(m_cCheckBox->isChecked()) targetParams.append(1.28); // 井筒储集系数 - // if(m_phiCheckBox->isChecked()) targetParams.append(0.1954); // 孔隙度 - - // double targetError = 0.02036; - // m_autoFitterPSO->setSimulationTargetParams(targetParams, targetError); - //} - - m_progressMonitor = new nmWxAutomaticfittingStart(this); if(m_autoFitterLM) { m_progressMonitor->setAutoFitter(m_autoFitterLM); @@ -1238,17 +806,10 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector>& targ int maxIterations = m_iterationEdit->text().toInt(); double targetError = m_errorLimitEdit->text().toDouble(); - QString wellName = m_targetWellCombo->currentText(); + QString wellName = m_targetWellName; m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName); - // 单位和勾选参数保持同一顺序,供横排参数区显示。 - QStringList selectedUnits; - if(m_kCheckBox->isChecked()) selectedUnits << tr("mD"); - if(m_sCheckBox->isChecked()) selectedUnits << QString(); - if(m_cCheckBox->isChecked()) selectedUnits << tr("m^3/MPa"); - if(m_phiCheckBox->isChecked()) selectedUnits << QString(); - if(m_dfcCheckBox->isChecked()) selectedUnits << tr("md.m"); - if(m_fractureHalfLengthCheckBox->isChecked()) selectedUnits << tr("m"); - m_progressMonitor->setSelectedParameters(selectedParams, selectedUnits); + // 名称和单位均按实际优化向量的顺序展示。 + m_progressMonitor->setSelectedParameters(selectedParams, selectedParameterLabels(true)); m_progressMonitor->show(); QTimer::singleShot(100, this, SLOT(runAutoFitting())); @@ -1408,28 +969,156 @@ void nmWxAutomaticFitting::cleanupFitting() void nmWxAutomaticFitting::refreshParametersFromModel() { - nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); - if(!manager) { - return; - } + // 拟合结束后按各井实际落地的值刷新初值和范围,保留参数勾选。 + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + saveModelParameters(); + reservoirData = manager->getReservoirDataCopy(); + initializeModelParameters(false); + onAlgorithmChanged(m_algorithmCombo->currentIndex()); + saveModelParameters(); + setAutomaticFittingValue(); + nmWxParameterProperty::notifyUpdateTable(); +} - // 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。 - reservoirData = manager->getReservoirDataCopy(); - const bool wasUpdatingRanges = m_updatingParameterRanges; - m_updatingParameterRanges = true; - m_parameterTable->item(0, 3)->setText(QString::number( - reservoirData.getPermeability().getValue().toDouble(), 'g', 10)); - m_parameterTable->item(3, 3)->setText(QString::number( - reservoirData.getPorosity().getValue().toDouble(), 'g', 10)); - - // 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。 - // 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 - m_autoParameterRanges = false; - onWellSelected(m_targetWellCombo->currentIndex()); - m_autoParameterRanges = true; - initializeSuggestedParameterRanges(); - m_updatingParameterRanges = wasUpdatingRanges; - - manager->updateAutomaticFittingData(automaticFittingData); - nmWxParameterProperty::notifyUpdateTable(); +void nmWxAutomaticFitting::saveModelParameters() +{ + // 每个叶节点对应一个带井编码的参数,直接收集所有分组。 + QVector& parameters = automaticFittingData.getModelParameters(); + for(int i = 0; i < parameters.size(); ++i) { + nmFitParameter& p = parameters[i]; + QTreeWidgetItem* item = m_parameterItems[i]; + p.selected = !item->isDisabled() && item->checkState(0) == Qt::Checked; + p.initial = item->text(1).toDouble(); + p.lower = item->text(2).toDouble(); + p.upper = item->text(3).toDouble(); + } +} + +void nmWxAutomaticFitting::initializeModelParameters(bool restoreRanges) +{ + // 储层只建一组,各井按井型加入原有参数;不再通过切换页面收集数据。 + const bool updating = m_updatingParameterRanges; + m_updatingParameterRanges = true; + const QVector saved = automaticFittingData.getModelParameters(); + automaticFittingData.getModelParameters().clear(); + m_parameterItems.clear(); + m_parameterTable->clear(); + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + QTreeWidgetItem* reservoir = new QTreeWidgetItem(m_parameterTable, QStringList(tr("Reservoir parameters"))); + addParameter(reservoir, 0, nullptr, reservoirData.getPermeability().getValue().toDouble()); + addParameter(reservoir, 3, nullptr, reservoirData.getPorosity().getValue().toDouble()); + QTreeWidgetItem* wellsGroup = new QTreeWidgetItem(m_parameterTable, QStringList(tr("Well parameters"))); + const QVector wells = manager->getEffectiveCalculationWells(); + for(int i = 0; i < wells.size(); ++i) { + nmDataWellBase* well = manager->findWellByCode(wells[i].m_sWellCode); + if(!well) continue; + QTreeWidgetItem* group = new QTreeWidgetItem(wellsGroup, QStringList(well->getWellName())); + const double skin = well->getPerforationCount() > 0 + ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0; + addParameter(group, 1, well, skin); + addParameter(group, 2, well, well->getWellboreStorage().getValue().toDouble()); + if(nmDataVerticalFracturedWell* fractured = dynamic_cast(well)) { + addParameter(group, 4, well, fractured->getDfc().getValue().toDouble()); + addParameter(group, 5, well, fractured->getFractureHalfLength().getValue().toDouble()); + } else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast(well)) { + addParameter(group, 4, well, fractured->getDfc().getValue().toDouble()); + addParameter(group, 5, well, fractured->getFractureHalfLength().getValue().toDouble()); + } + } + const bool legacySelected[] = {automaticFittingData.getPermeabilitySelected(), + automaticFittingData.getSkinSelected(), automaticFittingData.getWellboreStorageSelected(), + automaticFittingData.getPorositySelected(), automaticFittingData.getFractureConductivitySelected(), + automaticFittingData.getFractureHalfLengthSelected()}; + nmDataAttribute* legacyMin[] = {&automaticFittingData.getPermeabilityMin(), &automaticFittingData.getSkinMin(), + &automaticFittingData.getWellboreStorageMin(), &automaticFittingData.getPorosityMin(), + &automaticFittingData.getFractureConductivityMin(), &automaticFittingData.getFractureHalfLengthMin()}; + nmDataAttribute* legacyMax[] = {&automaticFittingData.getPermeabilityMax(), &automaticFittingData.getSkinMax(), + &automaticFittingData.getWellboreStorageMax(), &automaticFittingData.getPorosityMax(), + &automaticFittingData.getFractureConductivityMax(), &automaticFittingData.getFractureHalfLengthMax()}; + const QVector& parameters = automaticFittingData.getModelParameters(); + for(int i = 0; i < parameters.size(); ++i) { + const nmFitParameter& p = parameters[i]; + updateRangeForParameter(i, p.initial); + for(int j = 0; j < saved.size(); ++j) { + if(saved[j].type != p.type || saved[j].wellCode != p.wellCode) continue; + m_parameterItems[i]->setCheckState(0, saved[j].selected ? Qt::Checked : Qt::Unchecked); + if(restoreRanges && p.initial >= saved[j].lower && p.initial <= saved[j].upper) + setParameterRange(i, saved[j].lower, saved[j].upper); + break; + } + // 兼容尚无多井列表的敏感性设置,旧井参数仍对应原来记录的目标井。 + if(saved.isEmpty() && m_sensitivitySettings && m_sensitivitySettings->confirmed + && (p.wellCode.isEmpty() || p.wellName == m_sensitivitySettings->targetWell)) { + m_parameterItems[i]->setCheckState(0, legacySelected[p.type] ? Qt::Checked : Qt::Unchecked); + const double lower = legacyMin[p.type]->getValue().toDouble(); + const double upper = legacyMax[p.type]->getValue().toDouble(); + if(restoreRanges && p.initial >= lower && p.initial <= upper) setParameterRange(i, lower, upper); + } + } + // 分组标题加粗并横跨数值列,叶节点仍保留独立勾选和编辑。 + for(QTreeWidgetItemIterator it(m_parameterTable); *it; ++it) { + if((*it)->data(0, Qt::UserRole).isValid()) continue; + QFont font = (*it)->font(0); + font.setBold(true); + (*it)->setFont(0, font); + (*it)->setFirstColumnSpanned(true); + (*it)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } + m_parameterTable->expandAll(); + m_updatingParameterRanges = updating; +} + +void nmWxAutomaticFitting::addParameter(QTreeWidgetItem* group, int type, + nmDataWellBase* well, double initial) +{ + // 名称在井分组内只显示参数本身,数据仍保留所属井编码供计算使用。 + nmFitParameter parameter; + parameter.type = type; + parameter.initial = initial; + const QString name = parameter.name(); + if(well) { + parameter.wellCode = well->getWellCode(); + parameter.wellName = well->getWellName(); + } + const int index = automaticFittingData.getModelParameters().size(); + automaticFittingData.getModelParameters().append(parameter); + QTreeWidgetItem* item = new QTreeWidgetItem(group); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable); + item->setData(0, Qt::UserRole, index); + item->setText(0, name); + item->setText(1, QString::number(initial, 'g', 10)); + item->setText(4, parameter.unit()); + item->setCheckState(0, !m_sensitivitySettings + && (!well || well->getWellCode() == nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode()) + ? Qt::Checked : Qt::Unchecked); + m_parameterItems.append(item); +} + +QVector nmWxAutomaticFitting::primaryInitialValues() const +{ + // 旧六参数接口仅提取主井与共享储层,其他井不会覆盖主井初值。 + QVector values(6, 0.0); + const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode(); + const QVector& parameters = automaticFittingData.getModelParameters(); + for(int i = 0; i < parameters.size(); ++i) + if(parameters[i].wellCode.isEmpty() || parameters[i].wellCode == primaryCode) + values[parameters[i].type] = parameters[i].initial; + return values; +} + +QStringList nmWxAutomaticFitting::selectedParameterLabels(bool units) const +{ + // LM 按完整向量顺序显示,PSO 沿用旧六参数类型顺序。 + QStringList labels; + const QVector& parameters = automaticFittingData.getModelParameters(); + if(m_algorithmCombo->currentIndex() == 1) { + for(int i = 0; i < parameters.size(); ++i) + if(parameters[i].selected) labels << (units ? parameters[i].unit() : parameters[i].name()); + } else { + for(int type = 0; type < 6; ++type) + for(int i = 0; i < parameters.size(); ++i) + if(parameters[i].selected && parameters[i].type == type) + labels << (units ? parameters[i].unit() : parameters[i].name()); + } + return labels; } diff --git a/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp b/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp index c1c3744e..a14ece49 100644 --- a/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp +++ b/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp @@ -151,16 +151,27 @@ void appendSensitivityParas(nmDataAnalyzeManager* manager, QStringList& paras, if(!batch->filteredParameterBounds(i, minimum, maximum)) continue; const QString lower = "Sensitivity_" + resultNames[index] + "_Lower"; const QString higher = "Sensitivity_" + resultNames[index] + "_Higher"; - appendResultPara(paras, values, groupCode, lower, minimum); - appendResultPara(paras, values, groupCode, higher, maximum); + // 多井结果复用 XML 分组机制,同名参数用所属井的分组编码区分。 + const QString parameterGroup = variable.m_wellCode.isEmpty() ? groupCode : groupCode + "_" + variable.m_wellCode; + if(parameterGroup != groupCode) groups[parameterGroup] = groups[groupCode] + " - " + variable.m_varGroup; + appendResultPara(paras, values, parameterGroup, lower, minimum); + appendResultPara(paras, values, parameterGroup, higher, maximum); } // 第四种在采样范围后追加各组拟合最优值的范围,沿用同一阈值和 XML 分组。 for(int i = 0; i < batch->m_fitParameterCodes.size(); ++i) { double minimum = 0.0, maximum = 0.0; if(!batch->filteredParameterBounds(i, minimum, maximum, true)) continue; - const QString name = "Sensitivity_Fit_" + batch->m_fitParameterCodes[i]; - appendResultPara(paras, values, groupCode, name + "_Minimum", minimum); - appendResultPara(paras, values, groupCode, name + "_Maximum", maximum); + const QString code = batch->m_fitParameterCodes[i]; + const int separator = code.indexOf('/'); + const QString wellCode = separator < 0 ? QString() : code.left(separator); + const QString parameterGroup = wellCode.isEmpty() ? groupCode : groupCode + "_" + wellCode; + if(parameterGroup != groupCode) { + nmDataWellBase* well = manager->findWellByCode(wellCode); + groups[parameterGroup] = groups[groupCode] + " - " + (well ? well->getWellName() : wellCode); + } + const QString name = "Sensitivity_Fit_" + code.mid(separator + 1); + appendResultPara(paras, values, parameterGroup, name + "_Minimum", minimum); + appendResultPara(paras, values, parameterGroup, name + "_Maximum", maximum); } } diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index f5867f04..2740891a 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -206,7 +206,7 @@ QString sensitivityRunParameters(const nmDataSensitive::BatchResult& results, co double value = run.m_vecParameters[i]; const QString unit = variable.m_modelValue.getUnit(); convertSamplingValue(variable.m_varName, value, samplingBaseUnit(variable.m_varName), unit, value); - descriptions << variable.m_varName + " = " + formatSamplingValue(value, 3) + descriptions << variable.displayName() + " = " + formatSamplingValue(value, 3) + (unit.isEmpty() ? QString() : " " + unit); } return descriptions.join(" + "); @@ -634,7 +634,7 @@ nmWxSensitivityScatter::nmWxSensitivityScatter( options->addWidget(new QLabel(tr("X Axis:"), this)); m_parameter = new QComboBox(this); for(int i = 0; i < results->m_variables.size(); ++i) - if(results->m_variables[i].m_enabled) m_parameter->addItem(results->m_variables[i].m_varName, i); + if(results->m_variables[i].m_enabled) m_parameter->addItem(results->m_variables[i].displayName(), i); options->addWidget(m_parameter, 1); options->addSpacing(20); options->addWidget(new QLabel(tr("Y Axis:"), this)); @@ -692,7 +692,7 @@ void nmWxSensitivityScatter::updateParameter() const int index = m_parameter->itemData(m_parameter->currentIndex()).toInt(); const nmDataSensitive::VariableSampling& variable = m_results->m_variables[index]; const QString unit = variable.m_modelValue.getUnit(); - m_plot->setAxisX(variable.m_varName, unit); + m_plot->setAxisX(variable.displayName(), unit); double modelValue = variable.m_modelValue.getValue().toDouble(); const bool hasModel = convertSamplingValue(variable.m_varName, modelValue, samplingBaseUnit(variable.m_varName), unit, modelValue) && qIsFinite(modelY) && (!errorAxis || modelY >= 0.0); @@ -1254,7 +1254,7 @@ private: nmDataSensitive::VariableSampling& variable = m_data.getVariable(index); const QString unit = variable.getModelValue().getUnit(); convertSamplingValue(variable.getVarName(), value, samplingBaseUnit(variable.getVarName()), unit, value); - return variable.getVarName() + " = " + formatSamplingValue(value, 3) + return variable.displayName() + " = " + formatSamplingValue(value, 3) + (unit.isEmpty() ? QString() : " " + unit); } @@ -1391,12 +1391,26 @@ void nmWxSensitive::closeEvent(QCloseEvent* event) void nmWxSensitive::refreshModelValues() { // 每次打开或模型更新时读取当前参数,沿用已有单位和物理范围限制。 - nmDataWellBase* well = m_pDataManager - ? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr; nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : nullptr; + const QVector wells = m_pDataManager + ? m_pDataManager->getEffectiveCalculationWells() : QVector(); + // 旧版主井配置补上井编码;已不参与模型的井不再生成样本。 + for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { + nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); + if(variable.m_varName == "Skin" || variable.m_varName == "C") { + if(variable.m_wellCode.isEmpty() && m_pDataManager) + variable.m_wellCode = m_pDataManager->getPrimaryWellCode(); + if(!m_pDataManager || !m_pDataManager->isWellSelectedForCalculation(variable.m_wellCode)) + variable.m_enabled = false; + } + } bool modelChanged = false; const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6)); - for (int n = 0; n < names.size(); ++n) { + for (int entry = 0; entry < wells.size() * 2 + 4; ++entry) { + const bool wellParameter = entry < wells.size() * 2; + const int n = wellParameter ? entry % 2 : entry - wells.size() * 2 + 2; + const QString wellCode = wellParameter ? wells[entry / 2].m_sWellCode : QString(); + nmDataWellBase* well = wellParameter ? m_pDataManager->findWellByCode(wellCode) : nullptr; nmDataAttribute* attribute = nullptr; const QString name = names[n]; if (well && name == "Skin" && well->getPerforationCount() > 0) @@ -1409,17 +1423,19 @@ void nmWxSensitive::refreshModelValues() int index = -1; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) - if (m_sensitiveData.getVariable(i).getVarName() == name) { index = i; break; } + if (m_sensitiveData.getVariable(i).getVarName() == name && + m_sensitiveData.getVariable(i).m_wellCode == wellCode) { index = i; break; } const bool fresh = index < 0; if (fresh) { nmDataSensitive::VariableSampling variable; variable.setVarName(name); - variable.setVarGroup(n < 2 ? "Tested Well" : "Reservoir"); + variable.m_wellCode = wellCode; variable.setNumber(5); m_sensitiveData.getVariables().append(variable); index = m_sensitiveData.getVariableCount() - 1; } nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index); + variable.setVarGroup(well ? well->getWellName() : "Reservoir"); double physicalMin = 0.0, physicalMax = 0.0; if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) { variable.setEnabled(false); @@ -1528,7 +1544,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly) for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); const QString name = variable.getVarName(); - const int idx = findVariableIndex(name); + const int idx = findVariableIndex(variable); if (idx < 0) continue; const bool available = isSupportedSamplingVariable(name) && variable.getModelValue().getValue().isValid(); @@ -1536,7 +1552,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly) QTreeWidgetItem* parent = variablesTree->topLevelItem(group); for (int child = 0; child < parent->childCount(); ++child) { QTreeWidgetItem* item = parent->child(child); - if (item->text(0) != name) continue; + if (item->text(0) != name || item->data(0, Qt::UserRole + 1).toString() != variable.m_wellCode) continue; item->setDisabled(!available); if (!refreshRangesOnly) item->setCheckState(0, available && variable.getEnabled() ? Qt::Checked : Qt::Unchecked); @@ -1594,15 +1610,19 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly) } } -int nmWxSensitive::findVariableIndex(const QString& varName) +int nmWxSensitive::findVariableIndex(const nmDataSensitive::VariableSampling& variable) { - // 根据创建顺序查找变量索引 - QStringList allVars; - allVars << "Zw" << "Hw" << "Lw" << "Skin" << "C" // Tested Well - << "Pi" << "k" << "h" << QString(QChar(0x03c6)) << "ntg" << "kz/kr" // Reservoir - << "Total compressibility"; // Pvt - - return allVars.indexOf(varName); + // 页面由树节点创建,通过井编码与参数名定位,不依赖固定页面次序。 + for(int group = 0; group < variablesTree->topLevelItemCount(); ++group) { + QTreeWidgetItem* parent = variablesTree->topLevelItem(group); + for(int child = 0; child < parent->childCount(); ++child) { + QTreeWidgetItem* item = parent->child(child); + if(item->text(0) == variable.m_varName && + item->data(0, Qt::UserRole + 1).toString() == variable.m_wellCode) + return item->data(0, Qt::UserRole).toInt() - 1; + } + } + return -1; } void nmWxSensitive::updateModelCount() @@ -1618,9 +1638,10 @@ void nmWxSensitive::updateModelCount() QTreeWidgetItem* item = parent->child(child); for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); - if (variable.getVarName() != item->text(0)) continue; + if (variable.getVarName() != item->text(0) || + variable.m_wellCode != item->data(0, Qt::UserRole + 1).toString()) continue; variable.setEnabled(!item->isDisabled() && item->checkState(0) == Qt::Checked); - const int idx = findVariableIndex(variable.getVarName()); + const int idx = findVariableIndex(variable); if (idx >= 0) variable.setNumber(m_numSpins[idx]->value()); } } @@ -1637,10 +1658,21 @@ void nmWxSensitive::updateModelCount() // 第四种确认设置且至少选择一个拟合参数后才开放生成,空选择直接禁用。 bool hasFittingParameters = false; if(!m_sensitiveData.getImproveSettings().isNull()) { - const nmDataAutomaticFitting& fitting = m_sensitiveData.getImproveSettings()->parameters; - hasFittingParameters = fitting.getPermeabilitySelected() || fitting.getSkinSelected() || - fitting.getWellboreStorageSelected() || fitting.getPorositySelected() || - fitting.getFractureConductivitySelected() || fitting.getFractureHalfLengthSelected(); + nmSensitivityFitSettings settings = *m_sensitiveData.getImproveSettings(); + QVector& parameters = settings.parameters.getModelParameters(); + // 新勾选的采样只取消同井同参数的拟合,其他井的同名参数仍可参与。 + for(int i = 0; i < parameters.size(); ++i) { + if(!parameters[i].wellCode.isEmpty() && + !m_pDataManager->isWellSelectedForCalculation(parameters[i].wellCode)) + parameters[i].selected = false; + for(int v = 0; v < m_sensitiveData.getVariableCount(); ++v) { + const nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(v); + if(variable.m_enabled && variable.parameterKey() == parameters[i].samplingKey()) + parameters[i].selected = false; + } + } + hasFittingParameters = settings.confirmed && settings.parameters.hasSelectedParameters(); + m_sensitiveData.setImproveSettings(QSharedPointer(new nmSensitivityFitSettings(settings))); } generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning && (calculationTypeCombo->currentIndex() != 3 || hasFittingParameters)); @@ -1655,7 +1687,7 @@ bool nmWxSensitive::saveDataFromUI() for (int i = 0; i < candidate.getVariableCount(); ++i) { nmDataSensitive::VariableSampling& variable = candidate.getVariable(i); const QString name = variable.getVarName(); - const int idx = findVariableIndex(name); + const int idx = findVariableIndex(variable); if (idx < 0 || !isSupportedSamplingVariable(name)) continue; const QString unit = m_unitLabels[idx]->text(); const QString baseUnit = samplingBaseUnit(name); @@ -1898,22 +1930,25 @@ void nmWxSensitive::populateTreeWidget() { variablesTree->clear(); - // ===== Tested Well ===== - QTreeWidgetItem *testedWellItem = new QTreeWidgetItem(variablesTree); - testedWellItem->setText(0, tr("Tested Well")); - testedWellItem->setCheckState(0, Qt::Unchecked); - testedWellItem->setFlags(testedWellItem->flags() | Qt::ItemIsUserCheckable); - - QStringList testedWellChildren; - testedWellChildren << "Zw" << "Hw" << "Lw" << "Skin" << "C"; - + // 只列出实际参与模型的井,每口井复用现有的参数及采样页。 + const QVector wells = m_pDataManager + ? m_pDataManager->getEffectiveCalculationWells() : QVector(); int i; - for (i = 0; i < testedWellChildren.size(); ++i) { - QTreeWidgetItem *childItem = new QTreeWidgetItem(testedWellItem); - childItem->setText(0, testedWellChildren.at(i)); - childItem->setCheckState(0, Qt::Unchecked); - childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable); - childItem->setDisabled(!isSupportedSamplingVariable(testedWellChildren.at(i))); + for(int w = 0; w < wells.size(); ++w) { + nmDataWellBase* well = m_pDataManager->findWellByCode(wells[w].m_sWellCode); + if(!well) continue; + QTreeWidgetItem* testedWellItem = new QTreeWidgetItem(variablesTree); + testedWellItem->setText(0, wells.size() == 1 ? tr("Tested Well") : well->getWellName()); + testedWellItem->setCheckState(0, Qt::Unchecked); + QStringList children; + children << "Zw" << "Hw" << "Lw" << "Skin" << "C"; + for(i = 0; i < children.size(); ++i) { + QTreeWidgetItem* child = new QTreeWidgetItem(testedWellItem); + child->setText(0, children[i]); + child->setData(0, Qt::UserRole + 1, well->getWellCode()); + child->setCheckState(0, Qt::Unchecked); + child->setDisabled(!isSupportedSamplingVariable(children[i])); + } } // ===== Reservoir ===== @@ -2296,7 +2331,7 @@ void nmWxSensitive::updateValuesForPage(int idx) table->setRowCount(number); nmDataSensitive::VariableSampling variable; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { - if (findVariableIndex(m_sensitiveData.getVariable(i).getVarName()) == idx) { + if (findVariableIndex(m_sensitiveData.getVariable(i)) == idx) { variable = m_sensitiveData.getVariable(i); break; } @@ -2375,7 +2410,7 @@ void nmWxSensitive::updateDistributionPage(int idx) } for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { nmDataSensitive::VariableSampling variable = m_sensitiveData.getVariable(i); - if(findVariableIndex(variable.m_varName) != idx) continue; + if(findVariableIndex(variable) != idx) continue; const bool valid = readDistributionPage(idx, variable); m_distributionPlots[idx]->setVariable(variable, valid); break; @@ -2393,8 +2428,9 @@ void nmWxSensitive::onSamplingEditingFinished() for (int idx = 0; idx < m_minSpins.size(); ++idx) { if (edit != m_minSpins[idx] && edit != m_maxSpins[idx]) continue; for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) { - const QString name = m_sensitiveData.getVariable(i).getVarName(); - if (findVariableIndex(name) != idx) continue; + const nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i); + 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 baseUnit = samplingBaseUnit(name); @@ -2634,7 +2670,7 @@ void nmWxSensitive::onImproveSettingsClicked() updateModelCount(); QStringList sampledParameters; foreach(const nmDataSensitive::VariableSampling& variable, m_sensitiveData.getVariables()) - if(variable.m_enabled) sampledParameters.append(variable.m_varName); + if(variable.m_enabled) sampledParameters.append(variable.parameterKey()); nmWxAutomaticFitting dialog(this, &settings, sampledParameters); if(dialog.exec() == QDialog::Accepted) { m_sensitiveData.setImproveSettings(QSharedPointer(new nmSensitivityFitSettings(settings)));