fix(nmNum): 使用求解快照初始化拟合参数

- 从成功求解快照读取初值,沿用范围限制。
- 切井读取对应快照,新模型重新初始化设置。
- 无快照时禁用拟合,未选拟合参数时禁用生成。
feature/Sensitivity-20261012
lvjunjie 1 week ago
parent 525b65ec2a
commit cf9859c83f

@ -134,6 +134,9 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
// 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。 // 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。
static double calculatePressureRmse(const QVector<QVector<double> >& measured, static double calculatePressureRmse(const QVector<QVector<double> >& measured,
const QVector<QVector<double> >& simulated); const QVector<QVector<double> >& simulated);
// 从最后成功输入的裂缝端点读取半长,不访问当前编辑中的井对象。
static bool getSolvedFractureHalfLength(const QSharedPointer<const nmPebiSolverInputSnapshot>& input,
const QString& wellCode, double& halfLength);
private: private:
/** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */

@ -27,6 +27,7 @@
#include "nmWxAutomaticfittingStart.h" #include "nmWxAutomaticfittingStart.h"
#include "nmSubWxs_global.h" #include "nmSubWxs_global.h"
#include "nmPebiResultSnapshot.h"
// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。 // 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。
struct nmSensitivityFitSettings struct nmSensitivityFitSettings
@ -35,6 +36,7 @@ struct nmSensitivityFitSettings
bool confirmed; bool confirmed;
nmDataAutomaticFitting parameters; nmDataAutomaticFitting parameters;
QString targetWell; QString targetWell;
QString snapshotId;
QVector<double> initialValues; QVector<double> initialValues;
}; };
@ -70,6 +72,7 @@ private:
void setupButtons(); void setupButtons();
void setAutomaticFittingValue(); void setAutomaticFittingValue();
void loadSensitivitySettings(); void loadSensitivitySettings();
const nmPebiResultWellSnapshot* solvedWell(int index) const;
void setParameterRowVisible(QTableWidget* table, int row, bool visible); void setParameterRowVisible(QTableWidget* table, int row, bool visible);
void renumberVisibleParameterRows(QTableWidget* table); void renumberVisibleParameterRows(QTableWidget* table);
void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType);
@ -113,6 +116,7 @@ private:
// 数据成员 // 数据成员
nmDataReservoir reservoirData; nmDataReservoir reservoirData;
QSharedPointer<const nmPebiResultSnapshot> m_solvedSnapshot;
nmDataAutomaticFitting automaticFittingData; nmDataAutomaticFitting automaticFittingData;
nmSensitivityFitSettings* m_sensitivitySettings; nmSensitivityFitSettings* m_sensitivitySettings;
QStringList m_sampledParameters; QStringList m_sampledParameters;

