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

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

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

@ -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<double> 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<const nmPebiResultSnapshot> m_solvedSnapshot;
nmDataAutomaticFitting automaticFittingData;
nmSensitivityFitSettings* m_sensitivitySettings;
QStringList m_sampledParameters;

@ -1028,6 +1028,37 @@ double nmCalculationDllPebiSolverTask::calculatePressureRmse(
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()
{
m_pSensitivityResult->m_runs.clear();

@ -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,28 +728,13 @@ 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<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());
}
}
// 井列表也取自求解快照,未求解的新井不作为旧模型的拟合目标。
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)));
@ -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<NM_SOLVER_MODEL_TYPE>(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<nmDataVerticalFracturedWell*>(targetWell)) {
conductivity = well->getDfc().getValue().toDouble();
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) {
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;

@ -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 nmPebiResultSnapshot>();
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."));
}

@ -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()

Loading…
Cancel
Save