|
|
#include "nmCalculationDllPebiSolverTask.h"
|
|
|
#include "pch.h"
|
|
|
#include "zxLogInstance.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmDataSensitive.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 "nmDataUtils.h"
|
|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
#include "nmPebiCellMapping.h"
|
|
|
#include "nmPebiStaticPropertyCalculator.h"
|
|
|
|
|
|
#include "nmCalculationPebiGrid.h"
|
|
|
#include "nmCalculationUtils.h"
|
|
|
#include "nmCalculationAutoFitLM.h"
|
|
|
#include "nmDataAutomaticFitting.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmDataPvtParaForPebi.h"
|
|
|
|
|
|
#include <QDebug>
|
|
|
#include <QCoreApplication>
|
|
|
#include <limits>
|
|
|
#include <QHash>
|
|
|
#include <QMutexLocker>
|
|
|
#include <QScopedPointer>
|
|
|
#include <QElapsedTimer>
|
|
|
#include <QSet>
|
|
|
#include <QUuid>
|
|
|
#include <iostream>
|
|
|
#include <fstream>
|
|
|
#include <sstream>
|
|
|
#include <map>
|
|
|
#include <algorithm>
|
|
|
#include <new>
|
|
|
#include <stdexcept>
|
|
|
#include <cmath>
|
|
|
#include <float.h>
|
|
|
#include <limits.h>
|
|
|
#include <string>
|
|
|
|
|
|
#include <vtkSmartPointer.h>
|
|
|
#include <vtkDoubleArray.h>
|
|
|
#include <vtkCellData.h>
|
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
|
|
/**
|
|
|
* @brief PEBI 求解任务独占的完整值输入,敏感性采样和拟合只修改此副本。
|
|
|
*/
|
|
|
struct nmPebiSolverInputSnapshot
|
|
|
{
|
|
|
// 只冻结会被采样覆盖的标量,不另存一份大型网格或完整模型。
|
|
|
struct SensitivityBaseline
|
|
|
{
|
|
|
SensitivityBaseline()
|
|
|
: m_nTarget(-1), m_dPressure(0.0),
|
|
|
m_dScenePermeability(0.0), m_dSceneThickness(0.0),
|
|
|
m_dScenePorosity(0.0), m_dReferencePermeability(0.0),
|
|
|
m_dReferenceThickness(0.0), m_dReferencePorosity(0.0)
|
|
|
{
|
|
|
}
|
|
|
int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。
|
|
|
QVector<double> m_dSceneSkin;
|
|
|
QVector<double> m_dSceneStorage;
|
|
|
QVector<double> m_dWellSkin;
|
|
|
QVector<double> m_dWellStorage;
|
|
|
double m_dPressure;
|
|
|
double m_dScenePermeability;
|
|
|
double m_dSceneThickness;
|
|
|
double m_dScenePorosity;
|
|
|
double m_dReferencePermeability;
|
|
|
double m_dReferenceThickness;
|
|
|
double m_dReferencePorosity;
|
|
|
};
|
|
|
|
|
|
nmPebiSolverInputSnapshot()
|
|
|
: m_nSolverType(nmDataAnalyzeManager::PebiSolverOriginal),
|
|
|
m_nOmpThreads(1),
|
|
|
m_nIluReuseSteps(1),
|
|
|
m_bRequiresGridCalculation(false),
|
|
|
m_bGridResultNeedsCommit(false),
|
|
|
m_bMayUseCachedGrid(false),
|
|
|
m_bAutoFitTargetOnly(false),
|
|
|
m_bRateForecast(false),
|
|
|
m_bResultMetadataCaptured(false)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。
|
|
|
nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。
|
|
|
/** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */
|
|
|
nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput;
|
|
|
SensitivityBaseline m_oSensitivityBaseline; ///< 首次复制时冻结,后续更新只读。
|
|
|
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_bRateForecast; ///< 产量预测只返回原始压力,不计算双对数和半对数。
|
|
|
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
|
|
|
}
|
|
|
|
|
|
double calculateSensitivityPressureRmse(const QVector<QVector<double> >& measured,
|
|
|
const QVector<QVector<double> >& simulated,
|
|
|
const QAtomicInt* cancelRequested)
|
|
|
{
|
|
|
if(measured.size() < 2 || simulated.size() < 2 || simulated[0].isEmpty() ||
|
|
|
measured[0].size() != measured[1].size() ||
|
|
|
simulated[0].size() != simulated[1].size()) return -1.0;
|
|
|
const QVector<double>& times = simulated[0];
|
|
|
const QVector<double>& pressures = simulated[1];
|
|
|
for(int i = 0; i < times.size(); ++i) {
|
|
|
if((i % 256) == 0 && isTaskCancellationRequested(cancelRequested)) return -1.0;
|
|
|
if(!isFiniteSolverNumber(times[i]) || !isFiniteSolverNumber(pressures[i]) ||
|
|
|
(i > 0 && times[i] <= times[i - 1])) return -1.0;
|
|
|
}
|
|
|
|
|
|
// 在实测时间上线性插值原始压力,只统计覆盖范围内的有效点,不外推。
|
|
|
double squaredError = 0.0;
|
|
|
int count = 0;
|
|
|
for(int i = 0; i < measured[0].size(); ++i) {
|
|
|
if((i % 256) == 0 && isTaskCancellationRequested(cancelRequested)) return -1.0;
|
|
|
const double time = measured[0][i];
|
|
|
const double pressure = measured[1][i];
|
|
|
if(!isFiniteSolverNumber(time) || !isFiniteSolverNumber(pressure) ||
|
|
|
time < times.first() || time > times.last()) continue;
|
|
|
const int right = static_cast<int>(
|
|
|
std::lower_bound(times.constBegin(), times.constEnd(), time) - times.constBegin());
|
|
|
double predicted = pressures[right];
|
|
|
if(times[right] != time) {
|
|
|
const double fraction = (time - times[right - 1]) / (times[right] - times[right - 1]);
|
|
|
predicted = pressures[right - 1] + fraction * (pressures[right] - pressures[right - 1]);
|
|
|
}
|
|
|
const double residual = predicted - pressure;
|
|
|
squaredError += residual * residual;
|
|
|
++count;
|
|
|
}
|
|
|
// 普通 Monte-Carlo 的各点权重均为 1;无有效点不能当作完全拟合。
|
|
|
return count > 0 && isFiniteSolverNumber(squaredError)
|
|
|
? std::sqrt(squaredError / count) : -1.0;
|
|
|
}
|
|
|
|
|
|
bool inputPseudoPressure(const nmDataBinaryTools::NM_PEBI_SCENE& scene,
|
|
|
double pressure, double& result)
|
|
|
{
|
|
|
// 使用本次任务捕获的拟压力表,后台插值不访问全局转换器。
|
|
|
const std::vector<double>& p = scene.PVT.pseudoPressureP;
|
|
|
const std::vector<double>& ps = scene.PVT.pseudoPressurePs;
|
|
|
if(p.size() < 2 || p.size() != ps.size() || !isFiniteSolverNumber(pressure)) return false;
|
|
|
const bool ascending = p[1] > p[0];
|
|
|
size_t first = 0, last = p.size();
|
|
|
while(first < last) {
|
|
|
const size_t mid = first + (last - first) / 2;
|
|
|
if(ascending ? p[mid] < pressure : p[mid] > pressure) first = mid + 1;
|
|
|
else last = mid;
|
|
|
}
|
|
|
const size_t right = (std::max)(size_t(1), (std::min)(first, p.size() - 1));
|
|
|
const double span = p[right] - p[right - 1];
|
|
|
if(span == 0.0) return false;
|
|
|
// 与现有离线计算一致,区间内线性插值,超出范围沿端点外推。
|
|
|
result = ps[right - 1] + (pressure - p[right - 1]) / span * (ps[right] - ps[right - 1]);
|
|
|
return isFiniteSolverNumber(result);
|
|
|
}
|
|
|
|
|
|
// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时取输入捕获阶段
|
|
|
// 冻结的制度参考相。PEBI 主求解仍在场景中接收油、气、水三相全部流量。
|
|
|
const QVector<double>* referenceRatesOf(
|
|
|
const nmPebiWellInputSnapshot& oWellInput)
|
|
|
{
|
|
|
switch(oWellInput.m_eFlowSchedulePhase) {
|
|
|
case PHASE_Oil:
|
|
|
return &oWellInput.m_vecOilRates;
|
|
|
case PHASE_Gas:
|
|
|
return &oWellInput.m_vecGasRates;
|
|
|
case PHASE_Water:
|
|
|
return &oWellInput.m_vecWaterRates;
|
|
|
default:
|
|
|
return nullptr;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
nmDataWellBase* pWellData,
|
|
|
bool bDisplayResultWell,
|
|
|
nmPebiWellInputSnapshot& oWellInput)
|
|
|
{
|
|
|
if(pWellData == NULL || !oWellInput.m_bRealWell ||
|
|
|
pWellData->getWellCode() != oWellInput.m_sWellCode ||
|
|
|
pWellData->getWellCategory() != oWellInput.m_eWellCategory ||
|
|
|
!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
|
|
|
pWellData->getWellInstanceId().isEmpty() ||
|
|
|
QUuid(pWellData->getWellInstanceId()).isNull()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 正式求解前统一重算历史双对数和半对数曲线,确保油井、水井和气井的
|
|
|
// 曲线签名都与本次输入一致。
|
|
|
if(!pDataManager->updateWellHistoryData(pWellData)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sCurrentGaugeInputSha1 =
|
|
|
pWellData->calculateGaugeInputSha1();
|
|
|
if(sCurrentGaugeInputSha1.isEmpty() ||
|
|
|
sCurrentGaugeInputSha1 != oWellInput.m_sGaugeInputSha1 ||
|
|
|
sCurrentGaugeInputSha1 != pWellData->getHistoryGaugeInputSha1()) {
|
|
|
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 &&
|
|
|
oWellInput.m_bRateControlled &&
|
|
|
!oWellInput.m_vecFlowDurations.isEmpty();
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 从同一个储层对象冻结静态属性输入及可选的正式结果储层参数。
|
|
|
*
|
|
|
* 自动拟合也必须捕获静态属性输入,但不需要正式结果元数据。只有全部字段都复制
|
|
|
* 成功后才设置 m_bCaptured,防止默认构造的零值被误认为有效业务输入。
|
|
|
*/
|
|
|
bool captureStaticPropertyInput(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
nmPebiSolverInputSnapshot& oSnapshot,
|
|
|
bool bCaptureResultReservoir,
|
|
|
nmDataReservoir** ppReservoir)
|
|
|
{
|
|
|
if(ppReservoir != NULL) {
|
|
|
*ppReservoir = NULL;
|
|
|
}
|
|
|
if(pDataManager == NULL ||
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmDataReservoir* pReservoir = pDataManager->getReservoirData();
|
|
|
if(pReservoir == NULL) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmPebiStaticPropertyCalculationInput oStaticInput;
|
|
|
// 数据层保持显示和持久化基准单位;渗透率只在组装求解器 Base.k 时转成 D。
|
|
|
oStaticInput.m_dReferencePermeabilityMilliDarcy =
|
|
|
pReservoir->getPermeability().getValue().toDouble();
|
|
|
oStaticInput.m_dReferencePorosity =
|
|
|
pReservoir->getPorosity().getValue().toDouble();
|
|
|
oStaticInput.m_dReferenceThicknessMeters =
|
|
|
pReservoir->getThickness().getValue().toDouble();
|
|
|
oStaticInput.m_vecDataSets =
|
|
|
pDataManager->getPropertyInterpolationDataSets();
|
|
|
// 授权路径随网格输入最先冻结;Kriging、建网和模型求解必须复用同一份值,
|
|
|
// 不能在分阶段捕获期间从 DataManager 再读出另一个时刻的路径。
|
|
|
oStaticInput.m_sLicensePath =
|
|
|
oSnapshot.m_oGridInput.m_sLicensePath;
|
|
|
oStaticInput.m_bCaptured = true;
|
|
|
|
|
|
if(bCaptureResultReservoir) {
|
|
|
nmPebiResultReservoirParameters oReservoir;
|
|
|
oReservoir.m_dInitialPressure = pReservoir->getInitialPressure()
|
|
|
.getValue().toDouble();
|
|
|
oReservoir.m_dPermeability =
|
|
|
oStaticInput.m_dReferencePermeabilityMilliDarcy;
|
|
|
oReservoir.m_dThickness =
|
|
|
oStaticInput.m_dReferenceThicknessMeters;
|
|
|
oReservoir.m_dPorosity = oStaticInput.m_dReferencePorosity;
|
|
|
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();
|
|
|
oSnapshot.m_oResultReservoirParameters = oReservoir;
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_oStaticPropertyInput = oStaticInput;
|
|
|
if(ppReservoir != NULL) {
|
|
|
*ppReservoir = pReservoir;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/** @brief 捕获完整结果专用的 PVT 和求解设置,不重复查询储层对象。 */
|
|
|
bool captureResultParameters(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
nmDataReservoir* pReservoir,
|
|
|
nmPebiSolverInputSnapshot& oSnapshot)
|
|
|
{
|
|
|
if(pDataManager == NULL || pReservoir == NULL ||
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 校验 DLL 三个原始单元数组共享的结构索引域并提取属性计算位置。
|
|
|
*
|
|
|
* 数量一致只能证明三个数组可按共同下标访问。下标 i 是否表示同一个求解单元
|
|
|
* 由 DLL 接口契约保证,应用层不重新排序,也不把 Trinodexy[i] 宣称为几何质心。
|
|
|
*/
|
|
|
bool extractStaticPropertyLocations(
|
|
|
const HX_NWTM_GRID_OUTPUT1& oGridOutput1,
|
|
|
const HX_NWTM_GRID_OUTPUT2& oGridOutput2,
|
|
|
QVector<QPointF>& vecLocations,
|
|
|
QString& sErrorMessage,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
vecLocations.clear();
|
|
|
sErrorMessage.clear();
|
|
|
|
|
|
const size_t nLocationCount = oGridOutput2.Trinodexy.size();
|
|
|
const size_t nIsPlotCount = oGridOutput1.PEBI_cell.isplot.size();
|
|
|
const size_t nPolygonIndexCount = oGridOutput1.PEBI_cell.pindex.size();
|
|
|
if(nLocationCount == 0 ||
|
|
|
nLocationCount != nIsPlotCount ||
|
|
|
nLocationCount != nPolygonIndexCount ||
|
|
|
nLocationCount > static_cast<size_t>(INT_MAX)) {
|
|
|
sErrorMessage = QLatin1String(
|
|
|
"PEBI property index arrays are empty or inconsistent.");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
vecLocations.reserve(static_cast<int>(nLocationCount));
|
|
|
for(size_t nIndex = 0; nIndex < nLocationCount; ++nIndex) {
|
|
|
if((nIndex % 256) == 0 &&
|
|
|
isTaskCancellationRequested(pCancelRequested)) {
|
|
|
vecLocations.clear();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Trinodexy 提供属性计算位置;本阶段只读取 X/Y,不涉及 TRI_cell。
|
|
|
const dVec1& vecPosition = oGridOutput2.Trinodexy[nIndex];
|
|
|
if(vecPosition.size() < 2 ||
|
|
|
!isFiniteSolverNumber(vecPosition[0]) ||
|
|
|
!isFiniteSolverNumber(vecPosition[1])) {
|
|
|
sErrorMessage = QString::fromLatin1(
|
|
|
"PEBI property location %1 is invalid.")
|
|
|
.arg(static_cast<qulonglong>(nIndex));
|
|
|
vecLocations.clear();
|
|
|
return false;
|
|
|
}
|
|
|
vecLocations.append(QPointF(vecPosition[0], vecPosition[1]));
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 用已初始化的模型输入和捕获的场景值组装一次完整模型输入。
|
|
|
*/
|
|
|
bool buildModelInputFromSnapshot(
|
|
|
const nmPebiSolverInputSnapshot& oSnapshot,
|
|
|
const QVector<QPointF>& vecPropertyLocations,
|
|
|
HX_NWTM_MODEL_INPUT& oModelInput,
|
|
|
nmPebiStaticPropertyValues& oStaticProperties,
|
|
|
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;
|
|
|
|
|
|
// 第四步:Calculator 只生成原始单元顺序的业务值,不接触 DLL 模型结构。
|
|
|
const nmPebiStaticPropertyCalculationStatus eStatus =
|
|
|
nmPebiStaticPropertyCalculator::calculate(
|
|
|
vecPropertyLocations,
|
|
|
oSnapshot.m_oStaticPropertyInput,
|
|
|
oStaticProperties,
|
|
|
&sErrorMessage,
|
|
|
pCancelRequested);
|
|
|
if(eStatus != NM_PebiStaticPropertyCalculation_Succeeded) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const int nCellCount = vecPropertyLocations.size();
|
|
|
dVec1 vecPermeability(static_cast<size_t>(nCellCount));
|
|
|
dVec1 vecPorosity(static_cast<size_t>(nCellCount));
|
|
|
dVec1 vecThickness(static_cast<size_t>(nCellCount));
|
|
|
for(int nIndex = 0; nIndex < nCellCount; ++nIndex) {
|
|
|
if((nIndex % 256) == 0 &&
|
|
|
isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
// 静态属性层统一保存 mD;求解器 Base.k 的唯一写入边界转换为 D。
|
|
|
vecPermeability[static_cast<size_t>(nIndex)] =
|
|
|
nmCalculationUtils::milliDarcyToDarcy(
|
|
|
oStaticProperties.m_vecPermeabilityMilliDarcy[nIndex]);
|
|
|
vecPorosity[static_cast<size_t>(nIndex)] =
|
|
|
oStaticProperties.m_vecPorosity[nIndex];
|
|
|
vecThickness[static_cast<size_t>(nIndex)] =
|
|
|
oStaticProperties.m_vecThicknessMeters[nIndex];
|
|
|
}
|
|
|
if(isTaskCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 三个临时数组全部完成后再一起交换,失败和取消不会留下半套求解输入。
|
|
|
oModelInput.Base.k.swap(vecPermeability);
|
|
|
oModelInput.Base.phi.swap(vecPorosity);
|
|
|
oModelInput.Base.h.swap(vecThickness);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
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_dPendingScalarMin(0.0),
|
|
|
m_dPendingScalarMax(0.0),
|
|
|
m_bPendingFullResultReady(false),
|
|
|
m_nCancelRequested(0),
|
|
|
m_nWasCancelled(0),
|
|
|
m_bSensitivityBatchCompleted(false)
|
|
|
{
|
|
|
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.";
|
|
|
fail(tr("Insufficient memory to prepare solver input."));
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
const nmCalculationDllPebiSolverTask& source, QObject* parent)
|
|
|
: QThread(parent), m_pDataManager(nullptr),
|
|
|
m_pInputSnapshot(new nmPebiSolverInputSnapshot(*source.m_pInputSnapshot)),
|
|
|
m_pManualCaptureState(nullptr), m_bManagerUseActive(false),
|
|
|
m_bInputSnapshotValid(source.m_bInputSnapshotValid),
|
|
|
m_nGridInputRevision(source.m_nGridInputRevision),
|
|
|
m_nResultInputRevision(source.m_nResultInputRevision),
|
|
|
m_lastRunSucceeded(false), m_nSolveTimeMs(-1),
|
|
|
m_sAutoFitTargetWellCode(source.m_sAutoFitTargetWellCode),
|
|
|
m_dPendingScalarMin(0.0), m_dPendingScalarMax(0.0),
|
|
|
m_bPendingFullResultReady(false), m_nCancelRequested(0), m_nWasCancelled(0),
|
|
|
m_bSensitivityBatchCompleted(false)
|
|
|
{
|
|
|
// 采样任务只持有自己的值快照;源任务和分析窗口可独立释放。
|
|
|
}
|
|
|
|
|
|
nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityRun(
|
|
|
const nmDataSensitive& sampling, quint64 runIndex, QString* errorMessage) const
|
|
|
{
|
|
|
// 从未运行的目标井快照复制输入,禁止采样过程回写原始模型。
|
|
|
if(errorMessage) errorMessage->clear();
|
|
|
if(isRunning() || !m_bInputSnapshotValid || !m_pInputSnapshot ||
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0) {
|
|
|
if(errorMessage) *errorMessage = m_sFailureReason.isEmpty()
|
|
|
? tr("Solver input is unavailable.") : m_sFailureReason;
|
|
|
return nullptr;
|
|
|
}
|
|
|
if(!m_pInputSnapshot->m_oGridResult.m_bSucceeded &&
|
|
|
!m_pInputSnapshot->m_bRequiresGridCalculation) {
|
|
|
if(errorMessage) *errorMessage = tr("Generate a valid base model before running sensitivity.");
|
|
|
return nullptr;
|
|
|
}
|
|
|
QScopedPointer<nmCalculationDllPebiSolverTask> task(
|
|
|
new nmCalculationDllPebiSolverTask(*this, nullptr));
|
|
|
nmPebiGridInputSnapshot& grid = task->m_pInputSnapshot->m_oGridInput;
|
|
|
nmPebiStaticPropertyCalculationInput& properties = task->m_pInputSnapshot->m_oStaticPropertyInput;
|
|
|
int target = -1;
|
|
|
for(int i = 0; i < grid.m_vecSolverWellOrder.size() && i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
if(grid.m_vecSolverWellOrder[i].m_sWellCode == m_sAutoFitTargetWellCode &&
|
|
|
grid.m_vecWellInputs[i].m_bRealWell) { target = i; break; }
|
|
|
}
|
|
|
if(target < 0 || target >= int(grid.m_oScene.CS.C.size()) ||
|
|
|
target >= int(grid.m_oScene.CS.S.size())) {
|
|
|
if(errorMessage) *errorMessage = tr("Solver input is unavailable.");
|
|
|
return nullptr;
|
|
|
}
|
|
|
// 首次创建时保存真实输入基准,后续切换采样参数也不会继承上一组的值。
|
|
|
nmPebiSolverInputSnapshot::SensitivityBaseline& baseline =
|
|
|
task->m_pInputSnapshot->m_oSensitivityBaseline;
|
|
|
baseline.m_nTarget = target;
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
baseline.m_dSceneSkin.append(grid.m_oScene.CS.S[i]);
|
|
|
baseline.m_dSceneStorage.append(grid.m_oScene.CS.C[i]);
|
|
|
baseline.m_dWellSkin.append(grid.m_vecWellInputs[i].m_dSkin);
|
|
|
baseline.m_dWellStorage.append(grid.m_vecWellInputs[i].m_dWellboreStorage);
|
|
|
}
|
|
|
baseline.m_dPressure = grid.m_oScene.Base.Pi;
|
|
|
baseline.m_dScenePermeability = grid.m_oScene.Base.k_ref;
|
|
|
baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref;
|
|
|
baseline.m_dScenePorosity = grid.m_oScene.Base.phi_ref;
|
|
|
baseline.m_dReferencePermeability = properties.m_dReferencePermeabilityMilliDarcy;
|
|
|
baseline.m_dReferenceThickness = properties.m_dReferenceThicknessMeters;
|
|
|
baseline.m_dReferencePorosity = properties.m_dReferencePorosity;
|
|
|
if(!task->updateSensitivityRun(sampling, runIndex, errorMessage)) return nullptr;
|
|
|
return task.take();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::updateSensitivityRun(
|
|
|
const nmDataSensitive& sampling, quint64 runIndex, QString* errorMessage)
|
|
|
{
|
|
|
if(errorMessage) errorMessage->clear();
|
|
|
// 更新只在两组之间进行;运行中的批量任务仅允许自己的工作线程更新。
|
|
|
if((isRunning() && (m_pSensitivitySampling.isNull() || QThread::currentThread() != this)) ||
|
|
|
m_pDataManager != nullptr || !m_pInputSnapshot ||
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget < 0) {
|
|
|
if(errorMessage) *errorMessage = tr("Sensitivity task is unavailable or still running.");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 即使本组配置无效,也不能将上次的成功状态或曲线当成本组结果。
|
|
|
m_bInputSnapshotValid = false;
|
|
|
discardPendingFullResult();
|
|
|
m_lastRunSucceeded = false;
|
|
|
m_sFailureReason.clear();
|
|
|
m_nSolveTimeMs = -1;
|
|
|
// 批量任务的停止标记持续到整次结束,不能在更新下一组时丢失界面的请求。
|
|
|
if(m_pSensitivitySampling.isNull()) {
|
|
|
m_nCancelRequested.fetchAndStoreOrdered(0);
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(0);
|
|
|
}
|
|
|
QVector<double> values;
|
|
|
if(!sampling.usesBaseUnits() || !sampling.getRunValues(runIndex, values)) {
|
|
|
fail(tr("Invalid sensitivity sampling configuration."));
|
|
|
if(errorMessage) *errorMessage = m_sFailureReason;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 在小型标量副本上恢复基准并校验,全部通过后一次写入,避免半更新输入。
|
|
|
nmPebiSolverInputSnapshot::SensitivityBaseline parameters =
|
|
|
m_pInputSnapshot->m_oSensitivityBaseline;
|
|
|
nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput;
|
|
|
nmPebiStaticPropertyCalculationInput& properties = m_pInputSnapshot->m_oStaticPropertyInput;
|
|
|
const QList<nmDataSensitive::VariableSampling>& variables = sampling.getVariables();
|
|
|
bool valid = values.size() == variables.size();
|
|
|
for(int i = 0; valid && i < variables.size(); ++i) {
|
|
|
const nmDataSensitive::VariableSampling& variable = variables[i];
|
|
|
if(!variable.m_enabled) continue;
|
|
|
const QString& name = variable.m_varName;
|
|
|
const double value = values[i];
|
|
|
valid = isFiniteSolverNumber(value);
|
|
|
if(!valid) break;
|
|
|
// 用井编码定位槽位,旧版无编码的参数仍对应分析主井。
|
|
|
if(name == "Skin" || name == "C") {
|
|
|
int wellIndex = variable.m_wellCode.isEmpty() ? parameters.m_nTarget : -1;
|
|
|
for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
|
|
|
if(grid.m_vecWellInputs[w].m_bRealWell &&
|
|
|
grid.m_vecWellInputs[w].m_sWellCode == variable.m_wellCode) { wellIndex = w; break; }
|
|
|
valid = value >= 0.0 && wellIndex >= 0;
|
|
|
if(!valid) break;
|
|
|
if(name == "Skin") {
|
|
|
parameters.m_dSceneSkin[wellIndex] = value;
|
|
|
parameters.m_dWellSkin[wellIndex] = value;
|
|
|
} else {
|
|
|
parameters.m_dSceneStorage[wellIndex] = value;
|
|
|
parameters.m_dWellStorage[wellIndex] = value;
|
|
|
}
|
|
|
} else if(name == "Pi") {
|
|
|
valid = value > 0.0;
|
|
|
parameters.m_dPressure = value;
|
|
|
} else if(name == "k" || name == "h" || name == QString(QChar(0x03c6))) {
|
|
|
valid = value > 0.0 && (name != QString(QChar(0x03c6)) || value <= 1.0);
|
|
|
const QString property = name == QString(QChar(0x03c6)) ? "phi" : name;
|
|
|
// 非均质插值会覆盖参考值,不能将这种运行误报为参数已变化。
|
|
|
for(int field = 0; valid && field < properties.m_vecDataSets.size(); ++field) {
|
|
|
const nmPropertyInterpolationDataSet& dataSet = properties.m_vecDataSets[field];
|
|
|
if(dataSet.useForCalculation && dataSet.property == property) valid = false;
|
|
|
}
|
|
|
if(name == "k") {
|
|
|
parameters.m_dReferencePermeability = value;
|
|
|
parameters.m_dScenePermeability = nmCalculationUtils::milliDarcyToDarcy(value);
|
|
|
} else if(name == "h") {
|
|
|
parameters.m_dReferenceThickness = value;
|
|
|
parameters.m_dSceneThickness = value;
|
|
|
} else {
|
|
|
parameters.m_dReferencePorosity = value;
|
|
|
parameters.m_dScenePorosity = value;
|
|
|
}
|
|
|
} else valid = false;
|
|
|
}
|
|
|
if(!valid) {
|
|
|
fail(tr("The sampled parameters are invalid or use a non-uniform field."));
|
|
|
if(errorMessage) *errorMessage = m_sFailureReason;
|
|
|
return false;
|
|
|
}
|
|
|
// 每组恢复所有井的基准,确定性单参数切换时不会继承上一口井的采样值。
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
grid.m_oScene.CS.S[i] = parameters.m_dSceneSkin[i];
|
|
|
grid.m_oScene.CS.C[i] = parameters.m_dSceneStorage[i];
|
|
|
grid.m_vecWellInputs[i].m_dSkin = parameters.m_dWellSkin[i];
|
|
|
grid.m_vecWellInputs[i].m_dWellboreStorage = parameters.m_dWellStorage[i];
|
|
|
}
|
|
|
grid.m_oScene.Base.Pi = parameters.m_dPressure;
|
|
|
grid.m_oScene.Base.k_ref = parameters.m_dScenePermeability;
|
|
|
grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness;
|
|
|
grid.m_oScene.Base.phi_ref = parameters.m_dScenePorosity;
|
|
|
properties.m_dReferencePermeabilityMilliDarcy = parameters.m_dReferencePermeability;
|
|
|
properties.m_dReferenceThicknessMeters = parameters.m_dReferenceThickness;
|
|
|
properties.m_dReferencePorosity = parameters.m_dReferencePorosity;
|
|
|
m_bInputSnapshotValid = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch(
|
|
|
const nmDataSensitive& sampling, std::vector<SensitivityRunResult>& results,
|
|
|
QString* errorMessage) const
|
|
|
{
|
|
|
results.clear();
|
|
|
// 同步入口与后台入口共用批量循环,只转移结果容器,不复制整批曲线。
|
|
|
QScopedPointer<nmCalculationDllPebiSolverTask> task(
|
|
|
createSensitivityBatch(sampling, errorMessage));
|
|
|
if(!task) return false;
|
|
|
task->run();
|
|
|
results.swap(task->m_pSensitivityResult->m_runs);
|
|
|
if(errorMessage) *errorMessage = task->m_sSensitivityFailureReason;
|
|
|
return task->m_bSensitivityBatchCompleted;
|
|
|
}
|
|
|
|
|
|
nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityBatch(
|
|
|
const nmDataSensitive& sampling, QString* errorMessage,
|
|
|
const nmSensitivityFitSettings* improveSettings) const
|
|
|
{
|
|
|
if(errorMessage) errorMessage->clear();
|
|
|
try {
|
|
|
// 所有配置与模型复制都在启动线程前完成,后台只读取独立的值输入。
|
|
|
nmDataSensitive frozenSampling(sampling);
|
|
|
// 冻结输入只保留采样配置,不让新任务额外延长上一批曲线的生命周期。
|
|
|
frozenSampling.setResults(QSharedPointer<nmDataSensitive::BatchResult>());
|
|
|
bool overflow = false;
|
|
|
const quint64 count = frozenSampling.calculateModelCount(&overflow);
|
|
|
const nmDataSensitive::CalculationType type = frozenSampling.getCalculationType();
|
|
|
if(overflow || count == 0 || !frozenSampling.usesBaseUnits() ||
|
|
|
(type != nmDataSensitive::CALC_DETERMINISTIC &&
|
|
|
type != nmDataSensitive::CALC_DETERMINISTIC_MULTIVARIATE &&
|
|
|
type != nmDataSensitive::CALC_MONTE_CARLO)) {
|
|
|
if(errorMessage) *errorMessage = tr("Invalid sensitivity sampling configuration.");
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
// 只在整次批量开始时复制模型与网格;首组参数由创建接口填入。
|
|
|
QString creationError;
|
|
|
QScopedPointer<nmCalculationDllPebiSolverTask> task(
|
|
|
createSensitivityRun(frozenSampling, 0, &creationError));
|
|
|
if(!task) {
|
|
|
if(errorMessage) *errorMessage = creationError;
|
|
|
return nullptr;
|
|
|
}
|
|
|
// 第四种的拟合配置按值冻结,整批样本共用确认过的初值和范围。
|
|
|
if(improveSettings) task->m_pSensitivityFitSettings =
|
|
|
QSharedPointer<const nmSensitivityFitSettings>(new nmSensitivityFitSettings(*improveSettings));
|
|
|
task->m_pSensitivitySampling = QSharedPointer<const nmDataSensitive>(
|
|
|
new nmDataSensitive(frozenSampling));
|
|
|
// 批次结果单独持有冻结配置与历史曲线,关闭参数窗口后仍可保存和显示。
|
|
|
task->m_pSensitivityResult = QSharedPointer<nmDataSensitive::BatchResult>(
|
|
|
new nmDataSensitive::BatchResult());
|
|
|
task->m_pSensitivityResult->m_calcType = frozenSampling.getCalculationType();
|
|
|
task->m_pSensitivityResult->m_variables = frozenSampling.getVariables();
|
|
|
task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode;
|
|
|
if(m_pDataManager != nullptr) {
|
|
|
// 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。
|
|
|
if(improveSettings) {
|
|
|
const nmDataAutomaticFitting& fit = improveSettings->parameters;
|
|
|
const QVector<nmFitParameter>& modelParameters = fit.getModelParameters();
|
|
|
for(int i = 0; i < modelParameters.size(); ++i) {
|
|
|
const nmFitParameter& p = modelParameters[i];
|
|
|
if(!p.selected) continue;
|
|
|
double value = p.type == 0 ? m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy
|
|
|
: m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity;
|
|
|
nmDataWellBase* parameterWell = m_pDataManager->findWellByCode(p.wellCode);
|
|
|
if(p.type != 0 && p.type != 3) {
|
|
|
if(!parameterWell) { if(errorMessage) *errorMessage = tr("The target well is unavailable."); return nullptr; }
|
|
|
if(p.type == 1) value = parameterWell->getPerforation(0)->getSkin().getValue().toDouble();
|
|
|
else if(p.type == 2) value = parameterWell->getWellboreStorage().getValue().toDouble();
|
|
|
else if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(parameterWell))
|
|
|
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
|
|
|
else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(parameterWell))
|
|
|
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
|
|
|
}
|
|
|
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(p.name(), value, p.unit()));
|
|
|
task->m_pSensitivityResult->m_fitParameterCodes.append(p.wellCode.isEmpty() ? p.code() : p.wellCode + "/" + p.code());
|
|
|
}
|
|
|
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
|
|
|
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
|
|
|
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
|
|
|
const char* names[] = { "Permeability", "Skin", "Wellbore storage", "Porosity",
|
|
|
"Fracture conductivity", "Fracture half length" };
|
|
|
const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" };
|
|
|
const char* codes[] = { "K", "Skin", "C", "phi", "Dfc", "FractureHalfLength" };
|
|
|
nmDataWellBase* well = m_pDataManager->findWellByName(improveSettings->targetWell);
|
|
|
if(well) {
|
|
|
// 启动前补齐拟合目标井的历史双对数,后台各组共用这份目标数据。
|
|
|
QVector<nmPebiWellInputSnapshot>& wells = task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs;
|
|
|
for(int i = 0; i < wells.size(); ++i) {
|
|
|
if(wells[i].m_bRealWell && wells[i].m_sWellCode == well->getWellCode()) {
|
|
|
wells[i].m_vecHistoryLogLog = well->getHistoryLogLog();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
double conductivity = 0.0, halfLength = 0.0;
|
|
|
if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
|
|
|
conductivity = fractured->getDfc().getValue().toDouble();
|
|
|
halfLength = fractured->getFractureHalfLength().getValue().toDouble();
|
|
|
} else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
|
|
|
conductivity = fractured->getDfc().getValue().toDouble();
|
|
|
halfLength = fractured->getFractureHalfLength().getValue().toDouble();
|
|
|
}
|
|
|
const double values[] = { m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy,
|
|
|
well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0,
|
|
|
well->getWellboreStorage().getValue().toDouble(),
|
|
|
m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, conductivity, halfLength };
|
|
|
for(int j = 0; j < 6; ++j) if(modelParameters.isEmpty() && selected[j]) {
|
|
|
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(
|
|
|
QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j]));
|
|
|
task->m_pSensitivityResult->m_fitParameterCodes.append(codes[j]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
nmDataWellBase* well = m_pDataManager->findWellByCode(task->m_sAutoFitTargetWellCode);
|
|
|
if(well) {
|
|
|
// 第三、四种方法按分析主井的实测压力统计误差,与拟合目标井分别取值。
|
|
|
const int target = task->m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget;
|
|
|
task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure = well->getHistoryPressure();
|
|
|
task->m_pSensitivityResult->m_sWellName = well->getWellName();
|
|
|
task->m_pSensitivityResult->m_vecHistoryLogLog = well->getHistoryLogLog();
|
|
|
// 对比曲线沿用已有求解结果,分析输入则取当前参数。
|
|
|
task->m_pSensitivityResult->m_vecBaseLogLog = well->getResultLogLog();
|
|
|
}
|
|
|
}
|
|
|
return task.take();
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
if(errorMessage) *errorMessage = tr("Insufficient memory to prepare solver input.");
|
|
|
return nullptr;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const std::vector<nmCalculationDllPebiSolverTask::SensitivityRunResult>&
|
|
|
nmCalculationDllPebiSolverTask::getSensitivityResults() const
|
|
|
{
|
|
|
// 调用方须先等待线程结束,运行中的结果容器不向界面开放读取。
|
|
|
static const std::vector<SensitivityRunResult> emptyResults;
|
|
|
return m_pSensitivityResult.isNull() ? emptyResults : m_pSensitivityResult->m_runs;
|
|
|
}
|
|
|
|
|
|
QSharedPointer<nmDataSensitive::BatchResult> nmCalculationDllPebiSolverTask::getSensitivityBatchResult() const
|
|
|
{
|
|
|
// 调用方只能在启动前或 wait() 后访问,后台写入期间不向界面发布。
|
|
|
return m_pSensitivityResult;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::wasSensitivityBatchCompleted() const
|
|
|
{
|
|
|
return m_bSensitivityBatchCompleted;
|
|
|
}
|
|
|
|
|
|
double nmCalculationDllPebiSolverTask::calculatePressureRmse(
|
|
|
const QVector<QVector<double> >& measured, const QVector<QVector<double> >& simulated)
|
|
|
{
|
|
|
// 散点图的基准点复用批量求解的插值与 RMSE 计算,不另写误差公式。
|
|
|
return calculateSensitivityPressureRmse(measured, simulated, nullptr);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fittedParameters)
|
|
|
{
|
|
|
// 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。
|
|
|
const nmSensitivityFitSettings& settings = *m_pSensitivityFitSettings;
|
|
|
const nmDataAutomaticFitting& fit = settings.parameters;
|
|
|
nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput;
|
|
|
nmPebiStaticPropertyCalculationInput& properties = m_pInputSnapshot->m_oStaticPropertyInput;
|
|
|
int target = -1;
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
if(grid.m_vecWellInputs[i].m_bRealWell &&
|
|
|
grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; }
|
|
|
}
|
|
|
if(target < 0) return fail(tr("The target well is unavailable."));
|
|
|
QVector<nmFitParameter> fitted = fit.getModelParameters();
|
|
|
if(fitted.isEmpty()) {
|
|
|
// 旧项目仍按原来的目标井和六参数顺序运行。
|
|
|
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
|
|
|
fit.getWellboreStorageSelected(), fit.getPorositySelected(),
|
|
|
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
|
|
|
for(int type = 0; type < 6; ++type) if(selected[type]) {
|
|
|
nmFitParameter p;
|
|
|
p.type = type;
|
|
|
p.selected = true;
|
|
|
if(type != 0 && type != 3) p.wellCode = grid.m_vecWellInputs[target].m_sWellCode;
|
|
|
fitted.append(p);
|
|
|
}
|
|
|
}
|
|
|
for(int i = fitted.size() - 1; i >= 0; --i)
|
|
|
if(!fitted[i].selected) fitted.remove(i);
|
|
|
// 每项记录自己的求解槽位,联合拟合时不再共用一个目标井槽位。
|
|
|
QVector<int> wellIndices;
|
|
|
for(int i = 0; i < fitted.size(); ++i) {
|
|
|
const nmFitParameter& p = fitted[i];
|
|
|
int slot = -1;
|
|
|
for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
|
|
|
if(grid.m_vecWellInputs[w].m_bRealWell && grid.m_vecWellInputs[w].m_sWellCode == p.wellCode) { slot = w; break; }
|
|
|
if(p.type != 0 && p.type != 3 && slot < 0) return fail(tr("The target well is unavailable."));
|
|
|
wellIndices.append(slot);
|
|
|
for(int field = 0; field < properties.m_vecDataSets.size(); ++field) {
|
|
|
const nmPropertyInterpolationDataSet& data = properties.m_vecDataSets[field];
|
|
|
if(data.useForCalculation && ((p.type == 0 && data.property == "k") || (p.type == 3 && data.property == "phi")))
|
|
|
return fail(tr("The sampled parameters are invalid or use a non-uniform field."));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 保存原裂缝端点,所有候选都从原几何缩放,防止重复评价产生累计偏差。
|
|
|
const std::vector<std::vector<double> > originalVertical = grid.m_oGridInput.FractureVerticalWell;
|
|
|
const std::vector<std::vector<std::vector<double> > > originalHorizontal = grid.m_oGridInput.MultistageFracturedHorizontalWell;
|
|
|
const QString resultWellCode = m_sAutoFitTargetWellCode;
|
|
|
m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode;
|
|
|
auto evaluate = [&](const QVector<double>& values) -> QVector<QVector<double> > {
|
|
|
if(isCancelRequested()) return QVector<QVector<double> >();
|
|
|
grid.m_oGridInput.FractureVerticalWell = originalVertical;
|
|
|
grid.m_oGridInput.MultistageFracturedHorizontalWell = originalHorizontal;
|
|
|
for(int i = 0; i < fitted.size(); ++i) {
|
|
|
const int type = fitted[i].type;
|
|
|
const int slot = wellIndices[i];
|
|
|
const double value = values[i];
|
|
|
if(type == 0) {
|
|
|
grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(value);
|
|
|
properties.m_dReferencePermeabilityMilliDarcy = value;
|
|
|
} else if(type == 3) {
|
|
|
grid.m_oScene.Base.phi_ref = value;
|
|
|
properties.m_dReferencePorosity = value;
|
|
|
} else if(type == 1) {
|
|
|
grid.m_oScene.CS.S[slot] = value;
|
|
|
grid.m_vecWellInputs[slot].m_dSkin = value;
|
|
|
} else if(type == 2) {
|
|
|
grid.m_oScene.CS.C[slot] = value;
|
|
|
grid.m_vecWellInputs[slot].m_dWellboreStorage = value;
|
|
|
} else {
|
|
|
std::vector<std::vector<double>*> fractures;
|
|
|
size_t vertical = 0, horizontal = 0;
|
|
|
for(int w = 0; w <= slot; ++w) {
|
|
|
const int wellType = grid.m_vecSolverWellOrder[w].m_eWellType;
|
|
|
if(wellType == Vertical_Fractured_Well) {
|
|
|
if(w == slot) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]);
|
|
|
++vertical;
|
|
|
} else if(wellType == Horizontal_Fractured_Well) {
|
|
|
if(w == slot) {
|
|
|
std::vector<std::vector<double> >& stages = grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal];
|
|
|
for(size_t j = 0; j < stages.size(); ++j) fractures.push_back(&stages[j]);
|
|
|
}
|
|
|
++horizontal;
|
|
|
}
|
|
|
}
|
|
|
if(fractures.empty()) return QVector<QVector<double> >();
|
|
|
for(size_t j = 0; j < fractures.size(); ++j) {
|
|
|
std::vector<double>& fracture = *fractures[j];
|
|
|
if(type == 4) fracture[5] = value;
|
|
|
else {
|
|
|
const double dx = (fracture[2] - fracture[0]) * 0.5;
|
|
|
const double dy = (fracture[3] - fracture[1]) * 0.5;
|
|
|
const double centerX = (fracture[0] + fracture[2]) * 0.5;
|
|
|
const double centerY = (fracture[1] + fracture[3]) * 0.5;
|
|
|
const double scale = value / std::sqrt(dx * dx + dy * dy);
|
|
|
fracture[0] = centerX - dx * scale;
|
|
|
fracture[1] = centerY - dy * scale;
|
|
|
fracture[2] = centerX + dx * scale;
|
|
|
fracture[3] = centerY + dy * scale;
|
|
|
}
|
|
|
}
|
|
|
if(type == 4) grid.m_vecWellInputs[slot].m_dDfc = value;
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = true;
|
|
|
}
|
|
|
}
|
|
|
m_sFailureReason.clear();
|
|
|
calculateSingleRun();
|
|
|
return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >();
|
|
|
};
|
|
|
auto cancelled = [&]() -> bool { return isCancelRequested(); };
|
|
|
const bool verticalWell = grid.m_vecSolverWellOrder[target].m_eWellType == Vertical_Well;
|
|
|
// 敏感性改善仅使用 LM,直接复用其拟合流程。
|
|
|
nmCalculationAutoFitLM fitter;
|
|
|
fitter.setTargetLogLogData(grid.m_vecWellInputs[target].m_vecHistoryLogLog);
|
|
|
const bool succeeded = fitter.startSensitivityFitting(settings, evaluate, cancelled, verticalWell);
|
|
|
const QVector<double> best = fitter.getBestSolution();
|
|
|
const QString error = fitter.getLastError();
|
|
|
// 算法最后评价的候选不一定最优;恢复分析井后用最优参数求解并保留三条曲线。
|
|
|
m_sAutoFitTargetWellCode = resultWellCode;
|
|
|
if(isCancelRequested()) return false;
|
|
|
if(!succeeded) {
|
|
|
m_sFailureReason.clear();
|
|
|
return fail(error.isEmpty() ? tr("No valid fitting result was obtained.") : error);
|
|
|
}
|
|
|
evaluate(best);
|
|
|
// 最优参数与最终曲线一同返回,失败组不留下可显示的拟合值。
|
|
|
if(m_lastRunSucceeded) fittedParameters = best;
|
|
|
return m_lastRunSucceeded;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
|
|
|
{
|
|
|
m_pSensitivityResult->m_runs.clear();
|
|
|
m_sSensitivityFailureReason.clear();
|
|
|
const quint64 count = m_pSensitivitySampling->calculateModelCount();
|
|
|
quint64 failed = 0;
|
|
|
QElapsedTimer progressTimer;
|
|
|
progressTimer.start();
|
|
|
emit sigSensitivityProgress(0, count, 0);
|
|
|
try {
|
|
|
// 拟合还会改动其他井的参数、裂缝几何及网格,整批只保留一份初始输入供逐组恢复。
|
|
|
QScopedPointer<nmPebiSolverInputSnapshot> fitBaseline;
|
|
|
if(!m_pSensitivityFitSettings.isNull())
|
|
|
fitBaseline.reset(new nmPebiSolverInputSnapshot(*m_pInputSnapshot));
|
|
|
|
|
|
for(quint64 runIndex = 0; runIndex < count; ++runIndex) {
|
|
|
if(isCancelRequested()) return false;
|
|
|
// 先还原输入,再由 updateSensitivityRun 填入本组采样值;停止标记不随输入重置。
|
|
|
if(!fitBaseline.isNull() && runIndex > 0) *m_pInputSnapshot = *fitBaseline;
|
|
|
SensitivityRunResult result;
|
|
|
result.m_nRunIndex = runIndex;
|
|
|
// 确定性组合和 Monte-Carlo 样本均按预览顺序读取,后台不重新采样。
|
|
|
bool inputReady = m_pSensitivitySampling->getRunValues(runIndex, result.m_vecParameters);
|
|
|
if(!inputReady) {
|
|
|
result.m_sFailureReason = tr("Invalid sensitivity sampling configuration.");
|
|
|
} else {
|
|
|
// 本组更新失败仍保留运行编号,下一组继续从冻结基准更新。
|
|
|
inputReady = updateSensitivityRun(*m_pSensitivitySampling, runIndex,
|
|
|
&result.m_sFailureReason);
|
|
|
}
|
|
|
if(inputReady) {
|
|
|
if(m_pSensitivityFitSettings.isNull()) {
|
|
|
calculateSingleRun();
|
|
|
result.m_bSucceeded = m_lastRunSucceeded;
|
|
|
} else {
|
|
|
result.m_bSucceeded = improveSensitivityRun(result.m_vecFittedParameters);
|
|
|
}
|
|
|
if(result.m_bSucceeded) {
|
|
|
result.m_vecPressure = m_autoFitResultPressure;
|
|
|
result.m_vecLogLog = m_autoFitResultLogLog;
|
|
|
result.m_vecSemiLog = m_autoFitResultSemiLog;
|
|
|
if(m_pSensitivityResult->m_calcType == nmDataSensitive::CALC_MONTE_CARLO) {
|
|
|
// 使用本批任务开始时读取的实测压力,计算过程中不访问界面数据。
|
|
|
const int target = m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget;
|
|
|
result.m_dPressureRmse = calculateSensitivityPressureRmse(
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure,
|
|
|
result.m_vecPressure, &m_nCancelRequested);
|
|
|
}
|
|
|
} else {
|
|
|
result.m_sFailureReason = m_sFailureReason;
|
|
|
}
|
|
|
}
|
|
|
if(isCancelRequested()) {
|
|
|
result.m_bCancelled = true;
|
|
|
result.m_bSucceeded = false;
|
|
|
result.m_sFailureReason.clear();
|
|
|
result.m_vecPressure.clear();
|
|
|
result.m_vecLogLog.clear();
|
|
|
result.m_vecSemiLog.clear();
|
|
|
result.m_dPressureRmse = -1.0;
|
|
|
result.m_vecFittedParameters.clear();
|
|
|
} else if(!result.m_bSucceeded) {
|
|
|
++failed;
|
|
|
}
|
|
|
// Qt 曲线容器按值保留;下一组清空任务结果不会清掉这里的曲线。
|
|
|
// 不预先展开全部组合或预分配总次数,也不合并重复的基准组合。
|
|
|
m_pSensitivityResult->m_runs.push_back(result);
|
|
|
// 快速失败的多组输入也限频通知,避免排队的进度信号堵塞界面。
|
|
|
if(progressTimer.elapsed() >= 100 || runIndex + 1 == count || isCancelRequested()) {
|
|
|
emit sigSensitivityProgress(runIndex + 1, count, failed);
|
|
|
progressTimer.restart();
|
|
|
}
|
|
|
if(isCancelRequested()) return false;
|
|
|
}
|
|
|
return true;
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
m_sSensitivityFailureReason = tr("Insufficient memory to process solver results.");
|
|
|
return false;
|
|
|
} catch(const std::length_error&) {
|
|
|
m_sSensitivityFailureReason = tr("Insufficient memory to process solver results.");
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::run()
|
|
|
{
|
|
|
if(!m_pSensitivitySampling.isNull()) {
|
|
|
// 同一后台线程逐组执行,仅在整次结束后发送完成信号。
|
|
|
m_bSensitivityBatchCompleted = executeSensitivityBatch();
|
|
|
m_lastRunSucceeded = m_bSensitivityBatchCompleted;
|
|
|
for(size_t i = 0; m_lastRunSucceeded && i < m_pSensitivityResult->m_runs.size(); ++i)
|
|
|
m_lastRunSucceeded = m_pSensitivityResult->m_runs[i].m_bSucceeded;
|
|
|
if(isCancelRequested()) {
|
|
|
m_bSensitivityBatchCompleted = false;
|
|
|
m_lastRunSucceeded = false;
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(1);
|
|
|
}
|
|
|
// 完成状态随曲线一起交给数据层,失败和停止时保留已处理组。
|
|
|
m_pSensitivityResult->m_bCompleted = m_bSensitivityBatchCompleted;
|
|
|
m_pSensitivityResult->m_bStopped = wasCancelled();
|
|
|
m_pSensitivityResult->m_sFailureReason = m_sSensitivityFailureReason;
|
|
|
discardPendingFullResult();
|
|
|
} else {
|
|
|
calculateSingleRun();
|
|
|
}
|
|
|
// 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。
|
|
|
releaseDataManagerUse();
|
|
|
emit sig_calculateDone(m_lastRunSucceeded);
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::calculateSingleRun()
|
|
|
{
|
|
|
m_nSolveTimeMs = -1;
|
|
|
bool bSucceeded = false;
|
|
|
try {
|
|
|
bSucceeded = this->execute();
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
// VTK 部分分配接口没有返回值,统一在任务边界拒绝候选并保留旧快照。
|
|
|
qWarning() << "Cannot allocate PEBI solver result snapshot.";
|
|
|
fail(tr("Insufficient memory to process solver results."));
|
|
|
discardPendingFullResult();
|
|
|
bSucceeded = false;
|
|
|
}
|
|
|
if(!bSucceeded) {
|
|
|
// execute() 的所有普通失败也必须释放压力帧、网格别名和候选快照。
|
|
|
// 该清理函数允许在取消或异常分支再次调用。
|
|
|
discardPendingFullResult();
|
|
|
}
|
|
|
if(isCancelRequested()) {
|
|
|
// 停止只丢弃任务局部结果,主线程不会进入 commitResult()。
|
|
|
m_nWasCancelled.fetchAndStoreOrdered(1);
|
|
|
discardPendingFullResult();
|
|
|
bSucceeded = false;
|
|
|
}
|
|
|
m_lastRunSucceeded = bSucceeded;
|
|
|
}
|
|
|
|
|
|
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 fail(tr("Solver input is unavailable."));
|
|
|
}
|
|
|
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 fail(tr("Solver input is unavailable."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("Failed to capture solver input."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("The well configuration is invalid."));
|
|
|
}
|
|
|
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 fail(tr("No valid wells are available for solving."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("The well configuration is invalid."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("Failed to capture well data."));
|
|
|
}
|
|
|
if(m_pInputSnapshot->m_oGridInput.m_vecWellInputs.isEmpty() ||
|
|
|
!captureResultWellMetadata(
|
|
|
m_pDataManager,
|
|
|
pWellData,
|
|
|
isDisplayResultWell(pAnalysisCase,
|
|
|
pWellData->getWellCode()),
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs.last())) {
|
|
|
return fail(tr("Failed to capture well data."));
|
|
|
}
|
|
|
++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 fail(tr("Failed to finalize solver input."));
|
|
|
}
|
|
|
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_nSolverType =
|
|
|
m_pDataManager->getPebiSolverType();
|
|
|
m_pInputSnapshot->m_nOmpThreads =
|
|
|
m_pDataManager->getPebiOmpThreads();
|
|
|
m_pInputSnapshot->m_nIluReuseSteps =
|
|
|
m_pDataManager->getPebiIluReuseSteps();
|
|
|
nmDataReservoir* pCapturedReservoir = NULL;
|
|
|
// 手工求解无条件冻结静态输入;正式结果参数复用同一次储层对象读取。
|
|
|
if(!captureStaticPropertyInput(m_pDataManager,
|
|
|
*m_pInputSnapshot,
|
|
|
true,
|
|
|
&pCapturedReservoir) ||
|
|
|
!captureResultParameters(m_pDataManager,
|
|
|
pCapturedReservoir,
|
|
|
*m_pInputSnapshot)) {
|
|
|
return fail(tr("Failed to capture solver settings."));
|
|
|
}
|
|
|
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 || !m_pSensitivitySampling.isNull())) {
|
|
|
emit sigStageChanged(sStage, nCurrent, nTotal);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::fail(const QString& sFailureReason)
|
|
|
{
|
|
|
if(!isCancelRequested() && m_sFailureReason.isEmpty()) {
|
|
|
m_sFailureReason = sFailureReason;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
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 fail(tr("Solver input is unavailable."));
|
|
|
}
|
|
|
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
m_pDataManager->getNumericalAnalysisCase();
|
|
|
if(pAnalysisCase == nullptr) {
|
|
|
return fail(tr("Solver input is unavailable."));
|
|
|
}
|
|
|
|
|
|
// 第一步:冻结几何和求解输入版本。提交阶段会再次核对,期间发生任何编辑
|
|
|
// 都只会让旧任务结果作废,不会影响后台正在读取的值快照。
|
|
|
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 fail(tr("The target well is unavailable."));
|
|
|
}
|
|
|
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 fail(tr("Failed to capture solver input."));
|
|
|
}
|
|
|
|
|
|
const QVector<nmSolverWellRef>& vecWellOrder =
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder;
|
|
|
if(m_pInputSnapshot->m_oGridInput.m_vecWellInputs.size() !=
|
|
|
vecWellOrder.size()) {
|
|
|
return fail(tr("Failed to capture well data."));
|
|
|
}
|
|
|
|
|
|
// 自动拟合候选保持原来的轻量输入;只有最终完整场求解冻结正式结果元数据。
|
|
|
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(
|
|
|
m_pDataManager,
|
|
|
pWellData,
|
|
|
isDisplayResultWell(pAnalysisCase,
|
|
|
oWellRef.m_sWellCode),
|
|
|
m_pInputSnapshot->m_oGridInput.m_vecWellInputs[nIndex])) {
|
|
|
return fail(tr("Failed to capture well data."));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第三步:冻结 DLL 求解配置和静态属性输入。静态输入必须在自动拟合分支
|
|
|
// 判断之外捕获,使每个粒子任务都读取候选参数写入后的最新储层参考值。
|
|
|
m_pInputSnapshot->m_nSolverType =
|
|
|
m_pDataManager->getPebiSolverType();
|
|
|
m_pInputSnapshot->m_nOmpThreads =
|
|
|
m_pDataManager->getPebiOmpThreads();
|
|
|
m_pInputSnapshot->m_nIluReuseSteps =
|
|
|
m_pDataManager->getPebiIluReuseSteps();
|
|
|
nmDataReservoir* pCapturedReservoir = NULL;
|
|
|
const bool bCaptureFullResult =
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly;
|
|
|
if(!captureStaticPropertyInput(m_pDataManager,
|
|
|
*m_pInputSnapshot,
|
|
|
bCaptureFullResult,
|
|
|
&pCapturedReservoir) ||
|
|
|
(bCaptureFullResult &&
|
|
|
!captureResultParameters(m_pDataManager,
|
|
|
pCapturedReservoir,
|
|
|
*m_pInputSnapshot))) {
|
|
|
return fail(tr("Failed to capture solver settings."));
|
|
|
}
|
|
|
|
|
|
// 第四步:主线程仅检查缓存并捕获基础网格的智能指针。生成后的基础网格
|
|
|
// 不在原位修改,智能指针保证 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;
|
|
|
if(pGridService->copyCurrentGridFor(m_pDataManager,
|
|
|
m_nGridInputRevision,
|
|
|
oGridOutput1,
|
|
|
oGridOutput2)) {
|
|
|
m_pInputSnapshot->m_oGridResult.m_oGridOutput1 = oGridOutput1;
|
|
|
m_pInputSnapshot->m_oGridResult.m_oGridOutput2 = oGridOutput2;
|
|
|
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::configureRateForecast(
|
|
|
double initialPressure, const QVector<double>& durations,
|
|
|
const QVector<double>& rates)
|
|
|
{
|
|
|
// 使用已有模型的任务副本,从零时刻重新施加制度,绝不改写正式模型及历史数据。
|
|
|
if(isRunning() || !m_bInputSnapshotValid || m_pInputSnapshot == NULL ||
|
|
|
!m_pInputSnapshot->m_bAutoFitTargetOnly) {
|
|
|
return fail(tr("Forecast input is unavailable."));
|
|
|
}
|
|
|
m_bInputSnapshotValid = false;
|
|
|
if(m_pDataManager == NULL || !m_pDataManager->isPebiResultSnapshotCurrent() ||
|
|
|
hasInputChanged(m_pDataManager)) {
|
|
|
return fail(tr("Generate the current model before forecasting."));
|
|
|
}
|
|
|
nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput;
|
|
|
nmDataBinaryTools::NM_PEBI_SCENE& scene = grid.m_oScene;
|
|
|
const int model = scene.solverType;
|
|
|
if(model < SMT_Oil_ConstPvt || model > SMT_Gas_PseudoPressure) {
|
|
|
return fail(tr("Rate forecast currently supports single-phase models only."));
|
|
|
}
|
|
|
int target = -1;
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
const nmPebiWellInputSnapshot& well = grid.m_vecWellInputs[i];
|
|
|
if(!well.m_bRealWell) continue;
|
|
|
if(well.m_sWellCode == m_sAutoFitTargetWellCode && well.m_bRateControlled) target = i;
|
|
|
}
|
|
|
if(target < 0) {
|
|
|
return fail(tr("The forecast well must be rate-controlled."));
|
|
|
}
|
|
|
if(!isFiniteSolverNumber(initialPressure) || initialPressure <= 0.0 ||
|
|
|
durations.isEmpty() || durations.size() != rates.size()) {
|
|
|
return fail(tr("Enter a positive initial pressure and a complete rate schedule."));
|
|
|
}
|
|
|
double totalTime = 0.0;
|
|
|
double cumulative = 0.0;
|
|
|
for(int i = 0; i < durations.size(); ++i) {
|
|
|
const double nextTime = totalTime + durations[i];
|
|
|
cumulative += rates[i] * durations[i] / 24.0;
|
|
|
if(!isFiniteSolverNumber(durations[i]) || durations[i] <= 0.0 ||
|
|
|
!isFiniteSolverNumber(rates[i]) || rates[i] < 0.0 ||
|
|
|
!isFiniteSolverNumber(nextTime) || nextTime <= totalTime ||
|
|
|
!isFiniteSolverNumber(cumulative)) {
|
|
|
return fail(tr("Each duration must be positive and each production rate non-negative."));
|
|
|
}
|
|
|
totalTime = nextTime;
|
|
|
}
|
|
|
|
|
|
// 当前 DLL 除油单相常量 PVT 外,单段制度会抛出 vector<T> too long。
|
|
|
// 拆为两个同产量的等长段,仅满足 DLL 输入要求,不改变用户的生产制度。
|
|
|
QVector<double> solverDurations = durations;
|
|
|
QVector<double> solverRates = rates;
|
|
|
if(durations.size() == 1 && model != SMT_Oil_ConstPvt) {
|
|
|
solverDurations[0] = durations[0] / 2.0;
|
|
|
solverDurations.append(durations[0] - solverDurations[0]);
|
|
|
solverRates.append(rates[0]);
|
|
|
}
|
|
|
// 只替换当前井的制度,保留其他井及手工裂缝槽位,沿用普通多井求解计算井间干扰。
|
|
|
scene.Rate.t[target].assign(solverDurations.constBegin(), solverDurations.constEnd());
|
|
|
scene.Rate.qo[target].assign(solverRates.size(), 0.0);
|
|
|
scene.Rate.qg[target].assign(solverRates.size(), 0.0);
|
|
|
scene.Rate.qw[target].assign(solverRates.size(), 0.0);
|
|
|
std::vector<double>& phaseRates = model <= SMT_Oil_VariablePvt ? scene.Rate.qo[target]
|
|
|
: model <= SMT_Water_VariablePvt ? scene.Rate.qw[target] : scene.Rate.qg[target];
|
|
|
phaseRates.assign(solverRates.constBegin(), solverRates.constEnd());
|
|
|
scene.Base.Pi = initialPressure;
|
|
|
m_pInputSnapshot->m_bRateForecast = true;
|
|
|
m_bInputSnapshotValid = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double> > nmCalculationDllPebiSolverTask::getRateForecastPressure() const
|
|
|
{
|
|
|
// 沿用目标井临时结果容器,预测结果不进入正式成果提交路径。
|
|
|
return m_pInputSnapshot != NULL && m_pInputSnapshot->m_bRateForecast
|
|
|
? m_autoFitResultPressure : QVector<QVector<double> >();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::prepareManualInput()
|
|
|
{
|
|
|
if(m_pInputSnapshot == nullptr) {
|
|
|
return fail(tr("Solver input snapshot is invalid."));
|
|
|
}
|
|
|
if(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 isCancelRequested()
|
|
|
? false
|
|
|
: fail(tr("Failed to prepare solver input."));
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
&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 fail(tr("The prepared result grid is invalid."));
|
|
|
}
|
|
|
} 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;
|
|
|
}
|
|
|
|
|
|
QString nmCalculationDllPebiSolverTask::getFailureReason() const
|
|
|
{
|
|
|
return m_pSensitivitySampling.isNull() ? m_sFailureReason : m_sSensitivityFailureReason;
|
|
|
}
|
|
|
|
|
|
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 isCancelRequested()
|
|
|
? false
|
|
|
: fail(tr("Solver input snapshot is invalid."));
|
|
|
}
|
|
|
|
|
|
const QAtomicInt* pCancelRequested =
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly && !m_pInputSnapshot->m_bRateForecast &&
|
|
|
m_pSensitivitySampling.isNull()
|
|
|
? NULL : &m_nCancelRequested;
|
|
|
|
|
|
// 第一步:清空本次任务自己的输出。旧成果仍保留在 DataManager 中,只有主线程
|
|
|
// 最终提交成功后才会被整体替换。
|
|
|
m_bPendingFullResultReady = false;
|
|
|
m_vecPendingWellResults.clear();
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
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);
|
|
|
nmCalculationPebiGrid* pGridService =
|
|
|
nmCalculationPebiGrid::getInstance();
|
|
|
if(pGridService == nullptr ||
|
|
|
!pGridService->calculateSnapshot(
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
oGridResult,
|
|
|
bCreateUnstructuredGrid,
|
|
|
pCancelRequested)) {
|
|
|
return isCancelRequested()
|
|
|
? false
|
|
|
: fail(tr("Grid generation failed."));
|
|
|
}
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 重开项目或当前几何改变时首次重建,后续采样复用本批网格。
|
|
|
m_pInputSnapshot->m_bRequiresGridCalculation = false;
|
|
|
if(bCreateUnstructuredGrid) {
|
|
|
if(oGridResult.m_pUnstructuredGrid == nullptr ||
|
|
|
oGridResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
|
|
|
return fail(tr("The generated grid is invalid."));
|
|
|
}
|
|
|
|
|
|
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 isCancelRequested()
|
|
|
? false
|
|
|
: fail(tr("The generated grid is invalid."));
|
|
|
}
|
|
|
|
|
|
// 第三步:先验证 DLL 原始单元数组的共同结构索引域。自动拟合不创建 VTK,
|
|
|
// 因此这项约束必须位于求解任务内,不能只依赖 VTK 转换路径。
|
|
|
QVector<QPointF> vecPropertyLocations;
|
|
|
QString sPreparationError;
|
|
|
if(!extractStaticPropertyLocations(oGridResult.m_oGridOutput1,
|
|
|
oGridResult.m_oGridOutput2,
|
|
|
vecPropertyLocations,
|
|
|
sPreparationError,
|
|
|
pCancelRequested)) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const QString sLogMessage =
|
|
|
QString::fromLatin1("Invalid PEBI property index domain: %1")
|
|
|
.arg(sPreparationError);
|
|
|
qWarning() << sLogMessage;
|
|
|
zxLogInstance::getInstance()->writeLogF(sLogMessage);
|
|
|
return fail(tr("The generated grid is invalid."));
|
|
|
}
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:由任务值快照重建完整模型输入。静态属性计算只读取任务自己的
|
|
|
// 位置和测点副本,因此工作线程不会访问或竞争 DataManager 对象。
|
|
|
// 直接初始化可避免先构造临时模型输入、再复制完整 GRID 的额外内存峰值。
|
|
|
HX_NWTM_MODEL_INPUT oModelInput(oGridResult.m_oGridOutput2);
|
|
|
nmPebiStaticPropertyValues oStaticProperties;
|
|
|
reportStage(tr("Preparing model input..."), 0, 0);
|
|
|
if(!buildModelInputFromSnapshot(*m_pInputSnapshot,
|
|
|
vecPropertyLocations,
|
|
|
oModelInput,
|
|
|
oStaticProperties,
|
|
|
sPreparationError,
|
|
|
pCancelRequested)) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const QString sLogMessage =
|
|
|
QString::fromLatin1("Static property preparation failed: %1")
|
|
|
.arg(sPreparationError);
|
|
|
qWarning() << sLogMessage;
|
|
|
zxLogInstance::getInstance()->writeLogF(sLogMessage);
|
|
|
// 沿用既有已翻译提示;详细原因已经写入日志,不扩展本阶段 UI 文案。
|
|
|
return fail(tr("Property interpolation failed."));
|
|
|
}
|
|
|
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第五步:模型求解与前面的建网、Kriging 共用 DLL 全局状态。Kriging 已在
|
|
|
// calculateKriging() 内独立持锁,这里只串行保护模型求解入口。
|
|
|
reportStage(tr("Waiting for solver..."), 0, 0);
|
|
|
nmSolverDllMutexLocker oDllLocker;
|
|
|
if(!oDllLocker.lock(nmCalculationUtils::getHxNwtmDllMutex(),
|
|
|
pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
// 等待锁结束时再次检查停止请求,避免已经停止的批量组仍进入 DLL。
|
|
|
if(isCancelRequested()) return false;
|
|
|
HMODULE hModelModule = LoadLibrary(L"HX_NWTM.dll");
|
|
|
if(hModelModule == nullptr) {
|
|
|
qWarning() << "Failed to load HX_NWTM.dll. Error code:"
|
|
|
<< GetLastError();
|
|
|
return fail(tr("Failed to load HX_NWTM.dll."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("The solver interface is incomplete."));
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
// 只统计进入数值求解器的调用,网格准备失败和缓存复用不计入。
|
|
|
emit solverInvoked();
|
|
|
pfnModel(oModelOutput,
|
|
|
oModelInput,
|
|
|
m_pInputSnapshot->m_oGridInput.m_sLicensePath
|
|
|
.toStdString());
|
|
|
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 fail(tr("An exception occurred while calling the solver."));
|
|
|
} catch(...) {
|
|
|
qWarning() << "SEH Exception Occurred during HX_NWTM_MODEL call";
|
|
|
logHX_NWTM_MODEL_INPUT_Simplified(oModelInput);
|
|
|
FreeLibrary(hModelModule);
|
|
|
return fail(tr("An exception occurred while calling the solver."));
|
|
|
}
|
|
|
|
|
|
// 后处理只读取本次局部输出,无需继续占用进程级 DLL 锁。
|
|
|
FreeLibrary(hModelModule);
|
|
|
oDllLocker.unlock();
|
|
|
|
|
|
// HX_NWTM_MODEL 没有停止入口;调用期间收到请求时,返回后直接丢弃输出。
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第五步:把井曲线和场压力构造成任务局部结果。该函数同样只读取输入快照。
|
|
|
const bool bSucceeded = buildPebiModeResult(
|
|
|
oModelOutput,
|
|
|
oModelInput.T,
|
|
|
oStaticProperties);
|
|
|
if(!bSucceeded && !isCancelRequested() && m_sFailureReason.isEmpty()) {
|
|
|
fail(tr("The solver result data is invalid."));
|
|
|
}
|
|
|
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,
|
|
|
nmPebiStaticPropertyValues& oStaticProperties)
|
|
|
{
|
|
|
if(m_pInputSnapshot == nullptr) {
|
|
|
return fail(tr("The solver result data is invalid."));
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
if(m_pInputSnapshot->m_bRateForecast) {
|
|
|
// 预测允许单段产量和全程关井,不依赖历史流动段或试井诊断曲线。
|
|
|
const nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput;
|
|
|
int target = -1;
|
|
|
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
|
|
|
if(grid.m_vecWellInputs[i].m_bRealWell &&
|
|
|
grid.m_vecWellInputs[i].m_sWellCode == m_sAutoFitTargetWellCode) target = i;
|
|
|
}
|
|
|
if(target < 0 || target >= static_cast<int>(p1.pw.size()) || p1.t.empty() ||
|
|
|
p1.pw[target].size() != p1.t.size()) {
|
|
|
return fail(tr("The solver returned incomplete forecast pressure data."));
|
|
|
}
|
|
|
QVector<QVector<double> > pressure(2);
|
|
|
if(p1.t[0] > 0.0) {
|
|
|
pressure[0].append(0.0);
|
|
|
pressure[1].append(grid.m_oScene.Base.Pi);
|
|
|
}
|
|
|
// 与普通多井求解一致,保留 DLL 返回的完整时间轴,不按当前井制度截断结果。
|
|
|
for(size_t i = 0; i < p1.t.size(); ++i) {
|
|
|
if(isCancelRequested()) return false;
|
|
|
if(!isFiniteSolverNumber(p1.t[i]) || p1.t[i] < 0.0 ||
|
|
|
(i > 0 && p1.t[i] <= p1.t[i - 1]) ||
|
|
|
!isFiniteSolverNumber(p1.pw[target][i]) || p1.pw[target][i] <= 0.0) {
|
|
|
return fail(tr("Invalid forecast pressure or time. Check the rate schedule and model."));
|
|
|
}
|
|
|
pressure[0].append(p1.t[i]);
|
|
|
pressure[1].append(p1.pw[target][i]);
|
|
|
}
|
|
|
m_autoFitResultPressure = pressure;
|
|
|
return true;
|
|
|
}
|
|
|
// p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure.
|
|
|
// 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力.
|
|
|
const bool usePseudoPressure = (modelType == static_cast<int>(SMT_Gas_VariablePvt));
|
|
|
const bool useInputPseudoPressure = usePseudoPressure &&
|
|
|
m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0;
|
|
|
CalPseudoPressure calPseudoPressure = usePseudoPressure && !useInputPseudoPressure
|
|
|
? getPseudoPressureConverter()
|
|
|
: nullptr;
|
|
|
|
|
|
if(usePseudoPressure && !useInputPseudoPressure && calPseudoPressure == nullptr) {
|
|
|
qWarning() << "Failed to load the gas pseudo-pressure converter.";
|
|
|
return fail(tr("Failed to load the gas pseudo-pressure converter."));
|
|
|
}
|
|
|
|
|
|
// 井顺序与后处理输入都来自任务启动时的同一份快照。
|
|
|
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 fail(tr("The solver returned incomplete result data."));
|
|
|
}
|
|
|
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 fail(tr("The solver returned invalid time data."));
|
|
|
}
|
|
|
}
|
|
|
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 fail(tr("The solver returned incomplete well pressure data."));
|
|
|
}
|
|
|
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 fail(tr("The solver returned invalid well pressure data."));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历每口井,处理其数据
|
|
|
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 QVector<double>* pReferenceRates =
|
|
|
referenceRatesOf(oWellInput);
|
|
|
const bool bRateControlled =
|
|
|
oWellInput.m_bRateControlled &&
|
|
|
pReferenceRates != nullptr &&
|
|
|
pReferenceRates->size() ==
|
|
|
oWellInput.m_vecFlowDurations.size() &&
|
|
|
!oWellInput.m_vecFlowDurations.isEmpty() &&
|
|
|
oWellInput.m_nFlowSectionIndex >= 1 &&
|
|
|
oWellInput.m_nFlowSectionIndex <=
|
|
|
oWellInput.m_vecFlowDurations.size();
|
|
|
|
|
|
vvecLogLog.clear();
|
|
|
vvecSemiLog.clear();
|
|
|
|
|
|
if(bRateControlled) {
|
|
|
// 准备传给 HX_NWTM_PLOT 的压力或气井拟压力序列。
|
|
|
QVector<double> vecPressureForPlot;
|
|
|
vecPressureForPlot.reserve(
|
|
|
static_cast<int>(p1.pw[wellIdx].size()));
|
|
|
|
|
|
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
|
|
|
if((i % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
// 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力.
|
|
|
double pressureForLog = p1.pw[wellIdx][i];
|
|
|
if(usePseudoPressure
|
|
|
&& (!isFiniteSolverNumber(pressureForLog)
|
|
|
// 后台任务使用本次输入中的转换表,避免其他窗口改写全局配置。
|
|
|
|| !(useInputPseudoPressure
|
|
|
? inputPseudoPressure(
|
|
|
m_pInputSnapshot->m_oGridInput.m_oScene, pressureForLog, pressureForLog)
|
|
|
: 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 fail(tr("Failed to convert gas pressure to pseudo-pressure."));
|
|
|
}
|
|
|
vecPressureForPlot.append(pressureForLog);
|
|
|
}
|
|
|
|
|
|
// HX_NWTM.dll 直接返回双对数三列和半对数两列,
|
|
|
// 数值后处理不再加载旧曲线库。
|
|
|
bool bHxPlotInterfaceAvailable = false;
|
|
|
QString sHxPlotError;
|
|
|
const bool bCalculatedByHxPlot =
|
|
|
nmDataUtils::calculateHxNwtmPlot(
|
|
|
oWellInput.m_vecFlowDurations,
|
|
|
*pReferenceRates,
|
|
|
currentWellTime,
|
|
|
vecPressureForPlot,
|
|
|
oWellInput.m_nFlowSectionIndex,
|
|
|
m_pInputSnapshot->m_oGridInput.m_sLicensePath,
|
|
|
vvecLogLog,
|
|
|
vvecSemiLog,
|
|
|
&bHxPlotInterfaceAvailable,
|
|
|
&sHxPlotError);
|
|
|
if(!bCalculatedByHxPlot) {
|
|
|
// 旧曲线库已下线,接口缺失或计算失败时直接返回,
|
|
|
// 避免继续使用空曲线或上一轮计算残留的数据。
|
|
|
qWarning() << (bHxPlotInterfaceAvailable
|
|
|
? "HX_NWTM_PLOT failed for well"
|
|
|
: "HX_NWTM_PLOT is not exported for well")
|
|
|
<< oWellRef.m_sWellCode << ":" << sHxPlotError;
|
|
|
return fail(tr("Failed to calculate pressure curves."));
|
|
|
}
|
|
|
if(isCancelRequested()) {
|
|
|
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;
|
|
|
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
|
|
|
? true
|
|
|
: fail(tr("The calculated pressure curves are invalid."));
|
|
|
}
|
|
|
|
|
|
// 第四步:完整求解只接受携带有效 OriginalCellId 的冻结基础网格。
|
|
|
// 场压力按同一映射过滤,不能再根据 isplot 顺序推测 VTK Cell 身份。
|
|
|
if(p1.p.size() != p1.t.size() ||
|
|
|
m_pInputSnapshot->m_pBaseGrid == nullptr ||
|
|
|
m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells() <= 0) {
|
|
|
return fail(tr("The solver returned incomplete field pressure data."));
|
|
|
}
|
|
|
QVector<vtkIdType> vecVtkToOriginal;
|
|
|
QVector<vtkIdType> vecOriginalToVtk;
|
|
|
QString sMappingError;
|
|
|
if(nmPebiCellMapping::read(m_pInputSnapshot->m_pBaseGrid,
|
|
|
&vecVtkToOriginal, &vecOriginalToVtk, &sMappingError) !=
|
|
|
nmPebiCellMapping::MappingStatus_Valid ||
|
|
|
vecVtkToOriginal.isEmpty() || vecOriginalToVtk.isEmpty() ||
|
|
|
vecVtkToOriginal.size() !=
|
|
|
m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells()) {
|
|
|
qWarning() << "Invalid PEBI result cell mapping:" << sMappingError;
|
|
|
return fail(tr("The generated grid is invalid."));
|
|
|
}
|
|
|
const int nOriginalCellCount = vecOriginalToVtk.size();
|
|
|
const int nVisibleCellCount = vecVtkToOriginal.size();
|
|
|
for(size_t nTimeIndex = 0; nTimeIndex < p1.p.size(); ++nTimeIndex) {
|
|
|
if(isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
if(p1.p[nTimeIndex].size() !=
|
|
|
static_cast<size_t>(nOriginalCellCount)) {
|
|
|
qWarning() << "Incomplete field pressure returned for time index:"
|
|
|
<< static_cast<int>(nTimeIndex);
|
|
|
return fail(tr("The solver returned incomplete field pressure data."));
|
|
|
}
|
|
|
for(int nVtkCellId = 0;
|
|
|
nVtkCellId < nVisibleCellCount; ++nVtkCellId) {
|
|
|
if((nVtkCellId % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const vtkIdType nOriginalCellId =
|
|
|
vecVtkToOriginal[nVtkCellId];
|
|
|
if(nOriginalCellId < 0 ||
|
|
|
nOriginalCellId >= nOriginalCellCount ||
|
|
|
!isFiniteSolverNumber(p1.p[nTimeIndex][
|
|
|
static_cast<size_t>(nOriginalCellId)])) {
|
|
|
qWarning() << "PEBI returned a non-finite field pressure at time/cell:"
|
|
|
<< static_cast<int>(nTimeIndex)
|
|
|
<< static_cast<int>(nOriginalCellId);
|
|
|
return fail(tr("The solver returned invalid field pressure data."));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第五步:在局部时间步映射中构造场压力,同时计算全局标量范围。
|
|
|
// 此处不触碰 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"); // 设置标量数据的名称
|
|
|
|
|
|
pressureData->SetNumberOfComponents(1);
|
|
|
pressureData->SetNumberOfValues(nVisibleCellCount);
|
|
|
|
|
|
double dCurrentTimeMin = DBL_MAX; // 当前时间步的最小值
|
|
|
double dCurrentTimeMax = -DBL_MAX; // 当前时间步的最大值
|
|
|
|
|
|
for(int nVtkCellId = 0;
|
|
|
nVtkCellId < nVisibleCellCount; ++nVtkCellId) {
|
|
|
if((nVtkCellId % 256) == 0 && isCancelRequested()) {
|
|
|
return false;
|
|
|
}
|
|
|
const vtkIdType nOriginalCellId =
|
|
|
vecVtkToOriginal[nVtkCellId];
|
|
|
const double dPressure = p1.p[timeIdx][
|
|
|
static_cast<size_t>(nOriginalCellId)];
|
|
|
pressureData->SetValue(nVtkCellId, dPressure);
|
|
|
dCurrentTimeMin = qMin(dCurrentTimeMin, dPressure);
|
|
|
dCurrentTimeMax = qMax(dCurrentTimeMax, dPressure);
|
|
|
}
|
|
|
|
|
|
// 更新全局范围
|
|
|
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 fail(tr("The solver returned invalid time data."));
|
|
|
}
|
|
|
|
|
|
// 第六步:基础网格在任务创建或局部建网阶段已经完成深拷贝。后台结果构造
|
|
|
// 不再读取 DataManager 中可能被网格窗口替换的 VTK 指针。
|
|
|
if(m_pInputSnapshot->m_pBaseGrid == nullptr ||
|
|
|
m_pInputSnapshot->m_pBaseGrid->GetNumberOfCells() <= 0) {
|
|
|
return fail(tr("The prepared result grid is invalid."));
|
|
|
}
|
|
|
|
|
|
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 fail(tr("The solver returned incomplete well pressure data."));
|
|
|
}
|
|
|
|
|
|
m_dPendingScalarMin = dMinP;
|
|
|
m_dPendingScalarMax = dMaxP;
|
|
|
m_bPendingFullResultReady =
|
|
|
buildPebiResultSnapshotCandidate(oStaticProperties);
|
|
|
if(!m_bPendingFullResultReady) {
|
|
|
// 候选结构或内存校验失败时立即释放大结果,旧快照保持原状。
|
|
|
fail(tr("Failed to build the result snapshot."));
|
|
|
discardPendingFullResult();
|
|
|
}
|
|
|
return m_bPendingFullResultReady;
|
|
|
}
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::discardPendingFullResult()
|
|
|
{
|
|
|
m_pPendingResultSnapshot.clear();
|
|
|
m_vecPendingWellResults.clear();
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
m_autoFitResultPressure.clear();
|
|
|
m_autoFitResultLogLog.clear();
|
|
|
m_autoFitResultSemiLog.clear();
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
|
// 候选可能与输入基础网格共享 VTK 对象,丢弃或发布后必须清除任务侧别名。
|
|
|
if(m_pInputSnapshot != NULL) {
|
|
|
m_pInputSnapshot->m_pBaseGrid = NULL;
|
|
|
m_pInputSnapshot->m_pSourceBaseGrid = NULL;
|
|
|
m_pInputSnapshot->m_oGridResult.m_pUnstructuredGrid = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate(
|
|
|
nmPebiStaticPropertyValues& oStaticProperties)
|
|
|
{
|
|
|
if(m_pInputSnapshot == NULL ||
|
|
|
m_pInputSnapshot->m_bAutoFitTargetOnly ||
|
|
|
!m_pInputSnapshot->m_bResultMetadataCaptured ||
|
|
|
m_pInputSnapshot->m_pBaseGrid == NULL ||
|
|
|
m_mapPendingTimeSteps.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
nmPebiResultSnapshotBuilder oBuilder;
|
|
|
if(!oBuilder.setInputRevisions(m_nGridInputRevision,
|
|
|
m_nResultInputRevision)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Builder 直接接管任务输入中的基础网格引用,不再为旧结果流程创建
|
|
|
// 第二套网格壳。输入快照残留的其他别名会在提交或丢弃边界统一清除。
|
|
|
if(!oBuilder.takeResultGrid(m_pInputSnapshot->m_pBaseGrid)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(!oBuilder.takeStaticProperties(oStaticProperties)) {
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
// 压力帧的规范引用已经进入 Builder,立即释放任务侧映射,降低发布前峰值。
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
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;
|
|
|
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;
|
|
|
oSlot.m_eWellCategory = oWellInput.m_eWellCategory;
|
|
|
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_eWellCategory = oWellInput.m_eWellCategory;
|
|
|
oWell.m_eWellMode = oWellInput.m_bRateControlled
|
|
|
? NM_CaseWell_RateControlled
|
|
|
: NM_CaseWell_Observation;
|
|
|
oWell.m_sGaugeInputSha1 = oWellInput.m_sGaugeInputSha1;
|
|
|
if(oWellInput.m_bRateControlled) {
|
|
|
// 结果切井必须显示本轮求解实际使用的制度,不能重新读取实时井。
|
|
|
oWell.m_eFlowSchedulePhase =
|
|
|
oWellInput.m_eFlowSchedulePhase;
|
|
|
oWell.m_nFlowSectionIndex =
|
|
|
oWellInput.m_nFlowSectionIndex;
|
|
|
oWell.m_bUseOilRate = oWellInput.m_bUseOilRate;
|
|
|
oWell.m_bUseGasRate = oWellInput.m_bUseGasRate;
|
|
|
oWell.m_bUseWaterRate = oWellInput.m_bUseWaterRate;
|
|
|
qSwap(oWell.m_vecFlowDurations,
|
|
|
oWellInput.m_vecFlowDurations);
|
|
|
qSwap(oWell.m_vecOilRates,
|
|
|
oWellInput.m_vecOilRates);
|
|
|
qSwap(oWell.m_vecGasRates,
|
|
|
oWellInput.m_vecGasRates);
|
|
|
qSwap(oWell.m_vecWaterRates,
|
|
|
oWellInput.m_vecWaterRates);
|
|
|
}
|
|
|
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);
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
m_pPendingResultSnapshot = oBuilder.takeCandidate();
|
|
|
if(m_pPendingResultSnapshot.isNull()) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
} catch(const std::bad_alloc&) {
|
|
|
qWarning() << "Cannot allocate PEBI result snapshot candidate.";
|
|
|
fail(tr("Insufficient memory to build the result snapshot."));
|
|
|
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 fail(tr("Failed to commit the solver results."));
|
|
|
}
|
|
|
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
pDataManager->getNumericalAnalysisCase();
|
|
|
if(pAnalysisCase == nullptr) {
|
|
|
discardPendingFullResult();
|
|
|
return fail(tr("Failed to commit the solver results."));
|
|
|
}
|
|
|
if(pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision) {
|
|
|
discardPendingFullResult();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:任务后台生成了新网格时,先在当前主线程按同一输入版本提交。
|
|
|
// 已有网格路径则再次确认其仍然有效。两条路径都不允许旧任务覆盖新编辑。
|
|
|
if(m_pInputSnapshot->m_bGridResultNeedsCommit) {
|
|
|
nmCalculationPebiGrid* pGridService =
|
|
|
nmCalculationPebiGrid::getInstance();
|
|
|
if(pGridService == nullptr ||
|
|
|
!pGridService->commitSnapshotResult(
|
|
|
pDataManager,
|
|
|
m_pInputSnapshot->m_oGridInput,
|
|
|
m_pInputSnapshot->m_oGridResult)) {
|
|
|
discardPendingFullResult();
|
|
|
return fail(tr("Failed to commit the generated grid."));
|
|
|
}
|
|
|
} else if(!pAnalysisCase->isGridValid()) {
|
|
|
discardPendingFullResult();
|
|
|
return fail(tr("The generated grid is no longer valid."));
|
|
|
}
|
|
|
|
|
|
// 第三步:Manager 在唯一发布点校验 UUID、槽位和版本后替换规范快照。
|
|
|
QString sError;
|
|
|
const bool bCommitted = pDataManager->commitPebiResultSnapshot(
|
|
|
m_pPendingResultSnapshot,
|
|
|
&sError);
|
|
|
if(!bCommitted) {
|
|
|
qWarning() << "Cannot commit PEBI result snapshot:" << sError;
|
|
|
fail(tr("Failed to commit the solver results."));
|
|
|
}
|
|
|
|
|
|
// 成功和失败都不允许任务继续持有候选可写别名;旧快照只由 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)
|
|
|
//
|
|
|
// // 将计算结果保存到对应的井数据里
|
|
|
// // 压力
|
|
|
// // 双对数
|
|
|
// // 半对数
|
|
|
//
|
|
|
// // 存储当前井名称和二维位置到映射
|
|
|
// 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;
|
|
|
}
|