@ -1028,6 +1028,37 @@ double nmCalculationDllPebiSolverTask::calculatePressureRmse(
return calculateSensitivityPressureRmse(measured, simulated, nullptr); return calculateSensitivityPressureRmse(measured, simulated, nullptr);
} }
bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength(
const QSharedPointer<const nmPebiSolverInputSnapshot>& 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<double>* 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() bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
{ {
m_pSensitivityResult->m_runs.clear(); m_pSensitivityResult->m_runs.clear();

@ -1,6 +1,7 @@
#include "nmWxAutomaticFitting.h" #include "nmWxAutomaticFitting.h"
#include "nmCalculationAutoFitPSO.h" #include "nmCalculationAutoFitPSO.h"
#include "nmCalculationAutoFitLM.h" #include "nmCalculationAutoFitLM.h"
#include "nmCalculationDllPebiSolverTask.h"
#include "nmWxAutomaticfittingStart.h" #include "nmWxAutomaticfittingStart.h"
#include "nmWxParameterProperty.h" #include "nmWxParameterProperty.h"
#include "nmAttrRegistry.h" #include "nmAttrRegistry.h"
@ -132,16 +133,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选, // Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。 // 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
bool showFractureConductivity = false; bool showFractureConductivity = false;
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); const nmPebiResultWellSnapshot* well = solvedWell(m_targetWellCombo->currentIndex());
if(manager && m_targetWellCombo) { if(well) showFractureConductivity = well->m_bHasDfc;
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, 5, showFractureConductivity); // Dfc
setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长 setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长
@ -430,6 +423,12 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
// 获取数据 // 获取数据
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy(); 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(); automaticFittingData = pManager->getAutomaticFittingDataCopy();
// 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true; m_autoParameterRanges = true;
@ -447,7 +446,9 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
} }
// 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。
initializeSuggestedParameterRanges(); 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; m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed"); DEBUG_UI("AutoFitting Constructor completed");
@ -455,7 +456,7 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
void nmWxAutomaticFitting::loadSensitivitySettings() void nmWxAutomaticFitting::loadSensitivitySettings()
{ {
// 先恢复目标井,再覆盖默认范围与初值,保留上次确认的完整设置。 // 同一次模型的拟合设置仍恢复用户确认的范围与初值。
m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell));
automaticFittingData = m_sensitivitySettings->parameters; automaticFittingData = m_sensitivitySettings->parameters;
m_autoParameterRanges = false; m_autoParameterRanges = false;
@ -727,28 +728,13 @@ void nmWxAutomaticFitting::setupControlPanel()
m_targetWellCombo = new QComboBox(); m_targetWellCombo = new QComboBox();
// 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。 // 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); // 井列表也取自求解快照,未求解的新井不作为旧模型的拟合目标。
if(pManager != nullptr) { for(int i = 0; !m_solvedSnapshot.isNull() && i < m_solvedSnapshot->getWellCount(); ++i) {
const QString sPrimaryWellCode = pManager->getPrimaryWellCode(); const nmPebiResultWellSnapshot* well = m_solvedSnapshot->getWellAt(i);
nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode); if(well->m_sWellCode == m_solvedSnapshot->getSensitivityWellCode())
if(pPrimaryWell != nullptr) { m_targetWellCombo->insertItem(0, well->m_sWellName, well->m_sWellInstanceId);
m_targetWellCombo->addItem(pPrimaryWell->getWellName()); else if(well->m_eWellMode == NM_CaseWell_RateControlled && !well->m_oCurves.m_vecHistoryPressure.isEmpty())
} m_targetWellCombo->addItem(well->m_sWellName, well->m_sWellInstanceId);
if(pManager->getIncludeOtherWells()) {
const QVector<nmCalculationWellRef> 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))); connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
@ -883,6 +869,7 @@ void nmWxAutomaticFitting::onAccept()
// 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。 // 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。
m_sensitivitySettings->parameters = automaticFittingData; m_sensitivitySettings->parameters = automaticFittingData;
m_sensitivitySettings->targetWell = m_targetWellCombo->currentText(); m_sensitivitySettings->targetWell = m_targetWellCombo->currentText();
m_sensitivitySettings->snapshotId = m_solvedSnapshot->getSnapshotId();
m_sensitivitySettings->initialValues.clear(); m_sensitivitySettings->initialValues.clear();
const int rows[] = {0, 1, 2, 3, 5, 6}; const int rows[] = {0, 1, 2, 3, 5, 6};
for(int i = 0; i < 6; ++i) for(int i = 0; i < 6; ++i)
@ -991,49 +978,43 @@ void nmWxAutomaticFitting::onReject()
reject(); 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) void nmWxAutomaticFitting::onWellSelected(int index)
{ {
// 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。 // 切井也读取同一次成功模型,默认范围继续使用原有生成及物理限制逻辑。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); const nmPebiResultWellSnapshot* targetWell = solvedWell(index);
if(!manager) {
return;
}
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index));
if(!targetWell) { if(!targetWell) {
return; return;
} }
const bool wasUpdatingRanges = m_updatingParameterRanges; const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true; m_updatingParameterRanges = true;
updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); updateParameterVisibility(m_parameterTable,
static_cast<NM_SOLVER_MODEL_TYPE>(m_solvedSnapshot->getSolverSettings().m_nSolverModelType));
nmDataPerforation* perforation = targetWell->getPerforation(0); if(targetWell->m_bHasSkin) {
if(perforation) { const double skin = targetWell->m_dSkin;
const double skin = perforation->getSkin().getValue().toDouble();
m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10)); m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
updateRangeForParameter(1, skin); 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)); m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
updateRangeForParameter(2, storage); updateRangeForParameter(2, storage);
} }
bool fracturedWell = false; const double conductivity = targetWell->m_dDfc;
double conductivity = 0.0;
double halfLength = 0.0; double halfLength = 0.0;
if(nmDataVerticalFracturedWell* well = dynamic_cast<nmDataVerticalFracturedWell*>(targetWell)) { if(targetWell->m_bHasDfc && nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength(
conductivity = well->getDfc().getValue().toDouble(); m_solvedSnapshot->getSensitivityInput(), targetWell->m_sWellCode, halfLength)) {
halfLength = well->getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
} else if(nmDataHorizontalFracturedWell* well = dynamic_cast<nmDataHorizontalFracturedWell*>(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(5, 3)->setText(QString::number(conductivity, 'g', 10));
m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10)); m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
@ -1406,8 +1387,12 @@ void nmWxAutomaticFitting::refreshParametersFromModel()
return; return;
} }
// 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。 // 拟合结束后重新取得最后成功快照,失败时仍沿用之前成功模型的初值。
reservoirData = manager->getReservoirDataCopy(); 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; const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true; m_updatingParameterRanges = true;
m_parameterTable->item(0, 3)->setText(QString::number( m_parameterTable->item(0, 3)->setText(QString::number(
@ -1415,8 +1400,7 @@ void nmWxAutomaticFitting::refreshParametersFromModel()
m_parameterTable->item(3, 3)->setText(QString::number( m_parameterTable->item(3, 3)->setText(QString::number(
reservoirData.getPorosity().getValue().toDouble(), 'g', 10)); reservoirData.getPorosity().getValue().toDouble(), 'g', 10));
// 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。 // 先刷新快照初值,再统一重建范围,避免即时校验读到新旧值混合的表格。
// 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。
m_autoParameterRanges = false; m_autoParameterRanges = false;
onWellSelected(m_targetWellCombo->currentIndex()); onWellSelected(m_targetWellCombo->currentIndex());
m_autoParameterRanges = true; m_autoParameterRanges = true;

@ -1435,6 +1435,9 @@ void nmWxNumericalDesign::updateTimeDependentSkinDisplay()
void nmWxNumericalDesign::onAutomaticFittingClicked() void nmWxNumericalDesign::onAutomaticFittingClicked()
{ {
// 与敏感性分析一样,成功生成模型后才有可用的拟合初值快照。
updateSensitiveButtonEnabled();
if(!m_pAutomaticfittingBtn->isEnabled()) return;
nmWxAutomaticFitting* dlg = new nmWxAutomaticFitting; nmWxAutomaticFitting* dlg = new nmWxAutomaticFitting;
dlg->show(); dlg->show();
} }
@ -1452,6 +1455,7 @@ void nmWxNumericalDesign::updateSensitiveButtonEnabled()
? m_pDataManager->getPebiResultSnapshot() : QSharedPointer<const nmPebiResultSnapshot>(); ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer<const nmPebiResultSnapshot>();
const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull(); const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull();
m_pSensitiveBtn->setEnabled(available); m_pSensitiveBtn->setEnabled(available);
m_pAutomaticfittingBtn->setEnabled(available);
m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis.")); m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis."));
} }

@ -1491,9 +1491,16 @@ void nmWxSensitive::updateModelCount()
const bool monteCarlo = m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO; const bool monteCarlo = m_sensitiveData.getCalculationType() == nmDataSensitive::CALC_MONTE_CARLO;
modelCountLabel->setVisible(!monteCarlo); modelCountLabel->setVisible(!monteCarlo);
m_monteCarloCount->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 && generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning &&
(calculationTypeCombo->currentIndex() != 3 || !m_improveSettings.isNull())); (calculationTypeCombo->currentIndex() != 3 || hasFittingParameters));
} }
bool nmWxSensitive::saveDataFromUI() bool nmWxSensitive::saveDataFromUI()

Loading…
Cancel
Save