|
|
#include "nmCalculationDllPebiSolverTask.h"
|
|
|
#include "singlePhaseSolver.h"
|
|
|
#include "zxLogInstance.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmDataWellBase.h"
|
|
|
#include "nmDataVerticalWell.h"
|
|
|
#include "nmDataVerticalFracturedWell.h"
|
|
|
#include "nmDataHorizontalFracturedWell.h"
|
|
|
#include "nmDataReservoir.h"
|
|
|
#include "nmDataAttribute.h"
|
|
|
#include "nmDataRegion.h"
|
|
|
#include "nmDataRegionMark.h"
|
|
|
#include "nmDataOutline.h"
|
|
|
#include "nmDataFracture.h"
|
|
|
#include "nmDataFault.h"
|
|
|
#include "nmDataTimeStepSetting.h"
|
|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
|
|
|
#include "nmCalculationPebiGrid.h"
|
|
|
#include "nmCalculationUtils.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmDataPvtParaForPebi.h"
|
|
|
|
|
|
#include <QDebug>
|
|
|
#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>
|
|
|
#include <string>
|
|
|
|
|
|
#include <vtkSmartPointer.h>
|
|
|
#include <vtkDoubleArray.h>
|
|
|
#include <vtkCellData.h>
|
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
|
|
/**
|
|
|
* @brief 一次 PEBI 求解任务独占的完整不可变输入。
|
|
|
*/
|
|
|
struct nmPebiSolverInputSnapshot
|
|
|
{
|
|
|
nmPebiSolverInputSnapshot()
|
|
|
: m_nSolverType(nmDataAnalyzeManager::PebiSolverOriginal),
|
|
|
m_nOmpThreads(1),
|
|
|
m_nIluReuseSteps(1),
|
|
|
m_bRequiresGridCalculation(false),
|
|
|
m_bGridResultNeedsCommit(false),
|
|
|
m_bMayUseCachedGrid(false),
|
|
|
m_bAutoFitTargetOnly(false),
|
|
|
m_bResultMetadataCaptured(false)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。
|
|
|
nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。
|
|
|
QVector<nmPropertyInterpolationDataSet> m_vecPropertyDataSets; ///< 属性插值值副本。
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> m_pBaseGrid; ///< 完整成果使用的基础 VTK 网格。
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> m_pSourceBaseGrid; ///< 主线程捕获引用,后台只读并深拷贝。
|
|
|
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; ///< 正式结果元数据是否已在主线程完整冻结。
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* @brief 手工求解在界面线程分批捕获期间使用的临时状态。
|
|
|
*
|
|
|
* 这里只暂存 DataManager 拥有的井指针;最后一批完成后立即释放,QThread 启动后
|
|
|
* 只能读取 nmPebiSolverInputSnapshot 中的值,不能跨线程访问这些对象。
|
|
|
*/
|
|
|
struct nmPebiManualCaptureState
|
|
|
{
|
|
|
enum CapturePhase
|
|
|
{
|
|
|
CapturePhase_Initialize = 0,
|
|
|
CapturePhase_Wells,
|
|
|
CapturePhase_FinalizeGrid,
|
|
|
CapturePhase_SolverSettings,
|
|
|
CapturePhase_GridCache
|
|
|
};
|
|
|
|
|
|
nmPebiManualCaptureState()
|
|
|
: m_ePhase(CapturePhase_Initialize),
|
|
|
m_nNextWellIndex(0)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
CapturePhase m_ePhase;
|
|
|
QVector<nmDataWellBase*> m_vecOrderedWells;
|
|
|
QSet<QString> m_setEffectiveWellCodes;
|
|
|
QHash<QString, int> m_mapWellModes;
|
|
|
int m_nNextWellIndex;
|
|
|
};
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
bool isTaskCancellationRequested(const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
return pCancelRequested != NULL &&
|
|
|
static_cast<int>(*pCancelRequested) != 0;
|
|
|
}
|
|
|
|
|
|
class nmSolverDllMutexLocker
|
|
|
{
|
|
|
public:
|
|
|
nmSolverDllMutexLocker()
|
|
|
: m_pMutex(NULL),
|
|
|
m_bLocked(false)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
~nmSolverDllMutexLocker()
|
|
|
{
|
|
|
unlock();
|
|
|
}
|
|
|
|
|
|
bool lock(QMutex* pMutex, const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
if(pMutex == NULL || m_bLocked) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pCancelRequested == NULL) {
|
|
|
pMutex->lock();
|
|
|
} else {
|
|
|
// 只让手工任务在等待全局 DLL 锁时可停止,互斥范围和锁顺序不变。
|
|
|
while(!pMutex->tryLock(100)) {
|
|
|
if(isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
m_pMutex = pMutex;
|
|
|
m_bLocked = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void unlock()
|
|
|
{
|
|
|
if(m_bLocked && m_pMutex != NULL) {
|
|
|
m_pMutex->unlock();
|
|
|
m_pMutex = NULL;
|
|
|
m_bLocked = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
QMutex* m_pMutex;
|
|
|
bool m_bLocked;
|
|
|
};
|
|
|
|
|
|
bool isFiniteSolverNumber(double value)
|
|
|
{
|
|
|
#ifdef _MSC_VER
|
|
|
return _finite(value) != 0;
|
|
|
#else
|
|
|
return std::isfinite(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,
|
|
|
const QVector<nmPropertyInterpolationDataSet>& dataSets,
|
|
|
const QString& licensePath,
|
|
|
QString& errorMessage,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
bool hasEnabledDataSet = false;
|
|
|
for(int i = 0; i < dataSets.size(); ++i) {
|
|
|
if(dataSets[i].useForCalculation) {
|
|
|
hasEnabledDataSet = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!hasEnabledDataSet) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> targetPoints;
|
|
|
targetPoints.reserve(static_cast<int>(modelInput.GRID.Trinodexy.size()));
|
|
|
for(size_t cellIndex = 0;
|
|
|
cellIndex < modelInput.GRID.Trinodexy.size(); ++cellIndex) {
|
|
|
if((cellIndex % 256) == 0 &&
|
|
|
isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
const dVec1& cellPosition = modelInput.GRID.Trinodexy[cellIndex];
|
|
|
if(cellPosition.size() < 2) {
|
|
|
errorMessage = QString("Grid cell %1 has no valid center coordinate.")
|
|
|
.arg(static_cast<qulonglong>(cellIndex));
|
|
|
return false;
|
|
|
}
|
|
|
targetPoints.append(QPointF(cellPosition[0], cellPosition[1]));
|
|
|
}
|
|
|
|
|
|
bool kApplied = false;
|
|
|
bool phiApplied = false;
|
|
|
bool hApplied = false;
|
|
|
for(int dataSetIndex = 0; dataSetIndex < dataSets.size(); ++dataSetIndex) {
|
|
|
if(isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
const nmPropertyInterpolationDataSet& dataSet = dataSets[dataSetIndex];
|
|
|
if(!dataSet.useForCalculation) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
dVec1* solverValues = NULL;
|
|
|
bool* propertyApplied = NULL;
|
|
|
if(dataSet.property == "k") {
|
|
|
solverValues = &modelInput.Base.k;
|
|
|
propertyApplied = &kApplied;
|
|
|
} else if(dataSet.property == "phi") {
|
|
|
solverValues = &modelInput.Base.phi;
|
|
|
propertyApplied = &phiApplied;
|
|
|
} else if(dataSet.property == "h") {
|
|
|
solverValues = &modelInput.Base.h;
|
|
|
propertyApplied = &hApplied;
|
|
|
} else {
|
|
|
errorMessage = QString("Dataset '%1' has an unknown property.")
|
|
|
.arg(dataSet.name);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(*propertyApplied) {
|
|
|
errorMessage = QString(
|
|
|
"More than one dataset is enabled for property %1.")
|
|
|
.arg(dataSet.property);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> measurementPoints;
|
|
|
QVector<double> measurementValues;
|
|
|
measurementPoints.reserve(dataSet.points.size());
|
|
|
measurementValues.reserve(dataSet.points.size());
|
|
|
for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) {
|
|
|
const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex];
|
|
|
measurementPoints.append(QPointF(point.x, point.y));
|
|
|
measurementValues.append(point.value);
|
|
|
}
|
|
|
|
|
|
QVector<double> interpolationValues;
|
|
|
QString calculationError;
|
|
|
if(!nmCalculationUtils::calculateKriging(
|
|
|
targetPoints,
|
|
|
measurementPoints,
|
|
|
measurementValues,
|
|
|
dataSet.nugget,
|
|
|
dataSet.sill,
|
|
|
dataSet.range,
|
|
|
dataSet.model,
|
|
|
licensePath,
|
|
|
interpolationValues,
|
|
|
&calculationError,
|
|
|
pCancelRequested)) {
|
|
|
errorMessage = QString("Dataset '%1': %2")
|
|
|
.arg(dataSet.name)
|
|
|
.arg(calculationError);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
solverValues->resize(interpolationValues.size());
|
|
|
for(int valueIndex = 0; valueIndex < interpolationValues.size(); ++valueIndex) {
|
|
|
if((valueIndex % 256) == 0 &&
|
|
|
isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
// 属性插值数据层以mD保存;渗透率在写入PEBI数组时转换为D。
|
|
|
(*solverValues)[valueIndex] = dataSet.property == "k"
|
|
|
? nmCalculationUtils::milliDarcyToDarcy(
|
|
|
interpolationValues[valueIndex])
|
|
|
: interpolationValues[valueIndex];
|
|
|
}
|
|
|
*propertyApplied = true;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 用已初始化的模型输入和捕获的场景值组装一次完整模型输入。
|
|
|
*/
|
|
|
bool buildModelInputFromSnapshot(
|
|
|
const nmPebiSolverInputSnapshot& oSnapshot,
|
|
|
HX_NWTM_MODEL_INPUT& oModelInput,
|
|
|
QString& sErrorMessage,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
const nmDataBinaryTools::NM_PEBI_SCENE& oScene =
|
|
|
oSnapshot.m_oGridInput.m_oScene;
|
|
|
if(isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第一步:恢复井槽位、产量制度和井筒参数。场景与网格快照在同一次捕获中
|
|
|
// 生成,因此这些外层数组天然使用同一个 DLL 下标顺序。
|
|
|
oModelInput.T = oScene.solverType;
|
|
|
oModelInput.Rate.t = oScene.Rate.t;
|
|
|
oModelInput.Rate.qo = oScene.Rate.qo;
|
|
|
oModelInput.Rate.qg = oScene.Rate.qg;
|
|
|
oModelInput.Rate.qw = oScene.Rate.qw;
|
|
|
oModelInput.CS.C = oScene.CS.C;
|
|
|
oModelInput.CS.S = oScene.CS.S;
|
|
|
|
|
|
// 第二步:恢复完整 PVT 数组。不能在工作线程重新读取 PVT 对象,否则界面
|
|
|
// 切换模型或编辑表格时仍会与求解器发生容器并发访问。
|
|
|
oModelInput.PVT.p = oScene.PVT.p;
|
|
|
oModelInput.PVT.pb = oScene.PVT.pb;
|
|
|
oModelInput.PVT.Rso = oScene.PVT.Rso;
|
|
|
oModelInput.PVT.Bo = oScene.PVT.Bo;
|
|
|
oModelInput.PVT.Co = oScene.PVT.Co;
|
|
|
oModelInput.PVT.miuo = oScene.PVT.miuo;
|
|
|
oModelInput.PVT.rouo = oScene.PVT.rouo;
|
|
|
oModelInput.PVT.Rv = oScene.PVT.Rv;
|
|
|
oModelInput.PVT.Bg = oScene.PVT.Bg;
|
|
|
oModelInput.PVT.Cg = oScene.PVT.Cg;
|
|
|
oModelInput.PVT.miug = oScene.PVT.miug;
|
|
|
oModelInput.PVT.roug = oScene.PVT.roug;
|
|
|
oModelInput.PVT.Z = oScene.PVT.Z;
|
|
|
oModelInput.PVT.Rsw = oScene.PVT.Rsw;
|
|
|
oModelInput.PVT.Bw = oScene.PVT.Bw;
|
|
|
oModelInput.PVT.Cw = oScene.PVT.Cw;
|
|
|
oModelInput.PVT.miuw = oScene.PVT.miuw;
|
|
|
oModelInput.PVT.rouw = oScene.PVT.rouw;
|
|
|
oModelInput.PVT.V = oScene.PVT.V;
|
|
|
oModelInput.PVT.k_kinitial = oScene.PVT.k_kinitial;
|
|
|
oModelInput.PVT.Cf_Cfinitial = oScene.PVT.Cf_Cfinitial;
|
|
|
oModelInput.PVT.So = oScene.PVT.So;
|
|
|
oModelInput.PVT.Kro = oScene.PVT.Kro;
|
|
|
oModelInput.PVT.Sg = oScene.PVT.Sg;
|
|
|
oModelInput.PVT.Krg = oScene.PVT.Krg;
|
|
|
oModelInput.PVT.Sw = oScene.PVT.Sw;
|
|
|
oModelInput.PVT.Krw = oScene.PVT.Krw;
|
|
|
|
|
|
// 第三步:先用储层参考值填满全部网格单元,再按捕获的属性数据组覆盖。
|
|
|
oModelInput.Base.Pi = oScene.Base.Pi;
|
|
|
oModelInput.Base.Cti = oScene.Base.Cti;
|
|
|
oModelInput.Base.Cf = oScene.Base.Cf;
|
|
|
oModelInput.Base.Soi = oScene.Base.Soi;
|
|
|
oModelInput.Base.Sgi = oScene.Base.Sgi;
|
|
|
oModelInput.Base.Swi = oScene.Base.Swi;
|
|
|
oModelInput.Base.d = oScene.Base.d;
|
|
|
oModelInput.Base.dt_Min = oScene.Base.dt_Min;
|
|
|
oModelInput.Base.dt_Max = oScene.Base.dt_Max;
|
|
|
|
|
|
const size_t nCellCount = oModelInput.GRID.Trinodexy.size();
|
|
|
oModelInput.Base.k = dVec1(nCellCount, oScene.Base.k_ref);
|
|
|
oModelInput.Base.phi = dVec1(nCellCount, oScene.Base.phi_ref);
|
|
|
oModelInput.Base.h = dVec1(nCellCount, oScene.Base.h_ref);
|
|
|
|
|
|
return applyPropertyInterpolation(
|
|
|
oModelInput,
|
|
|
oSnapshot.m_vecPropertyDataSets,
|
|
|
oSnapshot.m_oGridInput.m_sLicensePath,
|
|
|
sErrorMessage,
|
|
|
pCancelRequested);
|
|
|
}
|
|
|
|
|
|
bool isReasonableLogLogValue(double value)
|
|
|
{
|
|
|
const double maxReasonableAbsValue = 1.0e12;
|
|
|
return isFiniteSolverNumber(value)
|
|
|
&& value >= -maxReasonableAbsValue
|
|
|
&& value <= maxReasonableAbsValue;
|
|
|
}
|
|
|
|
|
|
bool isValidLogLogPoint(const Point& pt)
|
|
|
{
|
|
|
return isReasonableLogLogValue(pt.x)
|
|
|
&& isReasonableLogLogValue(pt.y)
|
|
|
&& isReasonableLogLogValue(pt.z)
|
|
|
&& pt.x > 0.0
|
|
|
&& pt.z >= DBL_EPSILON;
|
|
|
}
|
|
|
|
|
|
bool isValidSemiLogPoint(const Point& pt)
|
|
|
{
|
|
|
return isReasonableLogLogValue(pt.x)
|
|
|
&& pt.x > 0.0
|
|
|
&& !pt.pointData.empty()
|
|
|
&& isReasonableLogLogValue(pt.pointData[0]);
|
|
|
}
|
|
|
|
|
|
typedef bool (*CalPseudoPressure)(double, double&, int);
|
|
|
|
|
|
// mAlgPseudo.dll 是主界面拟压力算法所在模块.
|
|
|
// 此处直接解析 iAlgPseuCaller::calPS 的导出符号, 复用主界面的同一套转换实现.
|
|
|
// 函数参数依次为原始压力、输出拟压力和拟压力分区编号.
|
|
|
CalPseudoPressure getPseudoPressureConverter()
|
|
|
{
|
|
|
// 静态保存模块和函数地址, 避免每个粒子、每个压力点重复加载和解析 DLL.
|
|
|
// configPsAbouts() 初始化的数据及模式也保存在同一个 mAlgPseudo.dll 模块中.
|
|
|
static HMODULE module = LoadLibraryW(L"mAlgPseudo.dll");
|
|
|
static CalPseudoPressure converter = module
|
|
|
? reinterpret_cast<CalPseudoPressure>(GetProcAddress(
|
|
|
module, "?calPS@iAlgPseuCaller@@SA_NNAANH@Z"))
|
|
|
: nullptr;
|
|
|
return converter;
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
QString sPostprocessingDir,
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
const QString& sAutoFitTargetWellName,
|
|
|
QObject *parent,
|
|
|
bool bDeferManualSnapshot):
|
|
|
QThread(parent),
|
|
|
m_sPostprocessingDir(sPostprocessingDir),
|
|
|
m_pDataManager(pDataManager != nullptr
|
|
|
? pDataManager
|
|
|
: nmDataAnalyzeManager::getCurrentInstance()),
|
|
|
m_pInputSnapshot(nullptr),
|
|
|
m_pManualCaptureState(nullptr),
|
|
|
m_bManagerUseActive(false),
|
|
|
m_bInputSnapshotValid(false),
|
|
|
m_nGridInputRevision(0),
|
|
|
m_nResultInputRevision(0),
|
|
|
m_lastRunSucceeded(false),
|
|
|
m_nSolveTimeMs(-1),
|
|
|
m_nPebiCount(-1),
|
|
|
m_dPendingScalarMin(0.0),
|
|
|
m_dPendingScalarMax(0.0),
|
|
|
m_bPendingFullResultReady(false),
|
|
|
m_nCancelRequested(0),
|
|
|
m_nWasCancelled(0)
|
|
|
{
|
|
|
try {
|
|
|
m_pInputSnapshot = new nmPebiSolverInputSnapshot();
|
|
|
|
|
|
// 第一步:构造后到 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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmCalculationDllPebiSolverTask::~nmCalculationDllPebiSolverTask()
|
|
|
{
|
|
|
// Qt 4.8 没有可靠的外部 DLL 协作取消接口。强制 terminate() 可能让 DLL、
|
|
|
// VTK 或 STL 对象停在持锁/半析构状态,因此析构只能等待 run() 正常退出。
|
|
|
if(isRunning()) {
|
|
|
wait();
|
|
|
}
|
|
|
releaseDataManagerUse();
|
|
|
delete m_pManualCaptureState;
|
|
|
m_pManualCaptureState = nullptr;
|
|
|
delete m_pInputSnapshot;
|
|
|
m_pInputSnapshot = nullptr;
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::run()
|
|
|
{
|
|
|
m_nSolveTimeMs = -1;
|
|
|
m_nPebiCount = -1;
|
|
|
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);
|
|
|
discardPendingFullResult();
|
|
|
bSucceeded = false;
|
|
|
}
|
|
|
m_lastRunSucceeded = bSucceeded;
|
|
|
// 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。
|
|
|
releaseDataManagerUse();
|
|
|
emit sig_calculateDone(m_lastRunSucceeded);
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::requestCancel()
|
|
|
{
|
|
|
m_nCancelRequested.fetchAndStoreOrdered(1);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::wasCancelled() const
|
|
|
{
|
|
|
return static_cast<int>(m_nWasCancelled) != 0 ||
|
|
|
isCancelRequested();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::hasInputChanged(
|
|
|
nmDataAnalyzeManager* pDataManager) const
|
|
|
{
|
|
|
if(pDataManager == nullptr || pDataManager != m_pDataManager ||
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
pDataManager->getNumericalAnalysisCase();
|
|
|
return pAnalysisCase != nullptr &&
|
|
|
(pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::captureManualInputStep(
|
|
|
int nMaxWellCount,
|
|
|
bool& bFinished)
|
|
|
{
|
|
|
bFinished = false;
|
|
|
if(m_pManualCaptureState == nullptr || m_pInputSnapshot == nullptr ||
|
|
|
m_pDataManager == nullptr || isRunning() ||
|
|
|
QThread::currentThread() != m_pDataManager->thread()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(isCancelRequested()) {
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(1);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmPebiManualCaptureState* pState = m_pManualCaptureState;
|
|
|
nmCalculationPebiGrid* pGridService =
|
|
|
nmCalculationPebiGrid::getInstance();
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
m_pDataManager->getNumericalAnalysisCase();
|
|
|
if(pGridService == nullptr || pAnalysisCase == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pState->m_ePhase !=
|
|
|
nmPebiManualCaptureState::CapturePhase_Initialize &&
|
|
|
(pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision)) {
|
|
|
// 分批期间输入版本变化时整批作废,不能把不同时刻的值拼成一个快照。
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pState->m_ePhase ==
|
|
|
nmPebiManualCaptureState::CapturePhase_Initialize) {
|
|
|
m_nGridInputRevision = pAnalysisCase->getGridInputRevision();
|
|
|
m_nResultInputRevision = pAnalysisCase->getResultInputRevision();
|
|
|
m_sAutoFitTargetWellCode.clear();
|
|
|
|
|
|
if(!pGridService->beginManualInputSnapshot(
|
|
|
m_pDataManager, m_pInputSnapshot->m_oGridInput)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
m_pDataManager->getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
|
|
|
if(oWellRef.m_sWellCode.isEmpty() ||
|
|
|
pState->m_setEffectiveWellCodes.contains(
|
|
|
oWellRef.m_sWellCode)) {
|
|
|
return false;
|
|
|
}
|
|
|
pState->m_setEffectiveWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
pState->m_mapWellModes.insert(
|
|
|
oWellRef.m_sWellCode,
|
|
|
static_cast<int>(oWellRef.m_eMode));
|
|
|
}
|
|
|
if(pState->m_setEffectiveWellCodes.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QSet<QString> setOrderedWellCodes;
|
|
|
const QVector<nmDataVerticalWell*> vecVerticalWells =
|
|
|
m_pDataManager->getVerticalWellData();
|
|
|
for(int nIndex = 0; nIndex < vecVerticalWells.size(); ++nIndex) {
|
|
|
nmDataVerticalWell* pWellData = vecVerticalWells[nIndex];
|
|
|
if(pWellData != nullptr &&
|
|
|
pState->m_setEffectiveWellCodes.contains(
|
|
|
pWellData->getWellCode())) {
|
|
|
pState->m_vecOrderedWells.append(pWellData);
|
|
|
setOrderedWellCodes.insert(pWellData->getWellCode());
|
|
|
}
|
|
|
}
|
|
|
const QVector<nmDataVerticalFracturedWell*> vecVerticalFracturedWells =
|
|
|
m_pDataManager->getVerticalFracturedWellData();
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < vecVerticalFracturedWells.size();
|
|
|
++nIndex) {
|
|
|
nmDataVerticalFracturedWell* pWellData =
|
|
|
vecVerticalFracturedWells[nIndex];
|
|
|
if(pWellData != nullptr &&
|
|
|
pState->m_setEffectiveWellCodes.contains(
|
|
|
pWellData->getWellCode())) {
|
|
|
pState->m_vecOrderedWells.append(pWellData);
|
|
|
setOrderedWellCodes.insert(pWellData->getWellCode());
|
|
|
}
|
|
|
}
|
|
|
const QVector<nmDataHorizontalFracturedWell*>
|
|
|
vecHorizontalFracturedWells =
|
|
|
m_pDataManager->getHorizontalFracturedWellData();
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < vecHorizontalFracturedWells.size();
|
|
|
++nIndex) {
|
|
|
nmDataHorizontalFracturedWell* pWellData =
|
|
|
vecHorizontalFracturedWells[nIndex];
|
|
|
if(pWellData != nullptr &&
|
|
|
pState->m_setEffectiveWellCodes.contains(
|
|
|
pWellData->getWellCode())) {
|
|
|
pState->m_vecOrderedWells.append(pWellData);
|
|
|
setOrderedWellCodes.insert(pWellData->getWellCode());
|
|
|
}
|
|
|
}
|
|
|
if(setOrderedWellCodes != pState->m_setEffectiveWellCodes ||
|
|
|
pState->m_vecOrderedWells.size() !=
|
|
|
pState->m_setEffectiveWellCodes.size()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
pState->m_ePhase = nmPebiManualCaptureState::CapturePhase_Wells;
|
|
|
reportStage(tr("Capturing well data..."),
|
|
|
0,
|
|
|
pState->m_vecOrderedWells.size());
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(pState->m_ePhase == nmPebiManualCaptureState::CapturePhase_Wells) {
|
|
|
const int nBatchEnd = qMin(
|
|
|
pState->m_nNextWellIndex + qMax(1, nMaxWellCount),
|
|
|
pState->m_vecOrderedWells.size());
|
|
|
while(pState->m_nNextWellIndex < nBatchEnd) {
|
|
|
if(isCancelRequested()) {
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(1);
|
|
|
return false;
|
|
|
}
|
|
|
nmDataWellBase* pWellData =
|
|
|
pState->m_vecOrderedWells[pState->m_nNextWellIndex];
|
|
|
if(pWellData == nullptr ||
|
|
|
!pState->m_mapWellModes.contains(pWellData->getWellCode()) ||
|
|
|
!pGridService->appendManualWellInputSnapshot(
|
|
|
pWellData,
|
|
|
pState->m_mapWellModes.value(pWellData->getWellCode()),
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Capturing well data..."),
|
|
|
pState->m_nNextWellIndex,
|
|
|
pState->m_vecOrderedWells.size());
|
|
|
if(pState->m_nNextWellIndex >=
|
|
|
pState->m_vecOrderedWells.size()) {
|
|
|
pState->m_ePhase =
|
|
|
nmPebiManualCaptureState::CapturePhase_FinalizeGrid;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(pState->m_ePhase ==
|
|
|
nmPebiManualCaptureState::CapturePhase_FinalizeGrid) {
|
|
|
reportStage(tr("Finalizing input snapshot..."), 0, 0);
|
|
|
if(!pGridService->finishManualInputSnapshot(
|
|
|
m_pDataManager,
|
|
|
pState->m_setEffectiveWellCodes,
|
|
|
m_pInputSnapshot->m_oGridInput)) {
|
|
|
return false;
|
|
|
}
|
|
|
pState->m_ePhase =
|
|
|
nmPebiManualCaptureState::CapturePhase_SolverSettings;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(pState->m_ePhase ==
|
|
|
nmPebiManualCaptureState::CapturePhase_SolverSettings) {
|
|
|
reportStage(tr("Capturing solver settings..."), 0, 0);
|
|
|
m_pInputSnapshot->m_vecPropertyDataSets =
|
|
|
m_pDataManager->getPropertyInterpolationDataSets();
|
|
|
m_pInputSnapshot->m_nSolverType =
|
|
|
m_pDataManager->getPebiSolverType();
|
|
|
m_pInputSnapshot->m_nOmpThreads =
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Checking grid cache..."), 0, 0);
|
|
|
m_pInputSnapshot->m_bMayUseCachedGrid =
|
|
|
pGridService->isCurrentGridAvailableFor(
|
|
|
m_pDataManager, m_nGridInputRevision);
|
|
|
if(m_pInputSnapshot->m_bMayUseCachedGrid) {
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid =
|
|
|
m_pDataManager->getUnstructuredGrid();
|
|
|
if(m_pInputSnapshot->m_pSourceBaseGrid == nullptr ||
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid->GetNumberOfCells() <= 0) {
|
|
|
m_pInputSnapshot->m_bMayUseCachedGrid = false;
|
|
|
}
|
|
|
}
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation =
|
|
|
!m_pInputSnapshot->m_bMayUseCachedGrid;
|
|
|
|
|
|
// 至此快照不再含 DataManager 对象指针,先释放临时状态再允许启动 QThread。
|
|
|
delete m_pManualCaptureState;
|
|
|
m_pManualCaptureState = nullptr;
|
|
|
m_bInputSnapshotValid = true;
|
|
|
bFinished = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::isCancelRequested() const
|
|
|
{
|
|
|
return static_cast<int>(m_nCancelRequested) != 0;
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::reportStage(
|
|
|
const QString& sStage,
|
|
|
int nCurrent,
|
|
|
int nTotal)
|
|
|
{
|
|
|
if(m_pInputSnapshot != nullptr &&
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
emit sigStageChanged(sStage, nCurrent, nTotal);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::releaseDataManagerUse()
|
|
|
{
|
|
|
if(m_bManagerUseActive && m_pDataManager != nullptr) {
|
|
|
m_pDataManager->endBackgroundUse();
|
|
|
m_bManagerUseActive = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::captureInputSnapshot(
|
|
|
const QString& sAutoFitTargetWellName)
|
|
|
{
|
|
|
if(m_pDataManager == nullptr || m_pInputSnapshot == nullptr ||
|
|
|
QThread::currentThread() != m_pDataManager->thread()) {
|
|
|
qWarning() << "PEBI solver input must be captured on the DataManager thread.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
m_pDataManager->getNumericalAnalysisCase();
|
|
|
if(pAnalysisCase == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第一步:冻结几何和求解输入版本。提交阶段会再次核对,期间发生任何编辑
|
|
|
// 都只会让旧任务结果作废,不会影响后台正在读取的值快照。
|
|
|
m_nGridInputRevision = pAnalysisCase->getGridInputRevision();
|
|
|
m_nResultInputRevision = pAnalysisCase->getResultInputRevision();
|
|
|
|
|
|
const nmPebiSolverInputSnapshot oEmptySnapshot;
|
|
|
*m_pInputSnapshot = oEmptySnapshot;
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly =
|
|
|
!sAutoFitTargetWellName.isEmpty();
|
|
|
|
|
|
if(m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
nmDataWellBase* pTargetWell =
|
|
|
m_pDataManager->findWellByName(sAutoFitTargetWellName);
|
|
|
if(pTargetWell == nullptr || pTargetWell->getWellCode().isEmpty()) {
|
|
|
qWarning() << "Auto-fit target well is unavailable:"
|
|
|
<< sAutoFitTargetWellName;
|
|
|
return false;
|
|
|
}
|
|
|
m_sAutoFitTargetWellCode = pTargetWell->getWellCode();
|
|
|
} else {
|
|
|
m_sAutoFitTargetWellCode.clear();
|
|
|
}
|
|
|
|
|
|
// 第二步:手工求解只捕获 DataManager 值,场景数组延后到工作线程组装;
|
|
|
// 自动拟合保持原来的完整同步快照时机和行为。
|
|
|
nmCalculationPebiGrid* pGridService =
|
|
|
nmCalculationPebiGrid::getInstance();
|
|
|
if(pGridService == nullptr ||
|
|
|
!pGridService->captureInputSnapshot(
|
|
|
m_pDataManager,
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly) ||
|
|
|
m_pInputSnapshot->m_oGridInput.m_nGridInputRevision !=
|
|
|
m_nGridInputRevision) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QVector<nmSolverWellRef>& vecWellOrder =
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder;
|
|
|
if(m_pInputSnapshot->m_oGridInput.m_vecWellInputs.size() !=
|
|
|
vecWellOrder.size()) {
|
|
|
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 =
|
|
|
m_pDataManager->getPropertyInterpolationDataSets();
|
|
|
m_pInputSnapshot->m_nSolverType =
|
|
|
m_pDataManager->getPebiSolverType();
|
|
|
m_pInputSnapshot->m_nOmpThreads =
|
|
|
m_pDataManager->getPebiOmpThreads();
|
|
|
m_pInputSnapshot->m_nIluReuseSteps =
|
|
|
m_pDataManager->getPebiIluReuseSteps();
|
|
|
if(!m_pInputSnapshot->m_bAutoFitTargetOnly &&
|
|
|
!captureResultParameters(m_pDataManager, *m_pInputSnapshot)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:主线程仅检查缓存并捕获基础网格的智能指针。生成后的基础网格
|
|
|
// 不在原位修改,智能指针保证 DataManager 替换网格后旧对象仍存活;真正的
|
|
|
// DLL 输出复制和 VTK DeepCopy 在手工任务线程执行。
|
|
|
m_pInputSnapshot->m_bMayUseCachedGrid =
|
|
|
pGridService->isCurrentGridAvailableFor(
|
|
|
m_pDataManager, m_nGridInputRevision);
|
|
|
if(m_pInputSnapshot->m_bMayUseCachedGrid &&
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid =
|
|
|
m_pDataManager->getUnstructuredGrid();
|
|
|
if(m_pInputSnapshot->m_pSourceBaseGrid == nullptr ||
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid->GetNumberOfCells() <= 0) {
|
|
|
m_pInputSnapshot->m_bMayUseCachedGrid = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(m_pInputSnapshot->m_bAutoFitTargetOnly &&
|
|
|
m_pInputSnapshot->m_bMayUseCachedGrid) {
|
|
|
HX_NWTM_GRID_OUTPUT1 oGridOutput1;
|
|
|
HX_NWTM_GRID_OUTPUT2 oGridOutput2;
|
|
|
int nPebiCount = -1;
|
|
|
if(pGridService->copyCurrentGridFor(m_pDataManager,
|
|
|
m_nGridInputRevision,
|
|
|
oGridOutput1,
|
|
|
oGridOutput2,
|
|
|
nPebiCount)) {
|
|
|
m_pInputSnapshot->m_oGridResult.m_oGridOutput1 = oGridOutput1;
|
|
|
m_pInputSnapshot->m_oGridResult.m_oGridOutput2 = oGridOutput2;
|
|
|
m_pInputSnapshot->m_oGridResult.m_nPebiCount = nPebiCount;
|
|
|
m_pInputSnapshot->m_oGridResult.m_bSucceeded = true;
|
|
|
} else {
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = true;
|
|
|
}
|
|
|
} else if(!m_pInputSnapshot->m_bMayUseCachedGrid) {
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = true;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::prepareManualInput()
|
|
|
{
|
|
|
if(m_pInputSnapshot == nullptr ||
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Preparing input data..."), 0, 0);
|
|
|
nmCalculationPebiGrid* pGridService =
|
|
|
nmCalculationPebiGrid::getInstance();
|
|
|
if(pGridService == nullptr ||
|
|
|
!pGridService->prepareInputSnapshot(
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
&m_nCancelRequested) ||
|
|
|
isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Checking grid cache..."), 0, 0);
|
|
|
if(m_pInputSnapshot->m_bMayUseCachedGrid) {
|
|
|
nmPebiGridResult& oGridResult =
|
|
|
m_pInputSnapshot->m_oGridResult;
|
|
|
// 缓存直接复制到任务快照,避免大网格在局部对象中产生额外副本。
|
|
|
if(pGridService->copyCurrentGridFor(
|
|
|
m_pDataManager,
|
|
|
m_nGridInputRevision,
|
|
|
oGridResult.m_oGridOutput1,
|
|
|
oGridResult.m_oGridOutput2,
|
|
|
oGridResult.m_nPebiCount,
|
|
|
&m_nCancelRequested)) {
|
|
|
oGridResult.m_bSucceeded = true;
|
|
|
|
|
|
reportStage(tr("Preparing result grid..."), 0, 0);
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
m_pInputSnapshot->m_pBaseGrid =
|
|
|
vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
m_pInputSnapshot->m_pBaseGrid->DeepCopy(
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid);
|
|
|
if(isCancelRequested()) {
|
|
|
m_pInputSnapshot->m_pBaseGrid = nullptr;
|
|
|
return false;
|
|
|
}
|
|
|
if(m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
} else if(isCancelRequested()) {
|
|
|
return false;
|
|
|
} else {
|
|
|
// 捕获后缓存被其他网格任务替换时,仍按本任务快照在后台重建。
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return !isCancelRequested();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::wasSuccessful() const
|
|
|
{
|
|
|
return m_lastRunSucceeded;
|
|
|
}
|
|
|
|
|
|
int nmCalculationDllPebiSolverTask::getSolveTimeMs() const
|
|
|
{
|
|
|
return m_nSolveTimeMs;
|
|
|
}
|
|
|
|
|
|
int nmCalculationDllPebiSolverTask::getPebiCount() const
|
|
|
{
|
|
|
return m_nPebiCount;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double> > nmCalculationDllPebiSolverTask::getAutoFitResultPressure() const
|
|
|
{
|
|
|
return m_autoFitResultPressure;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double> > nmCalculationDllPebiSolverTask::getAutoFitResultLogLog() const
|
|
|
{
|
|
|
return m_autoFitResultLogLog;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double> > nmCalculationDllPebiSolverTask::getAutoFitResultSemiLog() const
|
|
|
{
|
|
|
return m_autoFitResultSemiLog;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::execute()
|
|
|
{
|
|
|
return !isCancelRequested() &&
|
|
|
prepareManualInput() &&
|
|
|
!isCancelRequested() &&
|
|
|
this->execPebiMode();
|
|
|
}
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::execPebiMode()
|
|
|
{
|
|
|
if(!m_bInputSnapshotValid || m_pInputSnapshot == nullptr ||
|
|
|
isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QAtomicInt* pCancelRequested =
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly
|
|
|
? NULL : &m_nCancelRequested;
|
|
|
|
|
|
// 第一步:清空本次任务自己的输出。旧成果仍保留在 DataManager 中,只有主线程
|
|
|
// 最终提交成功后才会被整体替换。
|
|
|
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();
|
|
|
|
|
|
nmPebiGridResult& oGridResult = m_pInputSnapshot->m_oGridResult;
|
|
|
if(m_pInputSnapshot->m_bRequiresGridCalculation) {
|
|
|
// 第二步:网格无效时只使用构造阶段捕获的值快照调用一次网格 DLL。
|
|
|
// 自动拟合候选不创建 VTK;完整求解创建局部 VTK,完成后回主线程提交。
|
|
|
const bool bCreateUnstructuredGrid =
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly;
|
|
|
reportStage(tr("Generating grid..."), 0, 0);
|
|
|
if(!nmCalculationPebiGrid::getInstance()->calculateSnapshot(
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
oGridResult,
|
|
|
bCreateUnstructuredGrid,
|
|
|
pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(bCreateUnstructuredGrid) {
|
|
|
if(oGridResult.m_pUnstructuredGrid == nullptr ||
|
|
|
oGridResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_pInputSnapshot->m_pBaseGrid =
|
|
|
vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
m_pInputSnapshot->m_pBaseGrid->DeepCopy(
|
|
|
oGridResult.m_pUnstructuredGrid);
|
|
|
if(isCancelRequested()) {
|
|
|
m_pInputSnapshot->m_pBaseGrid = nullptr;
|
|
|
return false;
|
|
|
}
|
|
|
m_pInputSnapshot->m_bGridResultNeedsCommit = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!oGridResult.m_bSucceeded ||
|
|
|
oGridResult.m_oGridOutput1.PEBI_cell.p.empty() ||
|
|
|
isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:由场景值快照重建完整模型输入。属性插值可以耗时,但它只读取任务
|
|
|
// 自己的点集副本,因此放在工作线程不会阻塞或竞争界面数据。
|
|
|
// 直接初始化可避免先构造临时模型输入、再复制完整 GRID 的额外内存峰值。
|
|
|
HX_NWTM_MODEL_INPUT oModelInput(oGridResult.m_oGridOutput2);
|
|
|
QString sInterpolationError;
|
|
|
reportStage(tr("Preparing model input..."), 0, 0);
|
|
|
if(!buildModelInputFromSnapshot(*m_pInputSnapshot,
|
|
|
oModelInput,
|
|
|
sInterpolationError,
|
|
|
pCancelRequested)) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const QString sLogMessage =
|
|
|
QString("Property interpolation failed: %1")
|
|
|
.arg(sInterpolationError);
|
|
|
qWarning() << sLogMessage;
|
|
|
zxLogInstance::getInstance()->writeLogF(sLogMessage);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:建网、模型求解和 Kriging 共用 DLL 全局状态,整个 DLL 调用必须串行。
|
|
|
reportStage(tr("Waiting for solver..."), 0, 0);
|
|
|
nmSolverDllMutexLocker oDllLocker;
|
|
|
if(!oDllLocker.lock(nmCalculationUtils::getHxNwtmDllMutex(),
|
|
|
pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
HMODULE hModelModule = LoadLibrary(L"HX_NWTM.dll");
|
|
|
if(hModelModule == nullptr) {
|
|
|
qWarning() << "Failed to load HX_NWTM.dll. Error code:"
|
|
|
<< GetLastError();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
typedef void (*HX_NWTM_MODEL_Fun)(
|
|
|
HX_NWTM_MODEL_OUTPUT&,
|
|
|
const HX_NWTM_MODEL_INPUT&,
|
|
|
std::string);
|
|
|
typedef void (*SetIntValueFunc)(int);
|
|
|
typedef int (*GetIntValueFunc)();
|
|
|
|
|
|
HX_NWTM_MODEL_Fun pfnModel =
|
|
|
reinterpret_cast<HX_NWTM_MODEL_Fun>(
|
|
|
GetProcAddress(hModelModule, "HX_NWTM_MODEL"));
|
|
|
SetIntValueFunc pfnSetSolverType =
|
|
|
reinterpret_cast<SetIntValueFunc>(
|
|
|
GetProcAddress(hModelModule, "set_solvetype"));
|
|
|
SetIntValueFunc pfnSetOmpThreads =
|
|
|
reinterpret_cast<SetIntValueFunc>(
|
|
|
GetProcAddress(hModelModule, "set_omp_threads"));
|
|
|
SetIntValueFunc pfnSetIluReuseSteps =
|
|
|
reinterpret_cast<SetIntValueFunc>(
|
|
|
GetProcAddress(hModelModule, "set_ilu_reuse_steps"));
|
|
|
GetIntValueFunc pfnGetSolveTime =
|
|
|
reinterpret_cast<GetIntValueFunc>(
|
|
|
GetProcAddress(hModelModule, "getsolvetime"));
|
|
|
|
|
|
if(pfnModel == nullptr || pfnSetSolverType == nullptr ||
|
|
|
pfnSetOmpThreads == nullptr || pfnSetIluReuseSteps == nullptr ||
|
|
|
pfnGetSolveTime == nullptr) {
|
|
|
qWarning() << "Failed to resolve PEBI solver configuration interface.";
|
|
|
FreeLibrary(hModelModule);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
HX_NWTM_MODEL_OUTPUT oModelOutput;
|
|
|
try {
|
|
|
pfnSetSolverType(m_pInputSnapshot->m_nSolverType);
|
|
|
if(m_pInputSnapshot->m_nSolverType ==
|
|
|
nmDataAnalyzeManager::PebiSolverCpuAccelerated) {
|
|
|
pfnSetOmpThreads(m_pInputSnapshot->m_nOmpThreads);
|
|
|
pfnSetIluReuseSteps(m_pInputSnapshot->m_nIluReuseSteps);
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Solving model..."), 0, 0);
|
|
|
pfnModel(oModelOutput,
|
|
|
oModelInput,
|
|
|
m_pInputSnapshot->m_oGridInput.m_sLicensePath
|
|
|
.toStdString());
|
|
|
m_nPebiCount = oGridResult.m_nPebiCount;
|
|
|
m_nSolveTimeMs = pfnGetSolveTime();
|
|
|
} catch(const std::exception& e) {
|
|
|
qWarning() << QString("C++ Exception during HX_NWTM_MODEL call: %1")
|
|
|
.arg(e.what());
|
|
|
logHX_NWTM_MODEL_INPUT_Simplified(oModelInput);
|
|
|
FreeLibrary(hModelModule);
|
|
|
return false;
|
|
|
} catch(...) {
|
|
|
qWarning() << "SEH Exception Occurred during HX_NWTM_MODEL call";
|
|
|
logHX_NWTM_MODEL_INPUT_Simplified(oModelInput);
|
|
|
FreeLibrary(hModelModule);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 后处理只读取本次局部输出,无需继续占用进程级 DLL 锁。
|
|
|
FreeLibrary(hModelModule);
|
|
|
oDllLocker.unlock();
|
|
|
|
|
|
// HX_NWTM_MODEL 没有停止入口;调用期间收到请求时,返回后直接丢弃输出。
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第五步:把井曲线和场压力构造成任务局部结果。该函数同样只读取输入快照。
|
|
|
const bool bSucceeded = buildPebiModeResult(
|
|
|
oModelOutput,
|
|
|
oModelInput.T,
|
|
|
oGridResult.m_oGridOutput1);
|
|
|
return bSucceeded;
|
|
|
}
|
|
|
|
|
|
std::vector<double> HX_logderivative(const std::vector<double>& x, const std::vector<double>& y, const int& n)
|
|
|
{
|
|
|
// 功能: 对数导数函数
|
|
|
// 作者: 何辉
|
|
|
// 日期: 2024.07.16
|
|
|
// 单位: 西安华线石油科技有限公司(西安石油大学)
|
|
|
std::vector<double> d; d.resize(n - 1);
|
|
|
d[0] = 0.5 * (y[1] - y[0]) / (x[1] - x[0]) * (x[1] + x[0]);
|
|
|
d[0] = max(d[0], DBL_EPSILON);
|
|
|
for (int i = 1; i < n - 2; ++i)
|
|
|
{
|
|
|
d[i] = (y[i - 1] * (x[i] - x[i + 1]) / ((x[i - 1] - x[i]) * (x[i - 1] - x[i + 1])) +
|
|
|
y[i] * (2 * x[i] - x[i - 1] - x[i + 1]) / ((x[i] - x[i - 1]) * (x[i] - x[i + 1])) +
|
|
|
y[i + 1] * (x[i] - x[i - 1]) / ((x[i - 1] - x[i + 1]) * (x[i] - x[i + 1]))) * x[i];
|
|
|
d[i] = max(d[i], DBL_EPSILON);
|
|
|
}
|
|
|
d[n - 2] = 0.5 * (y[n - 2] - y[n - 3]) / (x[n - 2] - x[n - 3]) * (x[n - 2] + x[n - 3]);
|
|
|
d[n - 2] = max(d[n - 2], DBL_EPSILON);
|
|
|
return d;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
|
|
|
HX_NWTM_MODEL_OUTPUT& p1,
|
|
|
int modelType,
|
|
|
const HX_NWTM_GRID_OUTPUT1& oGridOutput)
|
|
|
{
|
|
|
if(m_pInputSnapshot == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const bool autoFitTargetOnly =
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly;
|
|
|
bool autoFitTargetFound = false;
|
|
|
m_autoFitResultPressure.clear();
|
|
|
m_autoFitResultLogLog.clear();
|
|
|
m_autoFitResultSemiLog.clear();
|
|
|
|
|
|
QVector<QVector<double>> vvecPressure;
|
|
|
QVector<QVector<double>> vvecLogLog;
|
|
|
QVector<QVector<double>> vvecSemiLog;
|
|
|
// p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure.
|
|
|
// 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力.
|
|
|
const bool usePseudoPressure = (modelType == static_cast<int>(SMT_Gas_VariablePvt));
|
|
|
CalPseudoPressure calPseudoPressure = usePseudoPressure
|
|
|
? getPseudoPressureConverter()
|
|
|
: nullptr;
|
|
|
|
|
|
if(usePseudoPressure && calPseudoPressure == nullptr) {
|
|
|
qWarning() << "Failed to load the gas pseudo-pressure converter.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 井顺序与后处理输入都来自任务启动时的同一份快照。
|
|
|
const QVector<nmSolverWellRef>& vecWellsOrder =
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder;
|
|
|
const QVector<nmPebiWellInputSnapshot>& vecWellInputs =
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs;
|
|
|
|
|
|
// 第一步:真实井必须逐口具有与公共时间轴等长的井底压力。
|
|
|
// 观察井虽然没有源汇项,也必须由 DLL 返回压力,否则不能形成有效多井结果。
|
|
|
if(vecWellsOrder.isEmpty() ||
|
|
|
vecWellInputs.size() != vecWellsOrder.size() ||
|
|
|
p1.t.empty()) {
|
|
|
return false;
|
|
|
}
|
|
|
for(size_t nTimeIndex = 0; nTimeIndex < p1.t.size(); ++nTimeIndex) {
|
|
|
if((nTimeIndex % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(!isFiniteSolverNumber(p1.t[nTimeIndex])) {
|
|
|
qWarning() << "PEBI returned a non-finite time at index:"
|
|
|
<< static_cast<int>(nTimeIndex);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const nmSolverWellRef& oWellRef = vecWellsOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
continue;
|
|
|
}
|
|
|
if(nIndex >= static_cast<int>(p1.pw.size()) ||
|
|
|
p1.pw[nIndex].empty() ||
|
|
|
p1.pw[nIndex].size() != p1.t.size() ||
|
|
|
!vecWellInputs[nIndex].m_bRealWell ||
|
|
|
vecWellInputs[nIndex].m_sWellCode != oWellRef.m_sWellCode) {
|
|
|
qWarning() << "Incomplete well pressure returned for WellCode:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
for(size_t nTimeIndex = 0;
|
|
|
nTimeIndex < p1.pw[nIndex].size();
|
|
|
++nTimeIndex) {
|
|
|
if((nTimeIndex % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(!isFiniteSolverNumber(p1.pw[nIndex][nTimeIndex])) {
|
|
|
qWarning() << "PEBI returned a non-finite pressure for WellCode:"
|
|
|
<< oWellRef.m_sWellCode
|
|
|
<< "time index:" << static_cast<int>(nTimeIndex);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历每口井,处理其数据
|
|
|
reportStage(tr("Processing well results..."),
|
|
|
0,
|
|
|
vecWellsOrder.size());
|
|
|
for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) {
|
|
|
const nmSolverWellRef& oWellRef = vecWellsOrder[wellIdx];
|
|
|
const nmPebiWellInputSnapshot& oWellInput =
|
|
|
vecWellInputs[wellIdx];
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(autoFitTargetOnly &&
|
|
|
oWellRef.m_sWellCode != m_sAutoFitTargetWellCode) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 手工裂缝不是结果井,只用于保持 DLL 下标与网格输入一致。
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
reportStage(tr("Processing well results..."),
|
|
|
wellIdx + 1,
|
|
|
vecWellsOrder.size());
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 3.1 填充井底压力数据到局部变量
|
|
|
QVector<double> currentWellTime;
|
|
|
QVector<double> currentWellPressure;
|
|
|
|
|
|
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
currentWellTime.append(p1.t[i]);
|
|
|
currentWellPressure.append(p1.pw[wellIdx][i]);
|
|
|
}
|
|
|
|
|
|
vvecPressure.clear(); // 清空上次循环的数据,为当前井准备
|
|
|
vvecPressure.append(currentWellTime);
|
|
|
vvecPressure.append(currentWellPressure);
|
|
|
|
|
|
// 3.2 计算双对数和半对数曲线数据。井对象可能已被界面修改,后台只允许
|
|
|
// 使用构造阶段复制出的流量、流动段和坐标。
|
|
|
if(!oWellInput.m_bRealWell ||
|
|
|
oWellInput.m_sWellCode != oWellRef.m_sWellCode) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const bool bRateControlled =
|
|
|
oWellInput.m_bRateControlled &&
|
|
|
oWellInput.m_vecFlowPoints.size() >= 2;
|
|
|
|
|
|
vvecLogLog.clear();
|
|
|
vvecSemiLog.clear();
|
|
|
|
|
|
if(bRateControlled) {
|
|
|
// 准备压力数据 (用于传递给外部 DLL)
|
|
|
std::vector<Point> wellPressureDataForDll;
|
|
|
|
|
|
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
Point pt;
|
|
|
pt.x = p1.t[i];
|
|
|
|
|
|
// 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力.
|
|
|
double pressureForLog = p1.pw[wellIdx][i];
|
|
|
if(usePseudoPressure
|
|
|
&& (!isFiniteSolverNumber(pressureForLog)
|
|
|
// -1 与主界面调用一致, 表示使用 configPsAbouts() 配置的当前模式.
|
|
|
|| !calPseudoPressure(pressureForLog, pressureForLog, -1)
|
|
|
|| !isFiniteSolverNumber(pressureForLog))) {
|
|
|
qWarning() << "Failed to convert gas pressure to pseudo-pressure:"
|
|
|
<< p1.pw[wellIdx][i]
|
|
|
<< "well code:" << oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
pt.y = pressureForLog;
|
|
|
pt.z = 0.0;
|
|
|
wellPressureDataForDll.push_back(pt);
|
|
|
}
|
|
|
|
|
|
// 准备流量段数据
|
|
|
const QVector<QPointF>& vecTimeQ =
|
|
|
oWellInput.m_vecFlowPoints;
|
|
|
// 防御空流量数据,避免 -1 转成 std::vector 的巨大无符号长度。
|
|
|
int nTimeNumQ = qMax(0, vecTimeQ.size() - 1); // 移除第一个 0 点
|
|
|
std::vector<double> timeQ(nTimeNumQ);
|
|
|
std::vector<double> q(nTimeNumQ);
|
|
|
|
|
|
for(int i = 0; i < nTimeNumQ; ++i) {
|
|
|
timeQ[i] = vecTimeQ[i + 1].x();
|
|
|
q[i] = vecTimeQ[i + 1].y();
|
|
|
}
|
|
|
|
|
|
// 调用外部 DLL 计算双对数曲线
|
|
|
std::vector<Point> logPreResultFromDll; // 存储 DLL 的计算结果
|
|
|
const int iSectionFlowIndex =
|
|
|
oWellInput.m_nFlowSectionIndex;
|
|
|
|
|
|
// 第一步:无产量观察井只接收压力结果,不调用依赖产量制度的曲线 DLL。
|
|
|
HMODULE hMod_solver = nTimeNumQ > 0
|
|
|
? LoadLibrary(L"singlePhaseSolverDll.dll") : nullptr;
|
|
|
|
|
|
if(nTimeNumQ <= 0) {
|
|
|
// 第二步:保持固定的数据列结构,便于结果保存和后续读取;
|
|
|
// 各列为空明确表示该观察井没有可展示的双对数、半对数结果。
|
|
|
vvecLogLog.clear();
|
|
|
vvecLogLog.append(QVector<double>());
|
|
|
vvecLogLog.append(QVector<double>());
|
|
|
vvecLogLog.append(QVector<double>());
|
|
|
vvecSemiLog.clear();
|
|
|
vvecSemiLog.append(QVector<double>());
|
|
|
vvecSemiLog.append(QVector<double>());
|
|
|
} else if(hMod_solver) {
|
|
|
typedef bool (*PreLog)(const std::vector<Point>&, const int&, double*, double*, int, std::vector<Point>&);
|
|
|
PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre");
|
|
|
|
|
|
if(nullptr == preLogFun) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
std::cout << "preLogFun failed!\n";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 气井传入拟压力序列, 油井和水井仍传入原始压力序列.
|
|
|
// 计算失败必须向上返回, 避免 PSO 使用空曲线或上一粒子的旧曲线.
|
|
|
if(!preLogFun(wellPressureDataForDll,
|
|
|
iSectionFlowIndex,
|
|
|
timeQ.data(),
|
|
|
q.data(),
|
|
|
nTimeNumQ,
|
|
|
logPreResultFromDll)) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 填充双对数曲线数据到局部变量
|
|
|
QVector<double> logX, logY, logZ;
|
|
|
|
|
|
// 检查结果是否为空,并跳过第一个点
|
|
|
if (logPreResultFromDll.size() > 1) {
|
|
|
// 从索引 1 开始遍历,跳过索引 0 的第一个点
|
|
|
for (size_t i = 1; i < logPreResultFromDll.size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
return false;
|
|
|
}
|
|
|
const auto& pt = logPreResultFromDll[i];
|
|
|
if(!isValidLogLogPoint(pt)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
logX.append(pt.x);
|
|
|
logY.append(pt.y);
|
|
|
logZ.append(pt.z);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vvecLogLog.clear(); // 清空上次循环的数据
|
|
|
vvecLogLog.append(logX);
|
|
|
vvecLogLog.append(logY);
|
|
|
vvecLogLog.append(logZ);
|
|
|
|
|
|
// 填充半对数曲线数据到局部变量
|
|
|
QVector<double> semiLogX, semiLogY;
|
|
|
|
|
|
// 检查结果是否为空,并跳过第一个点
|
|
|
if (logPreResultFromDll.size() > 1) {
|
|
|
// 半对数曲线也应该同步跳过第一个点
|
|
|
for (size_t i = 1; i < logPreResultFromDll.size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
return false;
|
|
|
}
|
|
|
const auto& pt = logPreResultFromDll[i];
|
|
|
if(!isValidSemiLogPoint(pt)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
semiLogX.append(pt.x);
|
|
|
semiLogY.append(pt.pointData[0]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vvecSemiLog.clear(); // 清空上次循环的数据
|
|
|
vvecSemiLog.append(semiLogX);
|
|
|
vvecSemiLog.append(semiLogY);
|
|
|
|
|
|
FreeLibrary(hMod_solver);
|
|
|
} else {
|
|
|
qWarning() << "Failed to load singlePhaseSolverDll.dll.";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(autoFitTargetOnly) {
|
|
|
// 粒子评价结果保存在任务对象内,避免反复改写 DataManager.
|
|
|
m_autoFitResultPressure = vvecPressure;
|
|
|
m_autoFitResultLogLog = vvecLogLog;
|
|
|
m_autoFitResultSemiLog = vvecSemiLog;
|
|
|
autoFitTargetFound = true;
|
|
|
break;
|
|
|
} else {
|
|
|
// 所有真实井先写入任务局部快照;观察井的双对数和半对数保持为空。
|
|
|
// 任意后续步骤失败时,整个快照会被丢弃,旧成果不会被局部覆盖。
|
|
|
nmPebiWellResultSnapshot oWellResult;
|
|
|
oWellResult.m_sWellCode = oWellRef.m_sWellCode;
|
|
|
oWellResult.m_vecPressure = vvecPressure;
|
|
|
oWellResult.m_vecLogLog = vvecLogLog;
|
|
|
oWellResult.m_vecSemiLog = vvecSemiLog;
|
|
|
oWellResult.m_oLocation = oWellInput.m_oLocation;
|
|
|
m_vecPendingWellResults.append(oWellResult);
|
|
|
}
|
|
|
|
|
|
reportStage(tr("Processing well results..."),
|
|
|
wellIdx + 1,
|
|
|
vecWellsOrder.size());
|
|
|
}
|
|
|
|
|
|
if(autoFitTargetOnly) {
|
|
|
const bool pressureValid = autoFitTargetFound
|
|
|
&& m_autoFitResultPressure.size() >= 2
|
|
|
&& !m_autoFitResultPressure[0].isEmpty()
|
|
|
&& m_autoFitResultPressure[0].size() == m_autoFitResultPressure[1].size();
|
|
|
const bool logLogValid = m_autoFitResultLogLog.size() >= 3
|
|
|
&& !m_autoFitResultLogLog[0].isEmpty()
|
|
|
&& m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[1].size()
|
|
|
&& m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[2].size();
|
|
|
return pressureValid && logLogValid;
|
|
|
}
|
|
|
|
|
|
// 第四步:完整求解还必须具有与时间轴、网格绘图单元严格对应的场压力。
|
|
|
if(p1.p.size() != p1.t.size() ||
|
|
|
oGridOutput.PEBI_cell.isplot.empty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 计算有效单元数量
|
|
|
size_t actualPlotCellsCount = 0;
|
|
|
|
|
|
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(oGridOutput.PEBI_cell.isplot[i] == 1) {
|
|
|
actualPlotCellsCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(actualPlotCellsCount == 0) {
|
|
|
return false;
|
|
|
}
|
|
|
for(size_t nTimeIndex = 0; nTimeIndex < p1.p.size(); ++nTimeIndex) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(p1.p[nTimeIndex].size() < oGridOutput.PEBI_cell.isplot.size()) {
|
|
|
qWarning() << "Incomplete field pressure returned for time index:"
|
|
|
<< static_cast<int>(nTimeIndex);
|
|
|
return false;
|
|
|
}
|
|
|
for(size_t nCellIndex = 0;
|
|
|
nCellIndex < oGridOutput.PEBI_cell.isplot.size();
|
|
|
++nCellIndex) {
|
|
|
if((nCellIndex % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(oGridOutput.PEBI_cell.isplot[nCellIndex] == 1 &&
|
|
|
!isFiniteSolverNumber(p1.p[nTimeIndex][nCellIndex])) {
|
|
|
qWarning() << "PEBI returned a non-finite field pressure at time/cell:"
|
|
|
<< static_cast<int>(nTimeIndex)
|
|
|
<< static_cast<int>(nCellIndex);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第五步:在局部时间步映射中构造场压力,同时计算全局标量范围。
|
|
|
// 此处不触碰 DataManager,较短的新时间轴会在主线程整体替换时自然清除旧帧。
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
double dMinP = DBL_MAX;
|
|
|
double dMaxP = -DBL_MAX;
|
|
|
|
|
|
// 为每个时间步生成完整的 VTK 压力数组。
|
|
|
reportStage(tr("Processing field results..."),
|
|
|
0,
|
|
|
static_cast<int>(p1.p.size()));
|
|
|
for(size_t timeIdx = 0; timeIdx < p1.p.size(); ++timeIdx) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
// 获取当前时间步的时间值
|
|
|
double currentTime = p1.t[timeIdx];
|
|
|
|
|
|
// 创建 vtkDoubleArray 来存储当前时间步的压力数据
|
|
|
vtkSmartPointer<vtkDoubleArray> pressureData = vtkSmartPointer<vtkDoubleArray>::New();
|
|
|
pressureData->SetName("p"); // 设置标量数据的名称
|
|
|
|
|
|
// 直接使用外面计算好的 actualPlotCellsCount
|
|
|
pressureData->SetNumberOfValues(actualPlotCellsCount);
|
|
|
|
|
|
size_t destIdx = 0; // 目标数组的索引
|
|
|
double dCurrentTimeMin = DBL_MAX; // 当前时间步的最小值
|
|
|
double dCurrentTimeMax = -DBL_MAX; // 当前时间步的最大值
|
|
|
|
|
|
// 遍历原始数据,并根据 isplot 填充到 pressureData
|
|
|
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(oGridOutput.PEBI_cell.isplot[i] == 1) { // 只有当 isplot 为1时才考虑这个单元格
|
|
|
pressureData->SetValue(destIdx, p1.p[timeIdx][i]);
|
|
|
|
|
|
// 更新当前时间步的范围
|
|
|
dCurrentTimeMin = qMin(dCurrentTimeMin, p1.p[timeIdx][i]);
|
|
|
dCurrentTimeMax = qMax(dCurrentTimeMax, p1.p[timeIdx][i]);
|
|
|
|
|
|
destIdx++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新全局范围
|
|
|
dMinP = qMin(dMinP, dCurrentTimeMin);
|
|
|
dMaxP = qMax(dMaxP, dCurrentTimeMax);
|
|
|
|
|
|
m_mapPendingTimeSteps.insert(currentTime, pressureData);
|
|
|
|
|
|
const int nCompleted = static_cast<int>(timeIdx + 1);
|
|
|
const int nTotal = static_cast<int>(p1.p.size());
|
|
|
const int nReportStep = qMax(1, nTotal / 100);
|
|
|
if(nCompleted == nTotal || (nCompleted % nReportStep) == 0) {
|
|
|
reportStage(tr("Processing field results..."),
|
|
|
nCompleted,
|
|
|
nTotal);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// QMap 使用时间作为唯一键;重复时间会覆盖旧帧,因此必须检查数量一致。
|
|
|
if(m_mapPendingTimeSteps.size() != static_cast<int>(p1.t.size())) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第六步:基础网格在任务创建或局部建网阶段已经完成深拷贝。后台结果构造
|
|
|
// 不再读取 DataManager 中可能被网格窗口替换的 VTK 指针。
|
|
|
m_pPendingResultGrid = m_pInputSnapshot->m_pBaseGrid;
|
|
|
if(m_pPendingResultGrid == nullptr ||
|
|
|
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int nExpectedWellResultCount = 0;
|
|
|
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
|
|
|
if(vecWellsOrder[nIndex].m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
++nExpectedWellResultCount;
|
|
|
}
|
|
|
}
|
|
|
if(m_vecPendingWellResults.size() != nExpectedWellResultCount) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_dPendingScalarMin = dMinP;
|
|
|
m_dPendingScalarMax = dMaxP;
|
|
|
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(
|
|
|
nmDataAnalyzeManager* pDataManager)
|
|
|
{
|
|
|
// 第一步:自动拟合只返回目标曲线,不允许提交完整成果;手工求解必须回到
|
|
|
// DataManager 所属线程执行,保证界面看不到逐项替换过程中的中间状态。
|
|
|
if(m_pInputSnapshot == nullptr ||
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
wasCancelled() || isCancelRequested() ||
|
|
|
!m_bPendingFullResultReady ||
|
|
|
m_pPendingResultSnapshot.isNull() ||
|
|
|
pDataManager == nullptr ||
|
|
|
pDataManager != m_pDataManager ||
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
discardPendingFullResult();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
pDataManager->getNumericalAnalysisCase();
|
|
|
if(pAnalysisCase == nullptr ||
|
|
|
pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision ||
|
|
|
m_mapPendingTimeSteps.isEmpty() ||
|
|
|
m_pPendingResultGrid == nullptr ||
|
|
|
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
|
|
|
discardPendingFullResult();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:即使外部遗漏版本递增,同编码新井也不能接收旧任务结果。
|
|
|
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;
|
|
|
}
|
|
|
setWellIds.insert(pWell->m_sWellInstanceId);
|
|
|
}
|
|
|
|
|
|
// 第三步:任务后台生成了新网格时,先在当前主线程按同一输入版本提交。
|
|
|
// 已有网格路径则再次确认其仍然有效。两条路径都不允许旧任务覆盖新编辑。
|
|
|
if(m_pInputSnapshot->m_bGridResultNeedsCommit) {
|
|
|
if(!nmCalculationPebiGrid::getInstance()->commitSnapshotResult(
|
|
|
pDataManager,
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
m_pInputSnapshot->m_oGridResult)) {
|
|
|
discardPendingFullResult();
|
|
|
return false;
|
|
|
}
|
|
|
} else if(!pAnalysisCase->isGridValid()) {
|
|
|
discardPendingFullResult();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步: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;
|
|
|
}
|
|
|
|
|
|
// 成功和失败都不允许任务继续持有候选可写别名;旧快照只由 Manager 决定。
|
|
|
discardPendingFullResult();
|
|
|
return bCommitted;
|
|
|
}
|
|
|
|
|
|
//bool nmCalculationDllPebiSolverTask::savePebiModeResult(HX_NWTM_MODEL_OUTPUT& p1)
|
|
|
//{
|
|
|
// nmDataAnalyzeManager* pDataInstance = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
//
|
|
|
// QVector<QVector<double>> vvecPressure;
|
|
|
// QVector<QVector<double>> vvecLogLog;
|
|
|
// QVector<QVector<double>> vvecSemiLog;
|
|
|
//
|
|
|
// // 获取参与求解的井的顺序
|
|
|
// QVector<QPair<NM_WELL_MODEL, QString>> vecWellsOrder = nmDataAnalyzeManager::getCurrentInstance()->getCalculationWells();
|
|
|
//
|
|
|
// // 清空井名和二维位置的映射
|
|
|
// pDataInstance->clearWellLocations();
|
|
|
//
|
|
|
// // 遍历每口井,处理其数据
|
|
|
// for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) {
|
|
|
// NM_WELL_MODEL eWellType = vecWellsOrder[wellIdx].first; // 获取井的类型
|
|
|
// QString sWellName = vecWellsOrder[wellIdx].second; // 获取井的名称
|
|
|
//
|
|
|
// // 跳过裂缝(或未知井类型)
|
|
|
// if(eWellType == NM_WELL_MODEL::Unknow_Well) {
|
|
|
// continue;
|
|
|
// }
|
|
|
//
|
|
|
// // 3.1 填充井底压力数据到局部变量
|
|
|
// QVector<double> currentWellTime;
|
|
|
// QVector<double> currentWellPressure; // P_wf(t)
|
|
|
//
|
|
|
// // 确保 p1.pw[wellIdx] 存在且大小与 p1.t 匹配
|
|
|
// if(wellIdx < p1.pw.size()) {
|
|
|
// for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
|
|
|
// if(i < p1.t.size()) { // 确保时间数据也存在
|
|
|
// currentWellTime.append(p1.t[i]);
|
|
|
// currentWellPressure.append(p1.pw[wellIdx][i]);
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
//
|
|
|
// vvecPressure.clear(); // 清空上次循环的数据,为当前井准备
|
|
|
// vvecPressure.append(currentWellTime);
|
|
|
// vvecPressure.append(currentWellPressure);
|
|
|
//
|
|
|
// // 3.2 计算双对数和半对数曲线数据并存储到局部变量
|
|
|
// nmDataWellBase* pWellData = nmDataAnalyzeManager::getCurrentInstance()->findWellByName(sWellName);
|
|
|
//
|
|
|
// if(pWellData) {
|
|
|
//
|
|
|
// // --- START: 替换为 HX_logderivative 计算双对数数据 ---
|
|
|
//
|
|
|
// // 1. 获取初始压力 Pi,用于计算压力降 DeltaP
|
|
|
// double initialPressure = nmDataAnalyzeManager::getCurrentInstance()->getReservoirData()->getInitialPressure().getValue().toDouble();
|
|
|
//
|
|
|
// // 为了演示,如果无法获取 Pi,我们将使用数组中第一个压力点作为初始压力 Pi
|
|
|
// //if (!currentWellPressure.isEmpty()) {
|
|
|
// // // 通常第一个压力点 Pwf(t=0) 就是初始压力 Pi
|
|
|
// // initialPressure = currentWellPressure.first();
|
|
|
// //}
|
|
|
//
|
|
|
// // 2. 准备 t (x) 和 DeltaP (y) 的 std::vector
|
|
|
// std::vector<double> t_vec; // 时间 t (x)
|
|
|
// std::vector<double> deltaP_vec; // 压力降 DeltaP = Pi - Pwf(t) (y)
|
|
|
// int N = currentWellTime.size();
|
|
|
//
|
|
|
// for (int i = 0; i < N; ++i) {
|
|
|
// t_vec.push_back(currentWellTime[i]);
|
|
|
// // 计算压力降
|
|
|
// deltaP_vec.push_back(initialPressure - currentWellPressure[i]);
|
|
|
// }
|
|
|
//
|
|
|
// // 3. 调用 HX_logderivative 计算对数导数 (P')
|
|
|
// std::vector<double> logDerivative_vec;
|
|
|
//
|
|
|
// if (N > 2) { // 确保有足够的数据点 (N-1 个导数点)
|
|
|
// logDerivative_vec = HX_logderivative(t_vec, deltaP_vec, N);
|
|
|
// } else {
|
|
|
// // 数据点不足,无法计算导数
|
|
|
// logDerivative_vec.clear();
|
|
|
// }
|
|
|
//
|
|
|
// // 4. 组装双对数曲线数据 (t, DeltaP, P'),严格遵循绘图规则
|
|
|
// QVector<double> logX, logY, logZ; // logX: t_plot, logY: DeltaP_plot, logZ: P'
|
|
|
// size_t M = logDerivative_vec.size(); // 导数点数量 M = N - 1
|
|
|
//
|
|
|
// if (M >= 1) {
|
|
|
//
|
|
|
// // --- 4.1 第一个点 (索引 i=0) ---
|
|
|
// // 导数时间: t_plot = 0.5 * (x[1] + x[0])
|
|
|
// logX.append(0.5 * (t_vec[1] + t_vec[0]));
|
|
|
// // 压力降 (近似): DeltaP[0] 和 DeltaP[1] 的平均值
|
|
|
// logY.append((deltaP_vec[0] + deltaP_vec[1]) / 2.0);
|
|
|
// // 导数: d[0]
|
|
|
// logZ.append(logDerivative_vec[0]);
|
|
|
//
|
|
|
//
|
|
|
// // --- 4.2 中间点 (索引 i=1 到 N-3) ---
|
|
|
// // 在 logDerivative_vec 中,这些点对应索引 i=1 到 M-1 (即 N-3)
|
|
|
// for (size_t i = 1; i < (size_t)N - 2; ++i) {
|
|
|
// // 导数时间: t_plot = x[i]
|
|
|
// logX.append(t_vec[i]);
|
|
|
// // 压力降: 匹配原始数据 DeltaP[i]
|
|
|
// logY.append(deltaP_vec[i]);
|
|
|
// // 导数: d[i]
|
|
|
// logZ.append(logDerivative_vec[i]);
|
|
|
// }
|
|
|
//
|
|
|
// // --- 4.3 最后一个点 (索引 i=N-2) ---
|
|
|
// if (M >= 2) { // 确保至少有 2 个导数点
|
|
|
// size_t lastIdx = M - 1; // 对应 logDerivative_vec[N-2]
|
|
|
// // 导数时间: t_plot = 0.5 * (x[n-2] + x[n-3])
|
|
|
// // 对应 t_vec[N-2] 和 t_vec[N-3]
|
|
|
// logX.append(0.5 * (t_vec[N - 2] + t_vec[N - 3]));
|
|
|
//
|
|
|
// // 压力降 (近似): DeltaP[N-2] 和 DeltaP[N-3] 的平均值
|
|
|
// logY.append((deltaP_vec[N - 2] + deltaP_vec[N - 3]) / 2.0);
|
|
|
//
|
|
|
// // 导数: d[N-2]
|
|
|
// logZ.append(logDerivative_vec[lastIdx]);
|
|
|
// }
|
|
|
// }
|
|
|
//
|
|
|
// // 将组装好的数据存储到 vvecLogLog
|
|
|
// vvecLogLog.clear(); // 清空上次循环的数据
|
|
|
// vvecLogLog.append(logX); // 时间 (t_plot)
|
|
|
// vvecLogLog.append(logY); // 压力降 (DeltaP_plot)
|
|
|
// vvecLogLog.append(logZ); // 对数导数 (P')
|
|
|
//
|
|
|
//
|
|
|
// // 5. 组装半对数曲线数据
|
|
|
// // 半对数图 Y 轴是井底压力 Pwf(t),X 轴是时间 t
|
|
|
// // vvecSemiLog 存储 [t, Pwf(t)],使用原始数据即可
|
|
|
// vvecSemiLog.clear();
|
|
|
// vvecSemiLog.append(currentWellTime); // X轴: 时间 t
|
|
|
// vvecSemiLog.append(currentWellPressure); // Y轴: 井底压力 Pwf(t)
|
|
|
//
|
|
|
// // --- END: 替换为 HX_logderivative 计算双对数数据 ---
|
|
|
//
|
|
|
// } // end if(pWellData)
|
|
|
//
|
|
|
// // 将计算结果保存到对应的井数据里
|
|
|
// // 压力
|
|
|
// pWellData->setResultPressure(vvecPressure);
|
|
|
// // 双对数
|
|
|
// pWellData->setResultLogLog(vvecLogLog);
|
|
|
// // 半对数
|
|
|
// pWellData->setResultSemiLog(vvecSemiLog);
|
|
|
//
|
|
|
// // 存储当前井名称和二维位置到映射
|
|
|
// QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble());
|
|
|
// pDataInstance->addWellLocation(sWellName, ptWellCoords);
|
|
|
// }
|
|
|
|
|
|
// 主日志函数:打印 HX_NWTM_MODEL_INPUT 的内容
|
|
|
void nmCalculationDllPebiSolverTask::logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0)
|
|
|
{
|
|
|
|
|
|
// 1. Basic Parameters (T)
|
|
|
qDebug() << QString("Parameter T: %1").arg(p0.T);
|
|
|
|
|
|
// 2. GRID Data
|
|
|
qDebug() << "\n--- GRID Data ---";
|
|
|
qDebug() << QString("GRID.Trinodexy: Capacity %1").arg(p0.GRID.Trinodexy.size());
|
|
|
qDebug() << QString("GRID.Area: Capacity %1").arg(p0.GRID.Area.size());
|
|
|
qDebug() << QString("GRID.D: Capacity %1").arg(p0.GRID.D.size());
|
|
|
|
|
|
// 2.1 GRID.ZhiJingNeiBianJie
|
|
|
qDebug() << "\n -- GRID.ZhiJingNeiBianJie --";
|
|
|
qDebug() << QString(" n: %1").arg(p0.GRID.ZhiJingNeiBianJie.n);
|
|
|
qDebug() << QString(" XiLinw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.XiLinw.size());
|
|
|
qDebug() << QString(" lw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.lw.size());
|
|
|
qDebug() << QString(" dw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.dw.size());
|
|
|
qDebug() << QString(" rw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.rw.size());
|
|
|
qDebug() << QString(" inwell: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.inwell.size());
|
|
|
|
|
|
// 2.2 GRID.LieFengJingNeiBianJie
|
|
|
qDebug() << "\n -- GRID.LieFengJingNeiBianJie --";
|
|
|
qDebug() << QString(" n: %1").arg(p0.GRID.LieFengJingNeiBianJie.n);
|
|
|
qDebug() << QString(" XiLinf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.XiLinf.size());
|
|
|
qDebug() << QString(" lf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.lf.size());
|
|
|
qDebug() << QString(" df: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.df.size());
|
|
|
qDebug() << QString(" xf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.xf.size());
|
|
|
qDebug() << QString(" infra: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.infra.size());
|
|
|
|
|
|
// 2.3 GRID.DuoJiYaLieShuiPingJingNeiBianJie
|
|
|
qDebug() << "\n -- GRID.DuoJiYaLieShuiPingJingNeiBianJie --";
|
|
|
qDebug() << QString(" n: %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.n);
|
|
|
qDebug() << QString(" XiLinh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.XiLinh.size());
|
|
|
qDebug() << QString(" lh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.lh.size());
|
|
|
qDebug() << QString(" dh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dh.size());
|
|
|
qDebug() << QString(" dsxf: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dsxf.size());
|
|
|
qDebug() << QString(" inhor: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.inhor.size());
|
|
|
|
|
|
// 2.4 GRID.WaiBianJie
|
|
|
qDebug() << "\n -- GRID.WaiBianJie --";
|
|
|
qDebug() << QString(" n: %1").arg(p0.GRID.WaiBianJie.n);
|
|
|
qDebug() << QString(" WaiBianh: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBianh.size());
|
|
|
qDebug() << QString(" WaiBianl: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBianl.size());
|
|
|
qDebug() << QString(" WaiBiand: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBiand.size());
|
|
|
|
|
|
// 2.5 GRID.NeiBuDuanCeng
|
|
|
qDebug() << "\n -- GRID.NeiBuDuanCeng --";
|
|
|
qDebug() << QString(" n: %1").arg(p0.GRID.NeiBuDuanCeng.n);
|
|
|
qDebug() << QString(" faultb1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultb1.size());
|
|
|
qDebug() << QString(" faultb2: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultb2.size());
|
|
|
qDebug() << QString(" faultl1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultl1.size());
|
|
|
qDebug() << QString(" faultd1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultd1.size());
|
|
|
|
|
|
// 2.6 GRID.YuChuLiJuZhen
|
|
|
qDebug() << "\n -- GRID.YuChuLiJuZhen --";
|
|
|
qDebug() << QString(" ia: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.ia.size());
|
|
|
qDebug() << QString(" ja: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.ja.size());
|
|
|
qDebug() << QString(" nzeros: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.nzeros.size());
|
|
|
qDebug() << QString(" numk: %1").arg(p0.GRID.YuChuLiJuZhen.numk);
|
|
|
|
|
|
|
|
|
// 3. Rate Data
|
|
|
qDebug() << "\n--- Rate Data ---";
|
|
|
qDebug() << QString("Rate.t: Contains %1 data sets").arg(p0.Rate.t.size());
|
|
|
qDebug() << QString("Rate.qo: Contains %1 data sets").arg(p0.Rate.qo.size());
|
|
|
qDebug() << QString("Rate.qg: Contains %1 data sets").arg(p0.Rate.qg.size());
|
|
|
qDebug() << QString("Rate.qw: Contains %1 data sets").arg(p0.Rate.qw.size());
|
|
|
// Optional: Print capacity of each inner vector, e.g.:
|
|
|
// for (int i = 0; i < p0.Rate.t.size(); ++i) {
|
|
|
// zxLogInstance::getInstance()->writeLogF(QString(" Rate.t[%1] Capacity: %2").arg(i).arg(p0.Rate.t[i].size()));
|
|
|
// }
|
|
|
|
|
|
|
|
|
// 4. Pressure Data
|
|
|
qDebug() << "\n--- Pressure Data ---";
|
|
|
qDebug() << QString("Pressure.t: Contains %1 data sets").arg(p0.Pressure.t.size());
|
|
|
qDebug() << QString("Pressure.p: Contains %1 data sets").arg(p0.Pressure.p.size());
|
|
|
|
|
|
|
|
|
// 5. CS Wellbore Storage and Skin Data
|
|
|
qDebug() << "\n--- CS Data ---";
|
|
|
qDebug() << QString("CS.C: Capacity %1").arg(p0.CS.C.size());
|
|
|
qDebug() << QString("CS.S: Capacity %1").arg(p0.CS.S.size());
|
|
|
|
|
|
|
|
|
// 6. PVT Fluid Property Data
|
|
|
qDebug() << "\n--- PVT Data ---";
|
|
|
qDebug() << QString("PVT.p: Capacity %1").arg(p0.PVT.p.size());
|
|
|
qDebug() << QString("PVT.Rso: Capacity %1").arg(p0.PVT.Rso.size());
|
|
|
qDebug() << QString("PVT.Bo: Capacity %1").arg(p0.PVT.Bo.size());
|
|
|
qDebug() << QString("PVT.Co: Capacity %1").arg(p0.PVT.Co.size());
|
|
|
qDebug() << QString("PVT.miuo: Capacity %1").arg(p0.PVT.miuo.size());
|
|
|
qDebug() << QString("PVT.rouo: Capacity %1").arg(p0.PVT.rouo.size());
|
|
|
qDebug() << QString("PVT.Rv: Capacity %1").arg(p0.PVT.Rv.size());
|
|
|
qDebug() << QString("PVT.Bg: Capacity %1").arg(p0.PVT.Bg.size());
|
|
|
qDebug() << QString("PVT.Cg: Capacity %1").arg(p0.PVT.Cg.size());
|
|
|
qDebug() << QString("PVT.miug: Capacity %1").arg(p0.PVT.miug.size());
|
|
|
qDebug() << QString("PVT.roug: Capacity %1").arg(p0.PVT.roug.size());
|
|
|
qDebug() << QString("PVT.Z: Capacity %1").arg(p0.PVT.Z.size());
|
|
|
qDebug() << QString("PVT.Rsw: Capacity %1").arg(p0.PVT.Rsw.size());
|
|
|
qDebug() << QString("PVT.Bw: Capacity %1").arg(p0.PVT.Bw.size());
|
|
|
qDebug() << QString("PVT.Cw: Capacity %1").arg(p0.PVT.Cw.size());
|
|
|
qDebug() << QString("PVT.miuw: Capacity %1").arg(p0.PVT.miuw.size());
|
|
|
qDebug() << QString("PVT.rouw: Capacity %1").arg(p0.PVT.rouw.size());
|
|
|
qDebug() << QString("PVT.V: Capacity %1").arg(p0.PVT.V.size());
|
|
|
qDebug() << QString("PVT.k_kinitial: Capacity %1").arg(p0.PVT.k_kinitial.size());
|
|
|
qDebug() << QString("PVT.Cf_Cfinitial: Capacity %1").arg(p0.PVT.Cf_Cfinitial.size());
|
|
|
qDebug() << QString("PVT.So: Capacity %1").arg(p0.PVT.So.size());
|
|
|
qDebug() << QString("PVT.Kro: Capacity %1").arg(p0.PVT.Kro.size());
|
|
|
qDebug() << QString("PVT.Sg: Capacity %1").arg(p0.PVT.Sg.size());
|
|
|
qDebug() << QString("PVT.Krg: Capacity %1").arg(p0.PVT.Krg.size());
|
|
|
qDebug() << QString("PVT.Sw: Capacity %1").arg(p0.PVT.Sw.size());
|
|
|
qDebug() << QString("PVT.Krw: Capacity %1").arg(p0.PVT.Krw.size());
|
|
|
|
|
|
|
|
|
// 7. Base Data
|
|
|
qDebug() << "\n--- Base Data ---";
|
|
|
qDebug() << QString("Base.Pi: %1").arg(p0.Base.Pi);
|
|
|
qDebug() << QString("Base.Cti: %1").arg(p0.Base.Cti);
|
|
|
qDebug() << QString("Base.Cf: %1").arg(p0.Base.Cf);
|
|
|
qDebug() << QString("Base.Soi: %1").arg(p0.Base.Soi);
|
|
|
qDebug() << QString("Base.Sgi: %1").arg(p0.Base.Sgi);
|
|
|
qDebug() << QString("Base.Swi: %1").arg(p0.Base.Swi);
|
|
|
qDebug() << QString("Base.k: Capacity %1").arg(p0.Base.k.size());
|
|
|
qDebug() << QString("Base.phi: Capacity %1").arg(p0.Base.phi.size());
|
|
|
qDebug() << QString("Base.h: Capacity %1").arg(p0.Base.h.size());
|
|
|
|
|
|
qDebug() << "\n--- End of HX_NWTM_MODEL_INPUT Content ---";
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::saveHX_NWTM_MODEL_INPUT_ToTxt(const HX_NWTM_MODEL_INPUT& p0, const QString& filePath)
|
|
|
{
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
|
|
// Log an error if the file cannot be opened
|
|
|
qDebug() << QString("ERROR: Could not open file for writing: %1").arg(filePath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QTextStream out(&file);
|
|
|
|
|
|
out << "--- Start Printing HX_NWTM_MODEL_INPUT Content ---\n";
|
|
|
|
|
|
// 1. Basic Parameters (T)
|
|
|
out << QString("Parameter T: %1\n").arg(p0.T);
|
|
|
|
|
|
// 2. GRID Data
|
|
|
out << "\n--- GRID Data ---\n";
|
|
|
out << QString("GRID.Trinodexy: Capacity %1\n").arg(p0.GRID.Trinodexy.size());
|
|
|
out << QString("GRID.Area: Capacity %1\n").arg(p0.GRID.Area.size());
|
|
|
out << QString("GRID.D: Capacity %1\n").arg(p0.GRID.D.size());
|
|
|
|
|
|
// 2.1 GRID.ZhiJingNeiBianJie
|
|
|
out << "\n -- GRID.ZhiJingNeiBianJie --\n";
|
|
|
out << QString(" n: %1\n").arg(p0.GRID.ZhiJingNeiBianJie.n);
|
|
|
out << QString(" XiLinw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.XiLinw.size());
|
|
|
out << QString(" lw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.lw.size());
|
|
|
out << QString(" dw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.dw.size());
|
|
|
out << QString(" rw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.rw.size());
|
|
|
out << QString(" inwell: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.inwell.size());
|
|
|
|
|
|
// 2.2 GRID.LieFengJingNeiBianJie
|
|
|
out << "\n -- GRID.LieFengJingNeiBianJie --\n";
|
|
|
out << QString(" n: %1\n").arg(p0.GRID.LieFengJingNeiBianJie.n);
|
|
|
out << QString(" XiLinf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.XiLinf.size());
|
|
|
out << QString(" lf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.lf.size());
|
|
|
out << QString(" df: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.df.size());
|
|
|
out << QString(" xf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.xf.size());
|
|
|
out << QString(" infra: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.infra.size());
|
|
|
|
|
|
// 2.3 GRID.DuoJiYaLieShuiPingJingNeiBianJie
|
|
|
out << "\n -- GRID.DuoJiYaLieShuiPingJingNeiBianJie --\n";
|
|
|
out << QString(" n: %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.n);
|
|
|
out << QString(" XiLinh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.XiLinh.size());
|
|
|
out << QString(" lh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.lh.size());
|
|
|
out << QString(" dh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dh.size());
|
|
|
out << QString(" dsxf: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dsxf.size());
|
|
|
out << QString(" inhor: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.inhor.size());
|
|
|
|
|
|
// 2.4 GRID.WaiBianJie
|
|
|
out << "\n -- GRID.WaiBianJie --\n";
|
|
|
out << QString(" n: %1\n").arg(p0.GRID.WaiBianJie.n);
|
|
|
out << QString(" WaiBianh: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBianh.size());
|
|
|
out << QString(" WaiBianl: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBianl.size());
|
|
|
out << QString(" WaiBiand: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBiand.size());
|
|
|
|
|
|
// 2.5 GRID.NeiBuDuanCeng
|
|
|
out << "\n -- GRID.NeiBuDuanCeng --\n";
|
|
|
out << QString(" n: %1\n").arg(p0.GRID.NeiBuDuanCeng.n);
|
|
|
out << QString(" faultb1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultb1.size());
|
|
|
out << QString(" faultb2: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultb2.size());
|
|
|
out << QString(" faultl1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultl1.size());
|
|
|
out << QString(" faultd1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultd1.size());
|
|
|
|
|
|
// 2.6 GRID.YuChuLiJuZhen
|
|
|
out << "\n -- GRID.YuChuLiJuZhen --\n";
|
|
|
out << QString(" ia: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.ia.size());
|
|
|
out << QString(" ja: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.ja.size());
|
|
|
out << QString(" nzeros: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.nzeros.size());
|
|
|
out << QString(" numk: %1\n").arg(p0.GRID.YuChuLiJuZhen.numk);
|
|
|
|
|
|
|
|
|
// 3. Rate Data
|
|
|
out << "\n--- Rate Data ---\n";
|
|
|
out << QString("Rate.t: Contains %1 data sets\n").arg(p0.Rate.t.size());
|
|
|
out << QString("Rate.qo: Contains %1 data sets\n").arg(p0.Rate.qo.size());
|
|
|
out << QString("Rate.qg: Contains %1 data sets\n").arg(p0.Rate.qg.size());
|
|
|
out << QString("Rate.qw: Contains %1 data sets\n").arg(p0.Rate.qw.size());
|
|
|
// Optional: Print capacity of each inner vector, e.g.:
|
|
|
// for (int i = 0; i < p0.Rate.t.size(); ++i) {
|
|
|
// out << QString(" Rate.t[%1] Capacity: %2\n").arg(i).arg(p0.Rate.t[i].size());
|
|
|
// }
|
|
|
|
|
|
|
|
|
// 4. Pressure Data
|
|
|
out << "\n--- Pressure Data ---\n";
|
|
|
out << QString("Pressure.t: Contains %1 data sets\n").arg(p0.Pressure.t.size());
|
|
|
out << QString("Pressure.p: Contains %1 data sets\n").arg(p0.Pressure.p.size());
|
|
|
|
|
|
|
|
|
// 5. CS Wellbore Storage and Skin Data
|
|
|
out << "\n--- CS Data ---\n";
|
|
|
out << QString("CS.C: Capacity %1\n").arg(p0.CS.C.size());
|
|
|
out << QString("CS.S: Capacity %1\n").arg(p0.CS.S.size());
|
|
|
|
|
|
|
|
|
// 6. PVT Fluid Property Data
|
|
|
out << "\n--- PVT Data ---\n";
|
|
|
out << QString("PVT.p: Capacity %1\n").arg(p0.PVT.p.size());
|
|
|
out << QString("PVT.Rso: Capacity %1\n").arg(p0.PVT.Rso.size());
|
|
|
out << QString("PVT.Bo: Capacity %1\n").arg(p0.PVT.Bo.size());
|
|
|
out << QString("PVT.Co: Capacity %1\n").arg(p0.PVT.Co.size());
|
|
|
out << QString("PVT.miuo: Capacity %1\n").arg(p0.PVT.miuo.size());
|
|
|
out << QString("PVT.rouo: Capacity %1\n").arg(p0.PVT.rouo.size());
|
|
|
out << QString("PVT.Rv: Capacity %1\n").arg(p0.PVT.Rv.size());
|
|
|
out << QString("PVT.Bg: Capacity %1\n").arg(p0.PVT.Bg.size());
|
|
|
out << QString("PVT.Cg: Capacity %1\n").arg(p0.PVT.Cg.size());
|
|
|
out << QString("PVT.miug: Capacity %1\n").arg(p0.PVT.miug.size());
|
|
|
out << QString("PVT.roug: Capacity %1\n").arg(p0.PVT.roug.size());
|
|
|
out << QString("PVT.Z: Capacity %1\n").arg(p0.PVT.Z.size());
|
|
|
out << QString("PVT.Rsw: Capacity %1\n").arg(p0.PVT.Rsw.size());
|
|
|
out << QString("PVT.Bw: Capacity %1\n").arg(p0.PVT.Bw.size());
|
|
|
out << QString("PVT.Cw: Capacity %1\n").arg(p0.PVT.Cw.size());
|
|
|
out << QString("PVT.miuw: Capacity %1\n").arg(p0.PVT.miuw.size());
|
|
|
out << QString("PVT.rouw: Capacity %1\n").arg(p0.PVT.rouw.size());
|
|
|
out << QString("PVT.V: Capacity %1\n").arg(p0.PVT.V.size());
|
|
|
out << QString("PVT.k_kinitial: Capacity %1\n").arg(p0.PVT.k_kinitial.size());
|
|
|
out << QString("PVT.Cf_Cfinitial: Capacity %1\n").arg(p0.PVT.Cf_Cfinitial.size());
|
|
|
out << QString("PVT.So: Capacity %1\n").arg(p0.PVT.So.size());
|
|
|
out << QString("PVT.Kro: Capacity %1\n").arg(p0.PVT.Kro.size());
|
|
|
out << QString("PVT.Sg: Capacity %1\n").arg(p0.PVT.Sg.size());
|
|
|
out << QString("PVT.Krg: Capacity %1\n").arg(p0.PVT.Krg.size());
|
|
|
out << QString("PVT.Sw: Capacity %1\n").arg(p0.PVT.Sw.size());
|
|
|
out << QString("PVT.Krw: Capacity %1\n").arg(p0.PVT.Krw.size());
|
|
|
|
|
|
|
|
|
// 7. Base Data
|
|
|
out << "\n--- Base Data ---\n";
|
|
|
out << QString("Base.Pi: %1\n").arg(p0.Base.Pi);
|
|
|
out << QString("Base.Cti: %1\n").arg(p0.Base.Cti);
|
|
|
out << QString("Base.Cf: %1\n").arg(p0.Base.Cf);
|
|
|
out << QString("Base.Soi: %1\n").arg(p0.Base.Soi);
|
|
|
out << QString("Base.Sgi: %1\n").arg(p0.Base.Sgi);
|
|
|
out << QString("Base.Swi: %1\n").arg(p0.Base.Swi);
|
|
|
out << QString("Base.k: Capacity %1\n").arg(p0.Base.k.size());
|
|
|
out << QString("Base.phi: Capacity %1\n").arg(p0.Base.phi.size());
|
|
|
out << QString("Base.h: Capacity %1\n").arg(p0.Base.h.size());
|
|
|
|
|
|
out << QString("Base.d: Capacity %1\n").arg(p0.Base.d);
|
|
|
out << QString("Base.dt_Max: Capacity %1\n").arg(p0.Base.dt_Max);
|
|
|
out << QString("Base.dt_Min: Capacity %1\n").arg(p0.Base.dt_Min);
|
|
|
|
|
|
out << "\n--- End of HX_NWTM_MODEL_INPUT Content ---\n";
|
|
|
|
|
|
file.close();
|
|
|
qDebug() << QString("Successfully saved HX_NWTM_MODEL_INPUT content to: %1").arg(filePath);
|
|
|
return true;
|
|
|
}
|