|
|
|
|
@ -14,6 +14,7 @@
|
|
|
|
|
#include "nmDataFracture.h"
|
|
|
|
|
#include "nmDataFault.h"
|
|
|
|
|
#include "nmDataTimeStepSetting.h"
|
|
|
|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
|
|
|
|
|
|
|
#include "nmCalculationPebiGrid.h"
|
|
|
|
|
#include "nmCalculationUtils.h"
|
|
|
|
|
@ -24,11 +25,13 @@
|
|
|
|
|
#include <QHash>
|
|
|
|
|
#include <QMutexLocker>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
#include <QUuid>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <map>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <new>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <float.h>
|
|
|
|
|
@ -36,6 +39,7 @@
|
|
|
|
|
|
|
|
|
|
#include <vtkSmartPointer.h>
|
|
|
|
|
#include <vtkDoubleArray.h>
|
|
|
|
|
#include <vtkCellData.h>
|
|
|
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -50,7 +54,8 @@ struct nmPebiSolverInputSnapshot
|
|
|
|
|
m_bRequiresGridCalculation(false),
|
|
|
|
|
m_bGridResultNeedsCommit(false),
|
|
|
|
|
m_bMayUseCachedGrid(false),
|
|
|
|
|
m_bAutoFitTargetOnly(false)
|
|
|
|
|
m_bAutoFitTargetOnly(false),
|
|
|
|
|
m_bResultMetadataCaptured(false)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -62,10 +67,17 @@ struct nmPebiSolverInputSnapshot
|
|
|
|
|
int m_nSolverType; ///< PEBI 求解器实现类型。
|
|
|
|
|
int m_nOmpThreads; ///< CPU 加速求解线程数。
|
|
|
|
|
int m_nIluReuseSteps; ///< ILU 预条件复用步数。
|
|
|
|
|
/** @brief 完整场结果使用的求解时储层值。 */
|
|
|
|
|
nmPebiResultReservoirParameters m_oResultReservoirParameters;
|
|
|
|
|
/** @brief 完整场结果使用的常量值和全部 PVT 数组。 */
|
|
|
|
|
nmPebiResultPvtParameters m_oResultPvtParameters;
|
|
|
|
|
/** @brief 完整场结果页面使用的求解与时间步设置。 */
|
|
|
|
|
nmPebiResultSolverSettings m_oResultSolverSettings;
|
|
|
|
|
bool m_bRequiresGridCalculation; ///< 后台是否需先用网格值快照计算一次网格。
|
|
|
|
|
bool m_bGridResultNeedsCommit; ///< 完整求解成功后是否需在主线程提交新网格。
|
|
|
|
|
bool m_bMayUseCachedGrid; ///< 捕获时是否存在同版本网格缓存。
|
|
|
|
|
bool m_bAutoFitTargetOnly; ///< true 时只构造目标井临时曲线,不提交成果。
|
|
|
|
|
bool m_bResultMetadataCaptured; ///< 正式结果元数据是否已在主线程完整冻结。
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -164,6 +176,159 @@ bool isFiniteSolverNumber(double value)
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isDisplayResultWell(
|
|
|
|
|
const nmDataNumericalAnalysisCase* pAnalysisCase,
|
|
|
|
|
const QString& sWellCode)
|
|
|
|
|
{
|
|
|
|
|
if(pAnalysisCase == NULL || sWellCode.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return pAnalysisCase->getPrimaryWellCode() == sWellCode ||
|
|
|
|
|
(pAnalysisCase->getIncludeOtherWells() &&
|
|
|
|
|
pAnalysisCase->isIncludedWell(sWellCode));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool captureResultWellMetadata(
|
|
|
|
|
nmDataWellBase* pWellData,
|
|
|
|
|
bool bDisplayResultWell,
|
|
|
|
|
nmPebiWellInputSnapshot& oWellInput)
|
|
|
|
|
{
|
|
|
|
|
if(pWellData == NULL || !oWellInput.m_bRealWell ||
|
|
|
|
|
pWellData->getWellCode() != oWellInput.m_sWellCode ||
|
|
|
|
|
pWellData->getWellInstanceId().isEmpty() ||
|
|
|
|
|
QUuid(pWellData->getWellInstanceId()).isNull()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 正式结果所需井值在主线程一次冻结;后台只读取本结构。
|
|
|
|
|
oWellInput.m_sWellInstanceId = pWellData->getWellInstanceId();
|
|
|
|
|
oWellInput.m_sWellName = pWellData->getWellName();
|
|
|
|
|
oWellInput.m_vecHistoryPressure = pWellData->getHistoryPressure();
|
|
|
|
|
oWellInput.m_vecHistoryLogLog = pWellData->getHistoryLogLog();
|
|
|
|
|
oWellInput.m_vecHistorySemiLog = pWellData->getHistorySemiLog();
|
|
|
|
|
oWellInput.m_dRadius =
|
|
|
|
|
pWellData->getRadius().getValue().toDouble();
|
|
|
|
|
oWellInput.m_bHasPerforation =
|
|
|
|
|
pWellData->getPerforationCount() > 0;
|
|
|
|
|
oWellInput.m_bDisplayResultWell = bDisplayResultWell;
|
|
|
|
|
|
|
|
|
|
nmDataVerticalFracturedWell* pVerticalFracturedWell =
|
|
|
|
|
dynamic_cast<nmDataVerticalFracturedWell*>(pWellData);
|
|
|
|
|
nmDataHorizontalFracturedWell* pHorizontalFracturedWell =
|
|
|
|
|
dynamic_cast<nmDataHorizontalFracturedWell*>(pWellData);
|
|
|
|
|
oWellInput.m_bHasDfc = pVerticalFracturedWell != NULL ||
|
|
|
|
|
pHorizontalFracturedWell != NULL;
|
|
|
|
|
if(pVerticalFracturedWell != NULL) {
|
|
|
|
|
oWellInput.m_dDfc = pVerticalFracturedWell->getDfc()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
} else if(pHorizontalFracturedWell != NULL) {
|
|
|
|
|
oWellInput.m_dDfc = pHorizontalFracturedWell->getDfc()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool captureResultParameters(
|
|
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
|
|
nmPebiSolverInputSnapshot& oSnapshot)
|
|
|
|
|
{
|
|
|
|
|
if(pDataManager == NULL ||
|
|
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmDataReservoir* pReservoir = pDataManager->getReservoirData();
|
|
|
|
|
if(pReservoir == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmPebiResultReservoirParameters& oReservoir =
|
|
|
|
|
oSnapshot.m_oResultReservoirParameters;
|
|
|
|
|
oReservoir.m_dInitialPressure = pReservoir->getInitialPressure()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dPermeability = pReservoir->getPermeability()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dThickness = pReservoir->getThickness()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dPorosity = pReservoir->getPorosity()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dTotalCompressibility = pReservoir->getCt()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dRockCompressibility = pReservoir->getCf()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dOilSaturation = pReservoir->getSoi()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dGasSaturation = pReservoir->getSgi()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oReservoir.m_dWaterSaturation = pReservoir->getSwi()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
|
|
|
|
|
nmPebiResultPvtParameters& oPvt =
|
|
|
|
|
oSnapshot.m_oResultPvtParameters;
|
|
|
|
|
oPvt.m_dConstantBo = pReservoir->getBo().getValue().toDouble();
|
|
|
|
|
oPvt.m_dConstantMiuo = pReservoir->getMiuo().getValue().toDouble();
|
|
|
|
|
oPvt.m_dConstantBg = pReservoir->getBg().getValue().toDouble();
|
|
|
|
|
oPvt.m_dConstantMiug = pReservoir->getMiug().getValue().toDouble();
|
|
|
|
|
oPvt.m_dConstantBw = pReservoir->getBw().getValue().toDouble();
|
|
|
|
|
oPvt.m_dConstantMiuw = pReservoir->getMiuw().getValue().toDouble();
|
|
|
|
|
|
|
|
|
|
nmDataPvtParaForPebi* pPvt = pDataManager->getPebiPvtPara();
|
|
|
|
|
if(pPvt != NULL) {
|
|
|
|
|
oPvt.m_bHasBubblePoint = true;
|
|
|
|
|
oPvt.m_dBubblePoint = pPvt->getPb().getValue().toDouble();
|
|
|
|
|
oPvt.m_vecPressure = pPvt->getPressure();
|
|
|
|
|
oPvt.m_vecRso = pPvt->getRso();
|
|
|
|
|
oPvt.m_vecBo = pPvt->getBo();
|
|
|
|
|
oPvt.m_vecCo = pPvt->getCo();
|
|
|
|
|
oPvt.m_vecMiuo = pPvt->getMiuo();
|
|
|
|
|
oPvt.m_vecRouo = pPvt->getRouo();
|
|
|
|
|
oPvt.m_vecRv = pPvt->getRv();
|
|
|
|
|
oPvt.m_vecBg = pPvt->getBg();
|
|
|
|
|
oPvt.m_vecCg = pPvt->getCg();
|
|
|
|
|
oPvt.m_vecMiug = pPvt->getMiug();
|
|
|
|
|
oPvt.m_vecRoug = pPvt->getRoug();
|
|
|
|
|
oPvt.m_vecZ = pPvt->getZ();
|
|
|
|
|
oPvt.m_vecRsw = pPvt->getRsw();
|
|
|
|
|
oPvt.m_vecBw = pPvt->getBw();
|
|
|
|
|
oPvt.m_vecCw = pPvt->getCw();
|
|
|
|
|
oPvt.m_vecMiuw = pPvt->getMiuw();
|
|
|
|
|
oPvt.m_vecRouw = pPvt->getRouw();
|
|
|
|
|
oPvt.m_vecV = pPvt->getV();
|
|
|
|
|
oPvt.m_vecKkInitial = pPvt->getKKinitial();
|
|
|
|
|
oPvt.m_vecCfCfInitial = pPvt->getCfCfinitial();
|
|
|
|
|
oPvt.m_vecSo = pPvt->getSo();
|
|
|
|
|
oPvt.m_vecKro = pPvt->getKro();
|
|
|
|
|
oPvt.m_vecSg = pPvt->getSg();
|
|
|
|
|
oPvt.m_vecKrg = pPvt->getKrg();
|
|
|
|
|
oPvt.m_vecSw = pPvt->getSw();
|
|
|
|
|
oPvt.m_vecKrw = pPvt->getKrw();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmPebiResultSolverSettings& oSettings =
|
|
|
|
|
oSnapshot.m_oResultSolverSettings;
|
|
|
|
|
oSettings.m_nSolverModelType =
|
|
|
|
|
static_cast<int>(pDataManager->getSolverModelType());
|
|
|
|
|
oSettings.m_nPebiSolverType = oSnapshot.m_nSolverType;
|
|
|
|
|
oSettings.m_nOmpThreads = oSnapshot.m_nOmpThreads;
|
|
|
|
|
oSettings.m_nIluReuseSteps = oSnapshot.m_nIluReuseSteps;
|
|
|
|
|
oSettings.m_dGridControl =
|
|
|
|
|
oSnapshot.m_oGridInput.m_oGridInput.GridControl;
|
|
|
|
|
|
|
|
|
|
nmDataTimeStepSetting* pTimeStep = pDataManager->getTimeStep();
|
|
|
|
|
if(pTimeStep != NULL) {
|
|
|
|
|
oSettings.m_bHasTimeStepSettings = true;
|
|
|
|
|
oSettings.m_dTimeGrowthExponent = pTimeStep->getTimeGrowthExponent()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oSettings.m_dMinDeltaT = pTimeStep->getMinDeltaTAttribute()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
oSettings.m_dMaxDeltaT = pTimeStep->getMaxDeltaTAttribute()
|
|
|
|
|
.getValue().toDouble();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oSnapshot.m_bResultMetadataCaptured = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将启用的数据组插值到全部网格单元中心,并覆盖对应的求解器属性数组.
|
|
|
|
|
bool applyPropertyInterpolation(
|
|
|
|
|
HX_NWTM_MODEL_INPUT& modelInput,
|
|
|
|
|
@ -421,7 +586,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
|
|
m_pDataManager(pDataManager != nullptr
|
|
|
|
|
? pDataManager
|
|
|
|
|
: nmDataAnalyzeManager::getCurrentInstance()),
|
|
|
|
|
m_pInputSnapshot(new nmPebiSolverInputSnapshot()),
|
|
|
|
|
m_pInputSnapshot(nullptr),
|
|
|
|
|
m_pManualCaptureState(nullptr),
|
|
|
|
|
m_bManagerUseActive(false),
|
|
|
|
|
m_bInputSnapshotValid(false),
|
|
|
|
|
@ -436,19 +601,31 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
|
|
m_nCancelRequested(0),
|
|
|
|
|
m_nWasCancelled(0)
|
|
|
|
|
{
|
|
|
|
|
// 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。
|
|
|
|
|
if(m_pDataManager != nullptr) {
|
|
|
|
|
m_pDataManager->beginBackgroundUse();
|
|
|
|
|
m_bManagerUseActive = true;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
m_pInputSnapshot = new nmPebiSolverInputSnapshot();
|
|
|
|
|
|
|
|
|
|
if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) {
|
|
|
|
|
// 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。
|
|
|
|
|
m_pManualCaptureState = new nmPebiManualCaptureState();
|
|
|
|
|
} else {
|
|
|
|
|
// 自动拟合以及旧调用路径继续一次性同步捕获,保持原来的评价行为。
|
|
|
|
|
m_bInputSnapshotValid =
|
|
|
|
|
captureInputSnapshot(sAutoFitTargetWellName);
|
|
|
|
|
// 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。
|
|
|
|
|
if(m_pDataManager != nullptr) {
|
|
|
|
|
m_pDataManager->beginBackgroundUse();
|
|
|
|
|
m_bManagerUseActive = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) {
|
|
|
|
|
// 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。
|
|
|
|
|
m_pManualCaptureState = new nmPebiManualCaptureState();
|
|
|
|
|
} else {
|
|
|
|
|
// 自动拟合以及旧调用路径继续一次性同步捕获,保持原来的评价行为。
|
|
|
|
|
m_bInputSnapshotValid =
|
|
|
|
|
captureInputSnapshot(sAutoFitTargetWellName);
|
|
|
|
|
}
|
|
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
|
|
// 构造阶段内存不足时保留旧成果,任务保持无效并由既有完成流程报错。
|
|
|
|
|
qWarning() << "Cannot allocate PEBI solver input snapshot.";
|
|
|
|
|
delete m_pManualCaptureState;
|
|
|
|
|
m_pManualCaptureState = nullptr;
|
|
|
|
|
delete m_pInputSnapshot;
|
|
|
|
|
m_pInputSnapshot = nullptr;
|
|
|
|
|
m_bInputSnapshotValid = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -470,11 +647,19 @@ void nmCalculationDllPebiSolverTask::run()
|
|
|
|
|
{
|
|
|
|
|
m_nSolveTimeMs = -1;
|
|
|
|
|
m_nPebiCount = -1;
|
|
|
|
|
bool bSucceeded = this->execute();
|
|
|
|
|
bool bSucceeded = false;
|
|
|
|
|
try {
|
|
|
|
|
bSucceeded = this->execute();
|
|
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
|
|
// VTK 部分分配接口没有返回值,统一在任务边界拒绝候选并保留旧快照。
|
|
|
|
|
qWarning() << "Cannot allocate PEBI solver result snapshot.";
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
bSucceeded = false;
|
|
|
|
|
}
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
|
|
// 停止只丢弃任务局部结果,主线程不会进入 commitResult()。
|
|
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(1);
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
bSucceeded = false;
|
|
|
|
|
}
|
|
|
|
|
m_lastRunSucceeded = bSucceeded;
|
|
|
|
|
@ -643,6 +828,14 @@ bool nmCalculationDllPebiSolverTask::captureManualInputStep(
|
|
|
|
|
m_pInputSnapshot->m_oGridInput)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(m_pInputSnapshot->m_oGridInput.m_vecWellInputs.isEmpty() ||
|
|
|
|
|
!captureResultWellMetadata(
|
|
|
|
|
pWellData,
|
|
|
|
|
isDisplayResultWell(pAnalysisCase,
|
|
|
|
|
pWellData->getWellCode()),
|
|
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs.last())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++pState->m_nNextWellIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -682,6 +875,10 @@ bool nmCalculationDllPebiSolverTask::captureManualInputStep(
|
|
|
|
|
m_pDataManager->getPebiOmpThreads();
|
|
|
|
|
m_pInputSnapshot->m_nIluReuseSteps =
|
|
|
|
|
m_pDataManager->getPebiIluReuseSteps();
|
|
|
|
|
if(!captureResultParameters(m_pDataManager,
|
|
|
|
|
*m_pInputSnapshot)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
pState->m_ePhase =
|
|
|
|
|
nmPebiManualCaptureState::CapturePhase_GridCache;
|
|
|
|
|
return true;
|
|
|
|
|
@ -793,6 +990,35 @@ bool nmCalculationDllPebiSolverTask::captureInputSnapshot(
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自动拟合候选保持原来的轻量输入;只有最终完整场求解冻结正式结果元数据。
|
|
|
|
|
if(!m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
|
|
QHash<QString, nmDataWellBase*> mapWellsByCode;
|
|
|
|
|
const QVector<nmDataWellBase*> vecWells =
|
|
|
|
|
m_pDataManager->getWellDataList();
|
|
|
|
|
for(int nIndex = 0; nIndex < vecWells.size(); ++nIndex) {
|
|
|
|
|
nmDataWellBase* pWellData = vecWells[nIndex];
|
|
|
|
|
if(pWellData != NULL && !pWellData->getWellCode().isEmpty()) {
|
|
|
|
|
mapWellsByCode.insert(pWellData->getWellCode(), pWellData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for(int nIndex = 0; nIndex < vecWellOrder.size(); ++nIndex) {
|
|
|
|
|
const nmSolverWellRef& oWellRef = vecWellOrder[nIndex];
|
|
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
|
|
mapWellsByCode.value(oWellRef.m_sWellCode, NULL);
|
|
|
|
|
if(pWellData == NULL ||
|
|
|
|
|
!captureResultWellMetadata(
|
|
|
|
|
pWellData,
|
|
|
|
|
isDisplayResultWell(pAnalysisCase,
|
|
|
|
|
oWellRef.m_sWellCode),
|
|
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs[nIndex])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三步:复制属性插值及 DLL 求解配置。这些设置只影响模型求解结果,
|
|
|
|
|
// 不应在工作线程中再次从 DataManager 查询。
|
|
|
|
|
m_pInputSnapshot->m_vecPropertyDataSets =
|
|
|
|
|
@ -803,6 +1029,10 @@ bool nmCalculationDllPebiSolverTask::captureInputSnapshot(
|
|
|
|
|
m_pDataManager->getPebiOmpThreads();
|
|
|
|
|
m_pInputSnapshot->m_nIluReuseSteps =
|
|
|
|
|
m_pDataManager->getPebiIluReuseSteps();
|
|
|
|
|
if(!m_pInputSnapshot->m_bAutoFitTargetOnly &&
|
|
|
|
|
!captureResultParameters(m_pDataManager, *m_pInputSnapshot)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第四步:主线程仅检查缓存并捕获基础网格的智能指针。生成后的基础网格
|
|
|
|
|
// 不在原位修改,智能指针保证 DataManager 替换网格后旧对象仍存活;真正的
|
|
|
|
|
@ -957,7 +1187,9 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
m_vecPendingWellResults.clear();
|
|
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
|
|
m_mapPendingWellLocations.clear();
|
|
|
|
|
m_pPendingResultGrid = nullptr;
|
|
|
|
|
m_pPendingResultSnapshot.clear();
|
|
|
|
|
m_autoFitResultPressure.clear();
|
|
|
|
|
m_autoFitResultLogLog.clear();
|
|
|
|
|
m_autoFitResultSemiLog.clear();
|
|
|
|
|
@ -1598,8 +1830,201 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
|
|
|
|
|
|
|
|
|
|
m_dPendingScalarMin = dMinP;
|
|
|
|
|
m_dPendingScalarMax = dMaxP;
|
|
|
|
|
m_bPendingFullResultReady = true;
|
|
|
|
|
return true;
|
|
|
|
|
m_bPendingFullResultReady = buildPebiResultSnapshotCandidate();
|
|
|
|
|
if(!m_bPendingFullResultReady) {
|
|
|
|
|
// 候选结构或内存校验失败时立即释放大结果,旧快照保持原状。
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
}
|
|
|
|
|
return m_bPendingFullResultReady;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::discardPendingFullResult()
|
|
|
|
|
{
|
|
|
|
|
m_pPendingResultSnapshot.clear();
|
|
|
|
|
m_vecPendingWellResults.clear();
|
|
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
|
|
m_mapPendingWellLocations.clear();
|
|
|
|
|
m_pPendingResultGrid = NULL;
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
|
|
|
|
|
// 候选可能与输入基础网格共享 VTK 对象,丢弃或发布后必须清除任务侧别名。
|
|
|
|
|
if(m_pInputSnapshot != NULL &&
|
|
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
|
|
m_pInputSnapshot->m_pBaseGrid = NULL;
|
|
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid = NULL;
|
|
|
|
|
m_pInputSnapshot->m_oGridResult.m_pUnstructuredGrid = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate()
|
|
|
|
|
{
|
|
|
|
|
if(m_pInputSnapshot == NULL ||
|
|
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
|
|
!m_pInputSnapshot->m_bResultMetadataCaptured ||
|
|
|
|
|
m_pPendingResultGrid == NULL ||
|
|
|
|
|
m_mapPendingTimeSteps.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
nmPebiResultSnapshotBuilder oBuilder;
|
|
|
|
|
if(!oBuilder.setInputRevisions(m_nGridInputRevision,
|
|
|
|
|
m_nResultInputRevision)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Builder 接管同一 VTK 对象的候选引用;任务暂留一份引用供第三阶段
|
|
|
|
|
// 旧 UI 镜像发布,主线程提交后会立即清除全部任务侧别名。
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pResultGrid =
|
|
|
|
|
m_pPendingResultGrid;
|
|
|
|
|
if(!oBuilder.takeResultGrid(pResultGrid)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator
|
|
|
|
|
oTimeIt = m_mapPendingTimeSteps.constBegin();
|
|
|
|
|
for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) {
|
|
|
|
|
vtkSmartPointer<vtkDoubleArray> pPressure = oTimeIt.value();
|
|
|
|
|
if(!oBuilder.addPressureFrame(oTimeIt.key(), pPressure)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(!oBuilder.setScalarRange(m_dPendingScalarMin,
|
|
|
|
|
m_dPendingScalarMax)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVector<nmSolverWellRef>& vecSolverOrder =
|
|
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder;
|
|
|
|
|
QVector<nmPebiWellInputSnapshot>& vecWellInputs =
|
|
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs;
|
|
|
|
|
if(vecSolverOrder.size() != vecWellInputs.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList listDisplayWellIds;
|
|
|
|
|
m_mapPendingWellLocations.clear();
|
|
|
|
|
int nResultWellIndex = 0;
|
|
|
|
|
for(int nSlotIndex = 0;
|
|
|
|
|
nSlotIndex < vecSolverOrder.size();
|
|
|
|
|
++nSlotIndex) {
|
|
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nSlotIndex];
|
|
|
|
|
nmPebiWellInputSnapshot& oWellInput =
|
|
|
|
|
vecWellInputs[nSlotIndex];
|
|
|
|
|
|
|
|
|
|
nmPebiResultSolverSlot oSlot;
|
|
|
|
|
oSlot.m_nSolverIndex = nSlotIndex;
|
|
|
|
|
oSlot.m_eEntryKind = oWellRef.m_eEntryKind;
|
|
|
|
|
oSlot.m_sWellCode = oWellRef.m_sWellCode;
|
|
|
|
|
oSlot.m_eWellType = oWellRef.m_eWellType;
|
|
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
|
|
oSlot.m_sWellInstanceId = oWellInput.m_sWellInstanceId;
|
|
|
|
|
}
|
|
|
|
|
if(!oBuilder.addSolverSlot(oSlot)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(oWellRef.m_eEntryKind ==
|
|
|
|
|
NM_SolverEntry_ManualFracture) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(nResultWellIndex >= m_vecPendingWellResults.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmPebiWellResultSnapshot& oWellResult =
|
|
|
|
|
m_vecPendingWellResults[nResultWellIndex++];
|
|
|
|
|
if(!oWellInput.m_bRealWell ||
|
|
|
|
|
oWellInput.m_sWellCode != oWellRef.m_sWellCode ||
|
|
|
|
|
oWellResult.m_sWellCode != oWellRef.m_sWellCode) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmPebiResultWellSnapshot oWell;
|
|
|
|
|
oWell.m_sWellInstanceId = oWellInput.m_sWellInstanceId;
|
|
|
|
|
oWell.m_sWellCode = oWellInput.m_sWellCode;
|
|
|
|
|
oWell.m_sWellName = oWellInput.m_sWellName;
|
|
|
|
|
oWell.m_eWellType = oWellRef.m_eWellType;
|
|
|
|
|
oWell.m_eWellMode = oWellInput.m_bRateControlled
|
|
|
|
|
? NM_CaseWell_RateControlled
|
|
|
|
|
: NM_CaseWell_Observation;
|
|
|
|
|
oWell.m_oLocation = oWellInput.m_oLocation;
|
|
|
|
|
oWell.m_bHasPerforation = oWellInput.m_bHasPerforation;
|
|
|
|
|
oWell.m_bHasSkin = oWellInput.m_bHasPerforation;
|
|
|
|
|
oWell.m_bHasDfc = oWellInput.m_bHasDfc;
|
|
|
|
|
oWell.m_dRadius = oWellInput.m_dRadius;
|
|
|
|
|
oWell.m_dWellboreStorage =
|
|
|
|
|
oWellInput.m_dWellboreStorage;
|
|
|
|
|
oWell.m_dSkin = oWellInput.m_dSkin;
|
|
|
|
|
oWell.m_dDfc = oWellInput.m_dDfc;
|
|
|
|
|
|
|
|
|
|
// 六组曲线逐项交换进入候选,清空任务输入和局部结果中的可写容器。
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecHistoryPressure,
|
|
|
|
|
oWellInput.m_vecHistoryPressure);
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecHistoryLogLog,
|
|
|
|
|
oWellInput.m_vecHistoryLogLog);
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecHistorySemiLog,
|
|
|
|
|
oWellInput.m_vecHistorySemiLog);
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecResultPressure,
|
|
|
|
|
oWellResult.m_vecPressure);
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecResultLogLog,
|
|
|
|
|
oWellResult.m_vecLogLog);
|
|
|
|
|
qSwap(oWell.m_oCurves.m_vecResultSemiLog,
|
|
|
|
|
oWellResult.m_vecSemiLog);
|
|
|
|
|
|
|
|
|
|
m_mapPendingWellLocations.insert(oWell.m_sWellCode,
|
|
|
|
|
oWell.m_oLocation);
|
|
|
|
|
if(oWellInput.m_bDisplayResultWell) {
|
|
|
|
|
listDisplayWellIds.append(oWell.m_sWellInstanceId);
|
|
|
|
|
}
|
|
|
|
|
if(!oBuilder.takeWell(oWell)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(nResultWellIndex != m_vecPendingWellResults.size() ||
|
|
|
|
|
!oBuilder.setDisplayWellInstanceIds(listDisplayWellIds) ||
|
|
|
|
|
!oBuilder.setReservoirParameters(
|
|
|
|
|
m_pInputSnapshot->m_oResultReservoirParameters) ||
|
|
|
|
|
!oBuilder.setPvtParameters(
|
|
|
|
|
m_pInputSnapshot->m_oResultPvtParameters) ||
|
|
|
|
|
!oBuilder.setSolverSettings(
|
|
|
|
|
m_pInputSnapshot->m_oResultSolverSettings)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString sError;
|
|
|
|
|
if(!oBuilder.finalize(&sError)) {
|
|
|
|
|
qWarning() << "Cannot finalize PEBI result snapshot:"
|
|
|
|
|
<< sError;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三阶段旧动画仍会在结果网格上切换活动压力标量,不能把快照
|
|
|
|
|
// 内部基础网格直接交给它。CopyStructure 只建立独立网格壳并共享
|
|
|
|
|
// 已验证为只读的点和单元拓扑,不复制大网格,也不会共享 CellData。
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pLegacyResultGrid =
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
|
|
pLegacyResultGrid->CopyStructure(m_pPendingResultGrid);
|
|
|
|
|
pLegacyResultGrid->GetCellData()->Initialize();
|
|
|
|
|
if(pLegacyResultGrid->GetNumberOfCells() !=
|
|
|
|
|
m_pPendingResultGrid->GetNumberOfCells() ||
|
|
|
|
|
pLegacyResultGrid->GetCellData()->GetScalars() != NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pPendingResultSnapshot = oBuilder.takeCandidate();
|
|
|
|
|
if(m_pPendingResultSnapshot.isNull()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发布时 Manager 接收旧 UI 独占的网格壳;规范基础网格只留在快照中。
|
|
|
|
|
m_pPendingResultGrid = pLegacyResultGrid;
|
|
|
|
|
return true;
|
|
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
|
|
qWarning() << "Cannot allocate PEBI result snapshot candidate.";
|
|
|
|
|
m_pPendingResultSnapshot.clear();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::commitResult(
|
|
|
|
|
@ -1611,9 +2036,11 @@ bool nmCalculationDllPebiSolverTask::commitResult(
|
|
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
|
|
wasCancelled() || isCancelRequested() ||
|
|
|
|
|
!m_bPendingFullResultReady ||
|
|
|
|
|
m_pPendingResultSnapshot.isNull() ||
|
|
|
|
|
pDataManager == nullptr ||
|
|
|
|
|
pDataManager != m_pDataManager ||
|
|
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1625,24 +2052,26 @@ bool nmCalculationDllPebiSolverTask::commitResult(
|
|
|
|
|
m_mapPendingTimeSteps.isEmpty() ||
|
|
|
|
|
m_pPendingResultGrid == nullptr ||
|
|
|
|
|
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二步:在改动旧成果前一次性解析全部目标井,并检查 WellCode 不重复。
|
|
|
|
|
QVector<nmDataWellBase*> vecTargetWells;
|
|
|
|
|
QSet<QString> setWellCodes;
|
|
|
|
|
vecTargetWells.reserve(m_vecPendingWellResults.size());
|
|
|
|
|
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
|
|
|
|
|
const nmPebiWellResultSnapshot& oWellResult =
|
|
|
|
|
m_vecPendingWellResults[nIndex];
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
|
|
pDataManager->findWellByCode(oWellResult.m_sWellCode);
|
|
|
|
|
if(pWellData == nullptr ||
|
|
|
|
|
setWellCodes.contains(oWellResult.m_sWellCode)) {
|
|
|
|
|
// 第二步:即使外部遗漏版本递增,同编码新井也不能接收旧任务结果。
|
|
|
|
|
QSet<QString> setWellIds;
|
|
|
|
|
for(int nIndex = 0;
|
|
|
|
|
nIndex < m_pPendingResultSnapshot->getWellCount();
|
|
|
|
|
++nIndex) {
|
|
|
|
|
const nmPebiResultWellSnapshot* pWell =
|
|
|
|
|
m_pPendingResultSnapshot->getWellAt(nIndex);
|
|
|
|
|
nmDataWellBase* pLiveWell = pWell == NULL ? NULL :
|
|
|
|
|
pDataManager->findWellByCode(pWell->m_sWellCode);
|
|
|
|
|
if(pWell == NULL || pLiveWell == NULL ||
|
|
|
|
|
pLiveWell->getWellInstanceId() != pWell->m_sWellInstanceId ||
|
|
|
|
|
setWellIds.contains(pWell->m_sWellInstanceId)) {
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
setWellCodes.insert(oWellResult.m_sWellCode);
|
|
|
|
|
vecTargetWells.append(pWellData);
|
|
|
|
|
setWellIds.insert(pWell->m_sWellInstanceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三步:任务后台生成了新网格时,先在当前主线程按同一输入版本提交。
|
|
|
|
|
@ -1652,46 +2081,29 @@ bool nmCalculationDllPebiSolverTask::commitResult(
|
|
|
|
|
pDataManager,
|
|
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
|
|
m_pInputSnapshot->m_oGridResult)) {
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else if(!pAnalysisCase->isGridValid()) {
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第四步:最后一次登记版本。登记失败时旧成果仍未被修改;登记成功后当前
|
|
|
|
|
// 主线程事件不会被其他编辑操作插入,因此后续替换不存在可恢复失败分支。
|
|
|
|
|
if(!pAnalysisCase->markResultsAvailableIfCurrent(
|
|
|
|
|
m_nGridInputRevision, m_nResultInputRevision)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第五步:版本检查和对象解析全部通过后,在当前主线程事件内整体替换。
|
|
|
|
|
// 这些 setter 不发事件也不包含可恢复失败分支,外部只能在本函数返回后看到新成果。
|
|
|
|
|
pDataManager->clearWellLocations();
|
|
|
|
|
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
|
|
|
|
|
const nmPebiWellResultSnapshot& oWellResult =
|
|
|
|
|
m_vecPendingWellResults[nIndex];
|
|
|
|
|
nmDataWellBase* pWellData = vecTargetWells[nIndex];
|
|
|
|
|
pWellData->setResultPressure(oWellResult.m_vecPressure);
|
|
|
|
|
pWellData->setResultLogLog(oWellResult.m_vecLogLog);
|
|
|
|
|
pWellData->setResultSemiLog(oWellResult.m_vecSemiLog);
|
|
|
|
|
pDataManager->addWellLocation(oWellResult.m_sWellCode,
|
|
|
|
|
oWellResult.m_oLocation);
|
|
|
|
|
// 第四步:Manager 在一次发布点校验并替换规范快照及旧 UI 兼容引用。
|
|
|
|
|
QString sError;
|
|
|
|
|
const bool bCommitted = pDataManager->commitPebiResultSnapshot(
|
|
|
|
|
m_pPendingResultSnapshot,
|
|
|
|
|
m_mapPendingTimeSteps,
|
|
|
|
|
m_pPendingResultGrid,
|
|
|
|
|
m_mapPendingWellLocations,
|
|
|
|
|
&sError);
|
|
|
|
|
if(!bCommitted) {
|
|
|
|
|
qWarning() << "Cannot commit PEBI result snapshot:" << sError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pDataManager->clearTimeSteps();
|
|
|
|
|
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator oTimeIt =
|
|
|
|
|
m_mapPendingTimeSteps.constBegin();
|
|
|
|
|
for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) {
|
|
|
|
|
pDataManager->addTimeStep(oTimeIt.key(), oTimeIt.value());
|
|
|
|
|
}
|
|
|
|
|
pDataManager->setScalarRangeP(m_dPendingScalarMin,
|
|
|
|
|
m_dPendingScalarMax);
|
|
|
|
|
pDataManager->setResultBaseGrid(m_pPendingResultGrid);
|
|
|
|
|
|
|
|
|
|
// 第六步:清除待提交标志,防止重复完成信号再次覆盖后续结果。
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
return true;
|
|
|
|
|
// 成功和失败都不允许任务继续持有候选可写别名;旧快照只由 Manager 决定。
|
|
|
|
|
discardPendingFullResult();
|
|
|
|
|
return bCommitted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//bool nmCalculationDllPebiSolverTask::savePebiModeResult(HX_NWTM_MODEL_OUTPUT& p1)
|
|
|
|
|
|