fix(nmNum): 修复拟合参数同步与 LM 浮点边界问题

- 拟合结束后按模型实际参数刷新初值,无论成功或失败均重新生成范围
- 手工范围仅用于本轮拟合,切换目标井时读取模型最新参数
- 同步储层数据副本,避免后续保存设置覆盖为旧值
- 修正 LM 参数逆变换的端点与范围限制,避免浮点误差触发越界失败
feature/AutoFit-Optimize-20260914
lvjunjie 3 weeks ago
parent 046453ff0a
commit 45f73f2b26

@ -68,7 +68,7 @@ private:
void startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName); void startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName);
void cleanupFitting(); void cleanupFitting();
void updateBestParametersToTable(); void refreshParametersFromModel();
private: private:
@ -101,10 +101,6 @@ private:
// 数据成员 // 数据成员
nmDataReservoir reservoirData; nmDataReservoir reservoirData;
QVector<nmDataVerticalWell> m_verticalWells;
QVector<nmDataHorizontalWell> m_horizontalWells;
QVector<nmDataVerticalFracturedWell> m_verticalFracturedWells;
QVector<nmDataHorizontalFracturedWell> m_horizontalFracturedWells;
nmDataAutomaticFitting automaticFittingData; nmDataAutomaticFitting automaticFittingData;
// 自动拟合相关成员 // 自动拟合相关成员

