#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @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 m_vecPropertyDataSets; ///< 属性插值值副本。 vtkSmartPointer m_pBaseGrid; ///< 完整成果使用的基础 VTK 网格。 vtkSmartPointer 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 m_vecOrderedWells; QSet m_setEffectiveWellCodes; QHash m_mapWellModes; int m_nNextWellIndex; }; namespace { bool isTaskCancellationRequested(const QAtomicInt* pCancelRequested) { return pCancelRequested != NULL && static_cast(*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(pWellData); nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast(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(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& 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 targetPoints; targetPoints.reserve(static_cast(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(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 measurementPoints; QVector 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 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(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(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 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(oWellRef.m_eMode)); } if(pState->m_setEffectiveWellCodes.isEmpty()) { return false; } QSet setOrderedWellCodes; const QVector 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 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 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(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& 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 mapWellsByCode; const QVector 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::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 > nmCalculationDllPebiSolverTask::getAutoFitResultPressure() const { return m_autoFitResultPressure; } QVector > nmCalculationDllPebiSolverTask::getAutoFitResultLogLog() const { return m_autoFitResultLogLog; } QVector > 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::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( GetProcAddress(hModelModule, "HX_NWTM_MODEL")); SetIntValueFunc pfnSetSolverType = reinterpret_cast( GetProcAddress(hModelModule, "set_solvetype")); SetIntValueFunc pfnSetOmpThreads = reinterpret_cast( GetProcAddress(hModelModule, "set_omp_threads")); SetIntValueFunc pfnSetIluReuseSteps = reinterpret_cast( GetProcAddress(hModelModule, "set_ilu_reuse_steps")); GetIntValueFunc pfnGetSolveTime = reinterpret_cast( 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 HX_logderivative(const std::vector& x, const std::vector& y, const int& n) { // 功能: 对数导数函数 // 作者: 何辉 // 日期: 2024.07.16 // 单位: 西安华线石油科技有限公司(西安石油大学) std::vector 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> vvecPressure; QVector> vvecLogLog; QVector> vvecSemiLog; // p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure. // 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力. const bool usePseudoPressure = (modelType == static_cast(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& vecWellsOrder = m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder; const QVector& 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(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(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(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 currentWellTime; QVector 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 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& vecTimeQ = oWellInput.m_vecFlowPoints; // 防御空流量数据,避免 -1 转成 std::vector 的巨大无符号长度。 int nTimeNumQ = qMax(0, vecTimeQ.size() - 1); // 移除第一个 0 点 std::vector timeQ(nTimeNumQ); std::vector q(nTimeNumQ); for(int i = 0; i < nTimeNumQ; ++i) { timeQ[i] = vecTimeQ[i + 1].x(); q[i] = vecTimeQ[i + 1].y(); } // 调用外部 DLL 计算双对数曲线 std::vector 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()); vvecLogLog.append(QVector()); vvecLogLog.append(QVector()); vvecSemiLog.clear(); vvecSemiLog.append(QVector()); vvecSemiLog.append(QVector()); } else if(hMod_solver) { typedef bool (*PreLog)(const std::vector&, const int&, double*, double*, int, std::vector&); 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 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 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(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(nTimeIndex) << static_cast(nCellIndex); return false; } } } // 第五步:在局部时间步映射中构造场压力,同时计算全局标量范围。 // 此处不触碰 DataManager,较短的新时间轴会在主线程整体替换时自然清除旧帧。 m_mapPendingTimeSteps.clear(); double dMinP = DBL_MAX; double dMaxP = -DBL_MAX; // 为每个时间步生成完整的 VTK 压力数组。 reportStage(tr("Processing field results..."), 0, static_cast(p1.p.size())); for(size_t timeIdx = 0; timeIdx < p1.p.size(); ++timeIdx) { if(isCancelRequested()) { return false; } // 获取当前时间步的时间值 double currentTime = p1.t[timeIdx]; // 创建 vtkDoubleArray 来存储当前时间步的压力数据 vtkSmartPointer pressureData = vtkSmartPointer::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(timeIdx + 1); const int nTotal = static_cast(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(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 pResultGrid = m_pPendingResultGrid; if(!oBuilder.takeResultGrid(pResultGrid)) { return false; } QMap >::const_iterator oTimeIt = m_mapPendingTimeSteps.constBegin(); for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) { vtkSmartPointer pPressure = oTimeIt.value(); if(!oBuilder.addPressureFrame(oTimeIt.key(), pPressure)) { return false; } } if(!oBuilder.setScalarRange(m_dPendingScalarMin, m_dPendingScalarMax)) { return false; } QVector& vecSolverOrder = m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder; QVector& 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 pLegacyResultGrid = vtkSmartPointer::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 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> vvecPressure; // QVector> vvecLogLog; // QVector> vvecSemiLog; // // // 获取参与求解的井的顺序 // QVector> 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 currentWellTime; // QVector 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 t_vec; // 时间 t (x) // std::vector 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 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 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; }