feat(numerical): 实现 PEBI 结果快照原子提交

- 在工作线程构建并校验完整结果快照候选
- 在主线程按输入版本、井 UUID 和槽位顺序一次发布
- 失败、取消、结果过期或内存不足时保留旧快照
- 通过弱引用兼容 nmDataWellBase 现有结果 getter
- 转移井曲线容器并隔离旧 UI 结果网格,降低复制和污染风险
- 保持自动拟合逻辑不变
feature/pebi-result-snapshot-20260831
lh 1 week ago
parent 0086e07d88
commit acb39fe350

@ -7,6 +7,7 @@
#include <QMap>
#include <QPointF>
#include <QAtomicInt>
#include <QSharedPointer>
#include <iostream>
#include <vector>
#include <Windows.h>
@ -16,6 +17,7 @@
#include "pch.h"
class nmDataAnalyzeManager;
class nmPebiResultSnapshot;
class vtkDoubleArray;
class vtkUnstructuredGrid;
struct nmPebiSolverInputSnapshot;
@ -81,6 +83,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
void releaseDataManagerUse();
/** @brief 丢弃正式结果候选及任务侧全部 VTK 可写别名,不影响旧快照。 */
void discardPendingFullResult();
/** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */
bool captureInputSnapshot(const QString& sAutoFitTargetWellName);
/** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */
@ -96,6 +100,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
bool buildPebiModeResult(HX_NWTM_MODEL_OUTPUT& p1,
int nModelType,
const HX_NWTM_GRID_OUTPUT1& oGridOutput);
/** @brief 用任务局部输出构造并封闭完整结果候选,不访问实时数据对象。 */
bool buildPebiResultSnapshotCandidate();
/** @brief 输出精简版 DLL 输入日志,用于定位求解异常。 */
void logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0);
@ -137,7 +143,11 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
QVector<QVector<double> > m_autoFitResultSemiLog; ///< 目标井半对数结果。
QVector<nmPebiWellResultSnapshot> m_vecPendingWellResults; ///< 完整求解的井结果快照。
QMap<double, vtkSmartPointer<vtkDoubleArray> > m_mapPendingTimeSteps; ///< 场压力时间步快照。
vtkSmartPointer<vtkUnstructuredGrid> m_pPendingResultGrid; ///< 本次结果绑定的基础网格副本。
QMap<QString, QPointF> m_mapPendingWellLocations; ///< 旧 UI 兼容层使用的井编码位置映射。
/** @brief 发布前为求解基础网格,候选封闭后改为旧 UI 独占的结构网格。 */
vtkSmartPointer<vtkUnstructuredGrid> m_pPendingResultGrid;
/** @brief 工作线程已经完整校验、等待主线程发布的可写候选引用。 */
QSharedPointer<nmPebiResultSnapshot> m_pPendingResultSnapshot;
double m_dPendingScalarMin; ///< 全部场压力的最小值。
double m_dPendingScalarMax; ///< 全部场压力的最大值。
bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。

@ -28,22 +28,39 @@ struct nmPebiWellInputSnapshot
{
nmPebiWellInputSnapshot()
: m_nFlowSectionIndex(1),
m_dRadius(0.0),
m_dWellboreStorage(0.0),
m_dSkin(0.0),
m_dDfc(0.0),
m_bRateControlled(false),
m_bRealWell(false)
m_bRealWell(false),
m_bHasPerforation(false),
m_bHasDfc(false),
m_bDisplayResultWell(false)
{
}
QString m_sWellCode;
QString m_sWellName;
QVector<QPointF> m_vecFlowPoints;
QPointF m_oLocation;
int m_nFlowSectionIndex;
double m_dWellboreStorage;
double m_dSkin;
bool m_bRateControlled;
bool m_bRealWell;
QString m_sWellCode; ///< 项目内稳定井编码。
QString m_sWellName; ///< 捕获时井名称。
QString m_sWellInstanceId; ///< 完整场结果使用的稳定井 UUID。
QVector<QPointF> m_vecFlowPoints; ///< 求解使用的流量制度。
/** @brief 求解时冻结的历史压力曲线。 */
QVector<QVector<double> > m_vecHistoryPressure;
/** @brief 求解时冻结的历史双对数曲线。 */
QVector<QVector<double> > m_vecHistoryLogLog;
/** @brief 求解时冻结的历史半对数曲线。 */
QVector<QVector<double> > m_vecHistorySemiLog;
QPointF m_oLocation; ///< 求解时井平面位置。
int m_nFlowSectionIndex; ///< 当前流动段下标。
double m_dRadius; ///< 求解时井筒半径。
double m_dWellboreStorage; ///< 求解时井筒储集系数。
double m_dSkin; ///< 求解时表皮系数。
double m_dDfc; ///< 求解时裂缝导流能力。
bool m_bRateControlled; ///< 是否提供源汇项。
bool m_bRealWell; ///< 是否为真实井而非手工裂缝槽位。
bool m_bHasPerforation; ///< 求解时是否存在射孔。
bool m_bHasDfc; ///< 当前井型是否具有 Dfc 参数。
bool m_bDisplayResultWell; ///< 是否进入结果井下拉框。
};
/**

@ -542,6 +542,17 @@ public:
bool commitPebiResultSnapshot(
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
QString* pError = NULL);
/**
* @brief UI
* @note
*/
bool commitPebiResultSnapshot(
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
QMap<double, vtkSmartPointer<vtkDoubleArray> >&
mapLegacyTimeSteps,
vtkSmartPointer<vtkUnstructuredGrid>& pLegacyResultGrid,
QMap<QString, QPointF>& mapLegacyWellLocations,
QString* pError = NULL);
/** @brief 清除并行快照及其结果井查看状态。 */
void clearPebiResultSnapshot();
@ -787,6 +798,8 @@ public:
void sigPebiResultSnapshotChanged();
private:
/** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */
void rebindPebiResultSnapshotToWells();
/** @brief 创建本数据管理器的成果窗口;只用于读取本成果上下文,不拥有对象。 */
iSubWndFitting* m_pOwnerFitting;
/** @brief 保护后台使用计数和析构等待条件。 */

