From cf9859c83f1c571df437f031bf62d07336de42f0 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 16 Oct 2026 15:59:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(nmNum):=20=E4=BD=BF=E7=94=A8=E6=B1=82?= =?UTF-8?q?=E8=A7=A3=E5=BF=AB=E7=85=A7=E5=88=9D=E5=A7=8B=E5=8C=96=E6=8B=9F?= =?UTF-8?q?=E5=90=88=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从成功求解快照读取初值,沿用范围限制。 - 切井读取对应快照,新模型重新初始化设置。 - 无快照时禁用拟合,未选拟合参数时禁用生成。 --- .../nmCalculationDllPebiSolverTask.h | 3 + Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 4 + .../nmCalculationDllPebiSolverTask.cpp | 31 +++++ Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 106 ++++++++---------- Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp | 4 + Src/nmNum/nmSubWxs/nmWxSensitive.cpp | 11 +- 6 files changed, 96 insertions(+), 63 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index cb928a0b..61091194 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -134,6 +134,9 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { // 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。 static double calculatePressureRmse(const QVector >& measured, const QVector >& simulated); + // 从最后成功输入的裂缝端点读取半长,不访问当前编辑中的井对象。 + static bool getSolvedFractureHalfLength(const QSharedPointer& input, + const QString& wellCode, double& halfLength); private: /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 8acf26ad..f8edf144 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -27,6 +27,7 @@ #include "nmWxAutomaticfittingStart.h" #include "nmSubWxs_global.h" +#include "nmPebiResultSnapshot.h" // 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。 struct nmSensitivityFitSettings @@ -35,6 +36,7 @@ struct nmSensitivityFitSettings bool confirmed; nmDataAutomaticFitting parameters; QString targetWell; + QString snapshotId; QVector initialValues; }; @@ -70,6 +72,7 @@ private: void setupButtons(); void setAutomaticFittingValue(); void loadSensitivitySettings(); + const nmPebiResultWellSnapshot* solvedWell(int index) const; void setParameterRowVisible(QTableWidget* table, int row, bool visible); void renumberVisibleParameterRows(QTableWidget* table); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); @@ -113,6 +116,7 @@ private: // 数据成员 nmDataReservoir reservoirData; + QSharedPointer m_solvedSnapshot; nmDataAutomaticFitting automaticFittingData; nmSensitivityFitSettings* m_sensitivitySettings; QStringList m_sampledParameters; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 3df56e71..2d4b3b25 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -1028,6 +1028,37 @@ double nmCalculationDllPebiSolverTask::calculatePressureRmse( return calculateSensitivityPressureRmse(measured, simulated, nullptr); } +bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( + const QSharedPointer& input, + const QString& wellCode, double& halfLength) +{ + // 各类井的几何数组与同类型槽位顺序一致,水平压裂井的各条裂缝共用半长。 + if(input.isNull()) return false; + const nmPebiGridInputSnapshot& grid = input->m_oGridInput; + size_t vertical = 0, horizontal = 0; + for(int i = 0; i < grid.m_vecSolverWellOrder.size(); ++i) { + const nmSolverWellRef& well = grid.m_vecSolverWellOrder[i]; + const std::vector* fracture = nullptr; + if(well.m_eWellType == Vertical_Fractured_Well) { + if(vertical < grid.m_oGridInput.FractureVerticalWell.size()) + fracture = &grid.m_oGridInput.FractureVerticalWell[vertical]; + ++vertical; + } else if(well.m_eWellType == Horizontal_Fractured_Well) { + if(horizontal < grid.m_oGridInput.MultistageFracturedHorizontalWell.size() && + !grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal].empty()) + fracture = &grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal].front(); + ++horizontal; + } + if(well.m_sWellCode != wellCode) continue; + if(!fracture || fracture->size() < 4) return false; + const double dx = (*fracture)[2] - (*fracture)[0]; + const double dy = (*fracture)[3] - (*fracture)[1]; + halfLength = std::sqrt(dx * dx + dy * dy) * 0.5; + return qIsFinite(halfLength) && halfLength > 0.0; + } + return false; +} + bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() { m_pSensitivityResult->m_runs.clear(); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 3a8c92aa..c496d75a 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -1,6 +1,7 @@ #include "nmWxAutomaticFitting.h" #include "nmCalculationAutoFitPSO.h" #include "nmCalculationAutoFitLM.h" +#include "nmCalculationDllPebiSolverTask.h" #include "nmWxAutomaticfittingStart.h" #include "nmWxParameterProperty.h" #include "nmAttrRegistry.h" @@ -132,16 +133,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL // 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; - } - } + const nmPebiResultWellSnapshot* well = solvedWell(m_targetWellCombo->currentIndex()); + if(well) showFractureConductivity = well->m_bHasDfc; setParameterRowVisible(table, 5, showFractureConductivity); // Dfc setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长 @@ -430,6 +423,12 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett // 获取数据 nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); reservoirData = pManager->getReservoirDataCopy(); + // 初值来自最后成功模型;当前储层副本只保留原有属性及单位。 + m_solvedSnapshot = pManager->getPebiResultSnapshot(); + if(!m_solvedSnapshot.isNull()) { + reservoirData.getPermeability().setValue(m_solvedSnapshot->getReservoirParameters().m_dPermeability); + reservoirData.getPorosity().setValue(m_solvedSnapshot->getReservoirParameters().m_dPorosity); + } automaticFittingData = pManager->getAutomaticFittingDataCopy(); // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 m_autoParameterRanges = true; @@ -447,7 +446,9 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett } // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 initializeSuggestedParameterRanges(); - if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings(); + // 新模型重新初始化范围,同一模型才恢复上次确认的拟合设置。 + if(m_sensitivitySettings && m_sensitivitySettings->confirmed && !m_solvedSnapshot.isNull() && + m_sensitivitySettings->snapshotId == m_solvedSnapshot->getSnapshotId()) loadSensitivitySettings(); m_updatingParameterRanges = false; DEBUG_UI("AutoFitting Constructor completed"); @@ -455,7 +456,7 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett void nmWxAutomaticFitting::loadSensitivitySettings() { - // 先恢复目标井,再覆盖默认范围与初值,保留上次确认的完整设置。 + // 同一次模型的拟合设置仍恢复用户确认的范围与初值。 m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); automaticFittingData = m_sensitivitySettings->parameters; m_autoParameterRanges = false; @@ -727,29 +728,14 @@ void nmWxAutomaticFitting::setupControlPanel() 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()); - } - } - } + // 井列表也取自求解快照,未求解的新井不作为旧模型的拟合目标。 + for(int i = 0; !m_solvedSnapshot.isNull() && i < m_solvedSnapshot->getWellCount(); ++i) { + const nmPebiResultWellSnapshot* well = m_solvedSnapshot->getWellAt(i); + if(well->m_sWellCode == m_solvedSnapshot->getSensitivityWellCode()) + m_targetWellCombo->insertItem(0, well->m_sWellName, well->m_sWellInstanceId); + else if(well->m_eWellMode == NM_CaseWell_RateControlled && !well->m_oCurves.m_vecHistoryPressure.isEmpty()) + m_targetWellCombo->addItem(well->m_sWellName, well->m_sWellInstanceId); + } connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int))); m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); @@ -883,6 +869,7 @@ void nmWxAutomaticFitting::onAccept() // 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。 m_sensitivitySettings->parameters = automaticFittingData; m_sensitivitySettings->targetWell = m_targetWellCombo->currentText(); + m_sensitivitySettings->snapshotId = m_solvedSnapshot->getSnapshotId(); m_sensitivitySettings->initialValues.clear(); const int rows[] = {0, 1, 2, 3, 5, 6}; for(int i = 0; i < 6; ++i) @@ -991,49 +978,43 @@ void nmWxAutomaticFitting::onReject() reject(); } +const nmPebiResultWellSnapshot* nmWxAutomaticFitting::solvedWell(int index) const +{ + // 用快照中的稳定标识定位井,不依赖后来编辑的井名称或参数。 + return m_solvedSnapshot.isNull() ? nullptr : + m_solvedSnapshot->findWell(m_targetWellCombo->itemData(index).toString()); +} + void nmWxAutomaticFitting::onWellSelected(int index) { - // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。 - nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); - if(!manager) { - return; - } - nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index)); + // 切井也读取同一次成功模型,默认范围继续使用原有生成及物理限制逻辑。 + const nmPebiResultWellSnapshot* targetWell = solvedWell(index); if(!targetWell) { return; } const bool wasUpdatingRanges = m_updatingParameterRanges; m_updatingParameterRanges = true; - updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); + updateParameterVisibility(m_parameterTable, + static_cast(m_solvedSnapshot->getSolverSettings().m_nSolverModelType)); - nmDataPerforation* perforation = targetWell->getPerforation(0); - if(perforation) { - const double skin = perforation->getSkin().getValue().toDouble(); + if(targetWell->m_bHasSkin) { + const double skin = targetWell->m_dSkin; m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10)); if(m_autoParameterRanges) { updateRangeForParameter(1, skin); } } - const double storage = targetWell->getWellboreStorage().getValue().toDouble(); + const double storage = targetWell->m_dWellboreStorage; m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10)); if(m_autoParameterRanges) { updateRangeForParameter(2, storage); } - bool fracturedWell = false; - double conductivity = 0.0; + const double conductivity = targetWell->m_dDfc; 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) { + if(targetWell->m_bHasDfc && nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( + m_solvedSnapshot->getSensitivityInput(), targetWell->m_sWellCode, halfLength)) { 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) { @@ -1406,8 +1387,12 @@ void nmWxAutomaticFitting::refreshParametersFromModel() return; } - // 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。 + // 拟合结束后重新取得最后成功快照,失败时仍沿用之前成功模型的初值。 reservoirData = manager->getReservoirDataCopy(); + m_solvedSnapshot = manager->getPebiResultSnapshot(); + if(m_solvedSnapshot.isNull()) return; + reservoirData.getPermeability().setValue(m_solvedSnapshot->getReservoirParameters().m_dPermeability); + reservoirData.getPorosity().setValue(m_solvedSnapshot->getReservoirParameters().m_dPorosity); const bool wasUpdatingRanges = m_updatingParameterRanges; m_updatingParameterRanges = true; m_parameterTable->item(0, 3)->setText(QString::number( @@ -1415,8 +1400,7 @@ void nmWxAutomaticFitting::refreshParametersFromModel() m_parameterTable->item(3, 3)->setText(QString::number( reservoirData.getPorosity().getValue().toDouble(), 'g', 10)); - // 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。 - // 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 + // 先刷新快照初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 m_autoParameterRanges = false; onWellSelected(m_targetWellCombo->currentIndex()); m_autoParameterRanges = true; diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index 7cdb5476..a1fc0abb 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -1435,6 +1435,9 @@ void nmWxNumericalDesign::updateTimeDependentSkinDisplay() void nmWxNumericalDesign::onAutomaticFittingClicked() { + // 与敏感性分析一样,成功生成模型后才有可用的拟合初值快照。 + updateSensitiveButtonEnabled(); + if(!m_pAutomaticfittingBtn->isEnabled()) return; nmWxAutomaticFitting* dlg = new nmWxAutomaticFitting; dlg->show(); } @@ -1452,6 +1455,7 @@ void nmWxNumericalDesign::updateSensitiveButtonEnabled() ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer(); const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull(); m_pSensitiveBtn->setEnabled(available); + m_pAutomaticfittingBtn->setEnabled(available); m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis.")); } diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index acf0c22d..40b47c57 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -1491,9 +1491,16 @@ void nmWxSensitive::updateModelCount() const bool monteCarlo = m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO; modelCountLabel->setVisible(!monteCarlo); m_monteCarloCount->setVisible(monteCarlo); - // 第四种确认拟合设置后才开放生成,其他方法沿用原有条件。 + // 第四种确认设置且至少选择一个拟合参数后才开放生成,空选择直接禁用。 + bool hasFittingParameters = false; + if(!m_improveSettings.isNull()) { + const nmDataAutomaticFitting& fitting = m_improveSettings->parameters; + hasFittingParameters = fitting.getPermeabilitySelected() || fitting.getSkinSelected() || + fitting.getWellboreStorageSelected() || fitting.getPorositySelected() || + fitting.getFractureConductivitySelected() || fitting.getFractureHalfLengthSelected(); + } generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning && - (calculationTypeCombo->currentIndex() != 3 || !m_improveSettings.isNull())); + (calculationTypeCombo->currentIndex() != 3 || hasFittingParameters)); } bool nmWxSensitive::saveDataFromUI()