@ -210,15 +210,25 @@ static double fromTrustRegionCoordinate(double coordinate,
double lower, double lower,
double upper) double upper)
{ {
// 候选内部坐标先限制在 [0,1],再执行上述映射的逆变换,保证写回
// DataManager 的参数始终位于用户设置的物理范围内。
coordinate = qMax(0.0, qMin(1.0, coordinate)); 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)) { 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 enum TrustRegionErrorComponent

@ -301,7 +301,7 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
} }
} }
// 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。 // 按当前表格中的初值为所有参数建立建议范围,首次加载和拟合完成后共用。
void nmWxAutomaticFitting::initializeSuggestedParameterRanges() void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
{ {
if(!m_parameterTable) { if(!m_parameterTable) {
@ -484,25 +484,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
{ {
DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16)); DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16));
// 加载已有井数据
QVector<nmDataWellBase*> listWellData = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList();
// 遍历并分类井数据
foreach (nmDataWellBase* well, listWellData) {
if (auto vfWell = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
m_verticalFracturedWells.append(*vfWell);
}
else if (auto hfWell = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
m_horizontalFracturedWells.append(*hfWell);
}
else if (auto vWell = dynamic_cast<nmDataVerticalWell*>(well)) {
m_verticalWells.append(*vWell);
}
else if (auto hWell = dynamic_cast<nmDataHorizontalWell*>(well)) {
m_horizontalWells.append(*hWell);
}
}
// 获取数据 // 获取数据
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy(); reservoirData = pManager->getReservoirDataCopy();
@ -902,7 +883,7 @@ void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
return; return;
} }
// 用户改动范围后,切井和拟合结果不再自动覆盖这组手工范围。 // 手工范围用于本轮拟合,切井时保留;拟合完成后再按最新参数自动生成范围。
if(item->column() == 2 || item->column() == 4) { if(item->column() == 2 || item->column() == 4) {
m_autoParameterRanges = false; m_autoParameterRanges = false;
} }
@ -1030,105 +1011,55 @@ void nmWxAutomaticFitting::onReject()
void nmWxAutomaticFitting::onWellSelected(int index) void nmWxAutomaticFitting::onWellSelected(int index)
{ {
// 获取选中的井名 // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。
QString selectedWellName = m_targetWellCombo->itemText(index); nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(!manager) {
// 在分类的井数据中查找匹配的井 return;
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;
}
} }
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index));
// 查找水平井 if(!targetWell) {
if (!found) { return;
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;
}
}
} }
// 查找垂直压裂井 const bool wasUpdatingRanges = m_updatingParameterRanges;
if (!found) { m_updatingParameterRanges = true;
for(int i = 0; i < m_verticalFracturedWells.size(); ++i) { updateParameterVisibility(m_parameterTable, manager->getSolverModelType());
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;
}
}
}
// 查找水平压裂井 nmDataPerforation* perforation = targetWell->getPerforation(0);
if (!found) { if(perforation) {
for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) { const double skin = perforation->getSkin().getValue().toDouble();
if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) { m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10));
skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); if(m_autoParameterRanges) {
wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble(); updateRangeForParameter(1, skin);
fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble();
fractureHalfLengthValue = m_horizontalFracturedWells[i].getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
found = true;
break;
}
} }
} }
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) { bool fracturedWell = false;
// 先按目标井类型刷新可见行,再写入当前井的井级初值。 double conductivity = 0.0;
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); double halfLength = 0.0;
if(manager) { if(nmDataVerticalFracturedWell* well = dynamic_cast<nmDataVerticalFracturedWell*>(targetWell)) {
updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); conductivity = well->getDfc().getValue().toDouble();
} halfLength = well->getFractureHalfLength().getValue().toDouble();
// 更新表格数据 fracturedWell = true;
// 确保表格项存在 } else if(nmDataHorizontalFracturedWell* well = dynamic_cast<nmDataHorizontalFracturedWell*>(targetWell)) {
if(!m_parameterTable->item(1, 3)) { conductivity = well->getDfc().getValue().toDouble();
m_parameterTable->setItem(1, 3, new QTableWidgetItem()); halfLength = well->getFractureHalfLength().getValue().toDouble();
} fracturedWell = true;
if(!m_parameterTable->item(2, 3)) { }
m_parameterTable->setItem(2, 3, new QTableWidgetItem()); if(fracturedWell) {
} m_parameterTable->item(5, 3)->setText(QString::number(conductivity, 'g', 10));
m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10));
// 设置皮肤系数(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));
}
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue); updateRangeForParameter(5, conductivity);
updateRangeForParameter(2, wellboreStorageValue); updateRangeForParameter(6, halfLength);
if(fracturedWell) {
updateRangeForParameter(5, fractureConductivityValue);
updateRangeForParameter(6, fractureHalfLengthValue);
}
} }
} }
m_updatingParameterRanges = wasUpdatingRanges;
} }
void nmWxAutomaticFitting::setAutomaticFittingValue() void nmWxAutomaticFitting::setAutomaticFittingValue()
@ -1361,10 +1292,9 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag
if(manager && manager->getAttrRegistry()) { if(manager && manager->getAttrRegistry()) {
manager->getAttrRegistry()->refreshAll(); manager->getAttrRegistry()->refreshAll();
} }
// 失败状态不代表参数未写回;初值与范围始终以模型实际保留的数据为准。
refreshParametersFromModel();
if(success) { if(success) {
// 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。
updateBestParametersToTable();
QString resultInfo; QString resultInfo;
if(m_autoFitterPSO || m_autoFitterLM) { if(m_autoFitterPSO || m_autoFitterLM) {
@ -1479,47 +1409,32 @@ void nmWxAutomaticFitting::cleanupFitting()
DEBUG_UI("=== CLEANUP FITTING END ==="); DEBUG_UI("=== CLEANUP FITTING END ===");
} }
void nmWxAutomaticFitting::updateBestParametersToTable() void nmWxAutomaticFitting::refreshParametersFromModel()
{ {
QVector<double> bestSolution; nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(!manager) {
// 获取最佳解决方案 return;
if (m_autoFitterPSO) {
bestSolution = m_autoFitterPSO->getBestSolution();
} else if(m_autoFitterLM) {
bestSolution = m_autoFitterLM->getBestSolution();
}
if (bestSolution.isEmpty()) return;
// 获取启用的参数索引
QVector<int> 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::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(); nmWxParameterProperty::notifyUpdateTable();
} }

Loading…
Cancel
Save