@ -6,11 +6,13 @@
#include "ZxDataObjectDbl.h"
#include "nmDataPerforation.h"
#include <QWeakPointer>
//#include "nmDataReservoir.h"
//#include "nmDataAnalyzeManager.h"
class nmDataReservoir;
class nmDataAnalyzeManager;
class nmPebiResultSnapshot;
class NM_DATA_EXPORT nmDataWellBase : public nmDataBase
{
@ -166,13 +168,19 @@ public:
NM_WELL_MODEL getWellType() const;
// 计算结果相关方法
/** @brief 返回当前井 UUID 在最后结果快照中的压力曲线;未绑定时兼容旧成员。 */
QVector<QVector<double>> getResultPressure();
/** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */
void setResultPressure(QVector<QVector<double>> pressureData);
/** @brief 返回当前井 UUID 在最后结果快照中的双对数曲线;未绑定时兼容旧成员。 */
QVector<QVector<double>> getResultLogLog();
/** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */
void setResultLogLog(QVector<QVector<double>> loglogData);
/** @brief 返回当前井 UUID 在最后结果快照中的半对数曲线;未绑定时兼容旧成员。 */
QVector<QVector<double>> getResultSemiLog();
/** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */
void setResultSemiLog(QVector<QVector<double>> semiLogData);
// 时间变表皮状态的getter和setter
@ -294,6 +302,9 @@ protected:
QVector<QVector<double>> m_vvecRstLogLog; // 计算结果双对数数据
QVector<QVector<double>> m_vvecRstSemiLog; // 计算结果半对数数据
/** @brief 最后结果快照的弱引用,不延长历史结果生命周期。 */
QWeakPointer<const nmPebiResultSnapshot> m_pPebiResultSnapshot;
// 射孔段集合
QVector<nmDataPerforation*> m_vecPerforations;
@ -301,4 +312,12 @@ protected:
double m_dLastWellLength;
nmDataReservoir* m_pReservoir;
private:
/** @brief 由数据管理器按井 UUID 统一绑定或解除最后结果快照。 */
void bindPebiResultSnapshot(
const QWeakPointer<const nmPebiResultSnapshot>& pSnapshot);
/** @brief 只有所属数据管理器可以维护井与结果快照之间的弱关联。 */
friend class nmDataAnalyzeManager;
};

@ -39,6 +39,8 @@ public:
bool addSolverSlot(const nmPebiResultSolverSlot& oSlot);
/** @brief 追加一口包含元数据和曲线的冻结结果井。 */
bool addWell(const nmPebiResultWellSnapshot& oWell);
/** @brief 转移一口井的字符串和曲线容器,避免提交阶段复制整套曲线。 */
bool takeWell(nmPebiResultWellSnapshot& oWell);
/** @brief 设置结果井下拉框使用的有序井 UUID 列表。 */
bool setDisplayWellInstanceIds(const QStringList& listWellInstanceIds);
/** @brief 设置求解时冻结的储层参数。 */

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

