From 45f73f2b26bc8fd45b08345da91289c50740adcf Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Mon, 14 Sep 2026 17:08:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(nmNum):=20=E4=BF=AE=E5=A4=8D=E6=8B=9F?= =?UTF-8?q?=E5=90=88=E5=8F=82=E6=95=B0=E5=90=8C=E6=AD=A5=E4=B8=8E=20LM=20?= =?UTF-8?q?=E6=B5=AE=E7=82=B9=E8=BE=B9=E7=95=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拟合结束后按模型实际参数刷新初值,无论成功或失败均重新生成范围 - 手工范围仅用于本轮拟合,切换目标井时读取模型最新参数 - 同步储层数据副本,避免后续保存设置覆盖为旧值 - 修正 LM 参数逆变换的端点与范围限制,避免浮点误差触发越界失败 --- Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 6 +- .../nmCalculation/nmCalculationAutoFitLM.cpp | 18 +- Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 219 ++++++------------ 3 files changed, 82 insertions(+), 161 deletions(-) diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 9e525e52..3f526c4d 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -68,7 +68,7 @@ private: void startAutoFitting(const QVector>& targetData, const QStringList& selectedParams, const QString& targetWellName); void cleanupFitting(); - void updateBestParametersToTable(); + void refreshParametersFromModel(); private: @@ -101,10 +101,6 @@ private: // 数据成员 nmDataReservoir reservoirData; - QVector m_verticalWells; - QVector m_horizontalWells; - QVector m_verticalFracturedWells; - QVector m_horizontalFracturedWells; nmDataAutomaticFitting automaticFittingData; // 自动拟合相关成员 diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index edea6655..8e4ae45b 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -210,15 +210,25 @@ static double fromTrustRegionCoordinate(double coordinate, double lower, double upper) { - // 候选内部坐标先限制在 [0,1],再执行上述映射的逆变换,保证写回 - // DataManager 的参数始终位于用户设置的物理范围内。 coordinate = qMax(0.0, qMin(1.0, coordinate)); + // 端点直接返回原始边界,避免对数或线性逆变换的舍入误差造成越界。 + if(coordinate <= 0.0) { + return lower; + } + if(coordinate >= 1.0) { + return upper; + } + + double value; if(useTrustRegionLogScale(parameterIndex, lower, upper)) { - return qExp(qLn(lower) + coordinate * (qLn(upper) - qLn(lower))); + value = qExp(qLn(lower) + coordinate * (qLn(upper) - qLn(lower))); + } else { + value = lower + coordinate * (upper - lower); } - return lower + coordinate * (upper - lower); + // 内部点转换后也限制到实际上下界,保证候选参数通过严格的边界检查。 + return qMax(lower, qMin(upper, value)); } enum TrustRegionErrorComponent diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 09a85b59..24200afd 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -301,7 +301,7 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, } } -// 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。 +// 按当前表格中的初值为所有参数建立建议范围,首次加载和拟合完成后共用。 void nmWxAutomaticFitting::initializeSuggestedParameterRanges() { if(!m_parameterTable) { @@ -484,25 +484,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) { DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16)); - // 加载已有井数据 - QVector listWellData = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList(); - - // 遍历并分类井数据 - foreach (nmDataWellBase* well, listWellData) { - if (auto vfWell = dynamic_cast(well)) { - m_verticalFracturedWells.append(*vfWell); - } - else if (auto hfWell = dynamic_cast(well)) { - m_horizontalFracturedWells.append(*hfWell); - } - else if (auto vWell = dynamic_cast(well)) { - m_verticalWells.append(*vWell); - } - else if (auto hWell = dynamic_cast(well)) { - m_horizontalWells.append(*hWell); - } - } - // 获取数据 nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); reservoirData = pManager->getReservoirDataCopy(); @@ -902,7 +883,7 @@ void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) return; } - // 用户改动范围后,切井和拟合结果不再自动覆盖这组手工范围。 + // 手工范围用于本轮拟合,切井时保留;拟合完成后再按最新参数自动生成范围。 if(item->column() == 2 || item->column() == 4) { m_autoParameterRanges = false; } @@ -1030,105 +1011,55 @@ void nmWxAutomaticFitting::onReject() void nmWxAutomaticFitting::onWellSelected(int index) { - // 获取选中的井名 - QString selectedWellName = m_targetWellCombo->itemText(index); - - // 在分类的井数据中查找匹配的井 - bool found = false; - bool fracturedWell = false; - double skinValue = 0.0; - double wellboreStorageValue = 0.0; - double fractureConductivityValue = 0.0; - double fractureHalfLengthValue = 0.0; - - // 查找垂直井 - for(int i = 0; i < m_verticalWells.size(); ++i) { - if(m_verticalWells[i].getWellName() == selectedWellName) { - skinValue = m_verticalWells[i].getPerforation(0)->getSkin().getValue().toDouble(); - wellboreStorageValue = m_verticalWells[i].getWellboreStorage().getValue().toDouble(); - found = true; - break; - } + // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。 + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + if(!manager) { + return; } - - // 查找水平井 - if (!found) { - for(int i = 0; i < m_horizontalWells.size(); ++i) { - if(m_horizontalWells[i].getWellName() == selectedWellName) { - skinValue = m_horizontalWells[i].getPerforation(0)->getSkin().getValue().toDouble(); - wellboreStorageValue = m_horizontalWells[i].getWellboreStorage().getValue().toDouble(); - found = true; - break; - } - } + nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index)); + if(!targetWell) { + return; } - // 查找垂直压裂井 - if (!found) { - for(int i = 0; i < m_verticalFracturedWells.size(); ++i) { - if(m_verticalFracturedWells[i].getWellName() == selectedWellName) { - skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); - wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble(); - fractureConductivityValue = m_verticalFracturedWells[i].getDfc().getValue().toDouble(); - fractureHalfLengthValue = m_verticalFracturedWells[i].getFractureHalfLength().getValue().toDouble(); - fracturedWell = true; - found = true; - break; - } - } - } + const bool wasUpdatingRanges = m_updatingParameterRanges; + m_updatingParameterRanges = true; + updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); - // 查找水平压裂井 - if (!found) { - for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) { - if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) { - skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); - wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble(); - fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble(); - fractureHalfLengthValue = m_horizontalFracturedWells[i].getFractureHalfLength().getValue().toDouble(); - fracturedWell = true; - found = true; - break; - } + 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); + } - if (found) { - // 先按目标井类型刷新可见行,再写入当前井的井级初值。 - nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); - if(manager) { - updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); - } - // 更新表格数据 - // 确保表格项存在 - if(!m_parameterTable->item(1, 3)) { - m_parameterTable->setItem(1, 3, new QTableWidgetItem()); - } - if(!m_parameterTable->item(2, 3)) { - m_parameterTable->setItem(2, 3, new QTableWidgetItem()); - } - - // 设置皮肤系数(Skin) - m_parameterTable->item(1, 3)->setText(QString::number(skinValue)); - - // 设置井筒储集系数(Wellbore storage) - m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue)); - if(fracturedWell && m_parameterTable->item(5, 3)) { - m_parameterTable->item(5, 3)->setText(QString::number(fractureConductivityValue)); - } - if(fracturedWell && m_parameterTable->item(6, 3)) { - m_parameterTable->item(6, 3)->setText(QString::number(fractureHalfLengthValue)); - } - + 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(1, skinValue); - updateRangeForParameter(2, wellboreStorageValue); - if(fracturedWell) { - updateRangeForParameter(5, fractureConductivityValue); - updateRangeForParameter(6, fractureHalfLengthValue); - } + updateRangeForParameter(5, conductivity); + updateRangeForParameter(6, halfLength); } } + m_updatingParameterRanges = wasUpdatingRanges; } void nmWxAutomaticFitting::setAutomaticFittingValue() @@ -1361,10 +1292,9 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag if(manager && manager->getAttrRegistry()) { manager->getAttrRegistry()->refreshAll(); } + // 失败状态不代表参数未写回;初值与范围始终以模型实际保留的数据为准。 + refreshParametersFromModel(); if(success) { - // 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。 - updateBestParametersToTable(); - QString resultInfo; if(m_autoFitterPSO || m_autoFitterLM) { @@ -1479,47 +1409,32 @@ void nmWxAutomaticFitting::cleanupFitting() DEBUG_UI("=== CLEANUP FITTING END ==="); } -void nmWxAutomaticFitting::updateBestParametersToTable() +void nmWxAutomaticFitting::refreshParametersFromModel() { - QVector bestSolution; - - // 获取最佳解决方案 - if (m_autoFitterPSO) { - bestSolution = m_autoFitterPSO->getBestSolution(); - } else if(m_autoFitterLM) { - bestSolution = m_autoFitterLM->getBestSolution(); - } - - if (bestSolution.isEmpty()) return; - - // 获取启用的参数索引 - QVector enabledParams; - if(m_kCheckBox->isChecked()) enabledParams.append(0); // 渗透率 - if(m_sCheckBox->isChecked()) enabledParams.append(1); // 表皮系数 - if(m_cCheckBox->isChecked()) enabledParams.append(2); // 井筒储集系数 - if(m_phiCheckBox->isChecked()) enabledParams.append(3); // 孔隙度 - if(m_swiCheckBox->isChecked()) enabledParams.append(4); // 初始含水饱和度 - if(m_dfcCheckBox->isChecked()) enabledParams.append(5); // 裂缝导流能力 - if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(6); // 裂缝半长 - - // 更新参数值和范围 - for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) { - int paramIndex = enabledParams[i]; - double bestValue = bestSolution[i]; - if(!nmAutoFitUiIsFinite(bestValue)) { - continue; - } - - // 更新初始值 - m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4)); - // 自动范围模式下,以拟合结果为中心复用首次建范围的规则;手工范围由用户保留。 - if(m_autoParameterRanges) { - updateRangeForParameter(paramIndex, bestValue); - } + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + if(!manager) { + return; } - // 保存更新 - nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData); - // 更新完成后,通知参数界面刷新 + // 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。 + 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_parameterTable->item(4, 3)->setText(QString::number( + reservoirData.getSwi().getValue().toDouble(), 'g', 10)); + + // 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。 + // 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 + m_autoParameterRanges = false; + onWellSelected(m_targetWellCombo->currentIndex()); + m_autoParameterRanges = true; + initializeSuggestedParameterRanges(); + m_updatingParameterRanges = wasUpdatingRanges; + + manager->updateAutomaticFittingData(automaticFittingData); nmWxParameterProperty::notifyUpdateTable(); }