@ -1676,6 +1676,8 @@ bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
// 第二步:先让注册表指向新对象,再销毁旧对象并统一通知观察者。
syncWellAttrs();
delete pOldWell;
// 同 UUID 井型替换后,新对象必须重新获得最后结果的弱引用。
rebindPebiResultSnapshotToWells();
QStringList listParameters;
for (int nIndex = 0; nIndex < WELL_PARA_DESC_COUNT; ++nIndex)
@ -3825,10 +3827,221 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot(
m_pPebiResultSnapshot = pPublishedCandidate;
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
pCandidate.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
return true;
}
bool nmDataAnalyzeManager::commitPebiResultSnapshot(
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
QMap<double, vtkSmartPointer<vtkDoubleArray> >& mapLegacyTimeSteps,
vtkSmartPointer<vtkUnstructuredGrid>& pLegacyResultGrid,
QMap<QString, QPointF>& mapLegacyWellLocations,
QString* pError)
{
if(pError != NULL)
{
pError->clear();
}
QString sError;
if(QThread::currentThread() != thread())
{
sError = "PEBI result snapshot must be committed on its owner thread.";
}
else if(pCandidate.isNull() || !pCandidate->isComplete())
{
sError = "PEBI result snapshot candidate is incomplete.";
}
else if(pCandidate->getGridInputRevision() !=
m_oNumericalAnalysisCase.getGridInputRevision() ||
pCandidate->getResultInputRevision() !=
m_oNumericalAnalysisCase.getResultInputRevision() ||
!m_oNumericalAnalysisCase.isGridValid())
{
sError = "PEBI result snapshot input revisions are stale.";
}
else if(pLegacyResultGrid == NULL ||
pLegacyResultGrid->GetNumberOfCells() !=
pCandidate->getResultCellCount() ||
mapLegacyTimeSteps.size() !=
pCandidate->getPressureFrameCount())
{
sError = "PEBI legacy field result does not match the snapshot.";
}
// 第三阶段仍需给旧场图保留只读约定下的 VTK 引用;发布前逐帧校验,
// 不能让旧 UI 镜像与规范快照指向不同尺寸或不同时间轴的数据。
if(sError.isEmpty())
{
int nFrameIndex = 0;
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator
oTimeIt = mapLegacyTimeSteps.constBegin();
for(; oTimeIt != mapLegacyTimeSteps.constEnd();
++oTimeIt, ++nFrameIndex)
{
vtkDoubleArray* pPressure = oTimeIt.value();
if(oTimeIt.key() !=
pCandidate->getPressureTimeAt(nFrameIndex) ||
pPressure == NULL ||
pPressure->GetNumberOfComponents() != 1 ||
pPressure->GetNumberOfTuples() !=
pCandidate->getResultCellCount())
{
sError = "PEBI legacy pressure frames are inconsistent.";
break;
}
}
}
QSet<QString> setSnapshotWellCodes;
if(sError.isEmpty())
{
for(int nIndex = 0; nIndex < pCandidate->getWellCount(); ++nIndex)
{
const nmPebiResultWellSnapshot* pWell =
pCandidate->getWellAt(nIndex);
nmDataWellBase* pLiveWell = pWell == NULL ? NULL :
findWellByCode(pWell->m_sWellCode);
if(pWell == NULL || pLiveWell == NULL ||
pLiveWell->getWellInstanceId() !=
pWell->m_sWellInstanceId ||
setSnapshotWellCodes.contains(pWell->m_sWellCode) ||
!mapLegacyWellLocations.contains(pWell->m_sWellCode) ||
!qIsFinite(mapLegacyWellLocations.value(
pWell->m_sWellCode).x()) ||
!qIsFinite(mapLegacyWellLocations.value(
pWell->m_sWellCode).y()))
{
sError = "PEBI result well identity or location is stale.";
break;
}
setSnapshotWellCodes.insert(pWell->m_sWellCode);
}
if(sError.isEmpty() &&
(setSnapshotWellCodes.isEmpty() ||
mapLegacyWellLocations.size() !=
setSnapshotWellCodes.size()))
{
sError = "PEBI result well locations are incomplete.";
}
}
// 当前网格登记的槽位也必须与候选完全相同,防止相同版本下的非法外部改写。
if(sError.isEmpty())
{
const QVector<nmSolverWellRef> vecCurrentOrder =
m_oNumericalAnalysisCase.getSolverWellOrder();
if(vecCurrentOrder.size() != pCandidate->getSolverSlotCount())
{
sError = "PEBI solver slot order has changed.";
}
for(int nIndex = 0;
sError.isEmpty() && nIndex < vecCurrentOrder.size();
++nIndex)
{
const nmSolverWellRef& oCurrent = vecCurrentOrder[nIndex];
const nmPebiResultSolverSlot* pSaved =
pCandidate->getSolverSlotAt(nIndex);
if(pSaved == NULL ||
oCurrent.m_nSolverIndex != pSaved->m_nSolverIndex ||
oCurrent.m_eEntryKind != pSaved->m_eEntryKind ||
oCurrent.m_sWellCode != pSaved->m_sWellCode ||
oCurrent.m_eWellType != pSaved->m_eWellType)
{
sError = "PEBI solver slot order has changed.";
}
}
}
double aScalarRange[2] = { 0.0, 0.0 };
if(sError.isEmpty() && !pCandidate->getScalarRange(aScalarRange))
{
sError = "PEBI result scalar range is unavailable.";
}
if(!sError.isEmpty())
{
// 失败时只销毁新候选;任务局部 VTK 引用由任务随后释放,旧结果不变。
pCandidate.clear();
if(pError != NULL)
{
*pError = sError;
}
return false;
}
QSharedPointer<const nmPebiResultSnapshot> pPublishedCandidate(
pCandidate);
QString sNextWellInstanceId = m_sCurrentResultWellInstanceId;
if(!pPublishedCandidate->isDisplayWell(sNextWellInstanceId))
{
const QStringList& listDisplayWellIds =
pPublishedCandidate->getDisplayWellInstanceIds();
sNextWellInstanceId = listDisplayWellIds.isEmpty()
? QString() : listDisplayWellIds.first();
}
// 所有可能失败的检查均已完成;先同步旧结果状态,再在同一主线程事件内
// 替换规范快照和兼容载荷,观察者只在末尾收到一次完整结果通知。
if(!m_oNumericalAnalysisCase.markResultsAvailableIfCurrent(
pCandidate->getGridInputRevision(),
pCandidate->getResultInputRevision()))
{
pCandidate.clear();
if(pError != NULL)
{
*pError = "PEBI result snapshot became stale before publish.";
}
return false;
}
m_pPebiResultSnapshot = pPublishedCandidate;
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
m_mapTimeStepDataP.swap(mapLegacyTimeSteps);
mapLegacyTimeSteps.clear();
m_mapWellLocations.swap(mapLegacyWellLocations);
mapLegacyWellLocations.clear();
vtkSmartPointer<vtkUnstructuredGrid> pOldResultGrid =
m_pResultBaseGrid;
m_pResultBaseGrid = pLegacyResultGrid;
pLegacyResultGrid = pOldResultGrid;
pLegacyResultGrid = NULL;
m_dScalarRangeP[0] = aScalarRange[0];
m_dScalarRangeP[1] = aScalarRange[1];
pCandidate.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
return true;
}
void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells()
{
QWeakPointer<const nmPebiResultSnapshot> pWeakSnapshot;
if(!m_pPebiResultSnapshot.isNull())
{
pWeakSnapshot = QWeakPointer<const nmPebiResultSnapshot>(
m_pPebiResultSnapshot);
}
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex)
{
nmDataWellBase* pWell = m_vWellData[nIndex];
if(pWell == NULL)
{
continue;
}
const bool bBelongsToSnapshot =
!m_pPebiResultSnapshot.isNull() &&
m_pPebiResultSnapshot->findWell(
pWell->getWellInstanceId()) != NULL;
pWell->bindPebiResultSnapshot(bBelongsToSnapshot
? pWeakSnapshot
: QWeakPointer<const nmPebiResultSnapshot>());
}
}
void nmDataAnalyzeManager::clearPebiResultSnapshot()
{
if(m_pPebiResultSnapshot.isNull() &&
@ -3839,6 +4052,7 @@ void nmDataAnalyzeManager::clearPebiResultSnapshot()
m_pPebiResultSnapshot.clear();
m_sCurrentResultWellInstanceId.clear();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
}

@ -1,6 +1,7 @@
#include "nmDataWellBase.h"
#include "nmDataReservoir.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSnapshot.h"
#include <QDir>
#include <QFile>
@ -98,6 +99,8 @@ nmDataWellBase::~nmDataWellBase()
nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
{
if(this != &other) {
// 工作副本不能继承 Manager 维护的结果绑定;提交替换后统一重绑。
m_pPebiResultSnapshot.clear();
m_sWellInstanceId = other.m_sWellInstanceId;
m_wellName = other.m_wellName;
m_wellCode = other.m_wellCode;
@ -490,6 +493,8 @@ bool nmDataWellBase::restoreWellInstanceId(
}
m_sWellInstanceId = oUuid.toString().remove('{').remove('}');
// UUID 改变后原弱引用不再具有关联意义,等待 Manager 重新绑定。
m_pPebiResultSnapshot.clear();
return true;
}
@ -777,6 +782,15 @@ void nmDataWellBase::setHistorySemiLog(QVector<QVector<double>> semiLogData) {
// 计算结果相关方法
QVector<QVector<double>> nmDataWellBase::getResultPressure()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultPressure;
}
}
return m_vvecRstPressure;
}
@ -787,6 +801,15 @@ void nmDataWellBase::setResultPressure(QVector<QVector<double>> pressureData)
QVector<QVector<double>> nmDataWellBase::getResultLogLog()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultLogLog;
}
}
return m_vvecRstLogLog;
}
@ -797,6 +820,15 @@ void nmDataWellBase::setResultLogLog(QVector<QVector<double>> loglogData)
QVector<QVector<double>> nmDataWellBase::getResultSemiLog()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultSemiLog;
}
}
return m_vvecRstSemiLog;
}
@ -805,6 +837,12 @@ void nmDataWellBase::setResultSemiLog(QVector<QVector<double>> semiLogData)
m_vvecRstSemiLog = semiLogData;
}
void nmDataWellBase::bindPebiResultSnapshot(
const QWeakPointer<const nmPebiResultSnapshot>& pSnapshot)
{
m_pPebiResultSnapshot = pSnapshot;
}
// 射孔管理相关方法
void nmDataWellBase::addPerforation(nmDataPerforation* perforation)
{

@ -496,6 +496,67 @@ bool nmPebiResultSnapshotBuilder::addWell(
return true;
}
bool nmPebiResultSnapshotBuilder::takeWell(
nmPebiResultWellSnapshot& oWell)
{
if(!canWrite())
{
return false;
}
try
{
m_pCandidate->m_vecWells.append(nmPebiResultWellSnapshot());
}
catch(const std::bad_alloc&)
{
return fail("Cannot allocate PEBI result well list.");
}
// Qt 4.8 没有移动语义,先追加空元素再交换隐式共享容器,
// 避免最终提交时重新复制六组可能很大的井曲线。
nmPebiResultWellSnapshot& oTarget =
m_pCandidate->m_vecWells.last();
qSwap(oTarget.m_sWellInstanceId, oWell.m_sWellInstanceId);
qSwap(oTarget.m_sWellCode, oWell.m_sWellCode);
qSwap(oTarget.m_sWellName, oWell.m_sWellName);
oTarget.m_eWellType = oWell.m_eWellType;
oTarget.m_eWellMode = oWell.m_eWellMode;
oTarget.m_oLocation = oWell.m_oLocation;
oTarget.m_bHasPerforation = oWell.m_bHasPerforation;
oTarget.m_bHasSkin = oWell.m_bHasSkin;
oTarget.m_bHasDfc = oWell.m_bHasDfc;
oTarget.m_dRadius = oWell.m_dRadius;
oTarget.m_dWellboreStorage = oWell.m_dWellboreStorage;
oTarget.m_dSkin = oWell.m_dSkin;
oTarget.m_dDfc = oWell.m_dDfc;
qSwap(oTarget.m_oCurves.m_vecHistoryPressure,
oWell.m_oCurves.m_vecHistoryPressure);
qSwap(oTarget.m_oCurves.m_vecHistoryLogLog,
oWell.m_oCurves.m_vecHistoryLogLog);
qSwap(oTarget.m_oCurves.m_vecHistorySemiLog,
oWell.m_oCurves.m_vecHistorySemiLog);
qSwap(oTarget.m_oCurves.m_vecResultPressure,
oWell.m_oCurves.m_vecResultPressure);
qSwap(oTarget.m_oCurves.m_vecResultLogLog,
oWell.m_oCurves.m_vecResultLogLog);
qSwap(oTarget.m_oCurves.m_vecResultSemiLog,
oWell.m_oCurves.m_vecResultSemiLog);
// 清除剩余值,保证调用方不再保留候选井的可写语义。
oWell.m_eWellType = Unknow_Well;
oWell.m_eWellMode = NM_CaseWell_Observation;
oWell.m_oLocation = QPointF();
oWell.m_bHasPerforation = false;
oWell.m_bHasSkin = false;
oWell.m_bHasDfc = false;
oWell.m_dRadius = 0.0;
oWell.m_dWellboreStorage = 0.0;
oWell.m_dSkin = 0.0;
oWell.m_dDfc = 0.0;
return true;
}
bool nmPebiResultSnapshotBuilder::setDisplayWellInstanceIds(
const QStringList& listWellInstanceIds)
{

Loading…
Cancel
Save