You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverT...

1816 lines
73 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmCalculationDllPebiSolverTask.h"
#include "singlePhaseSolver.h"
#include "zxLogInstance.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmDataVerticalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataReservoir.h"
#include "nmDataAttribute.h"
#include "nmDataRegion.h"
#include "nmDataRegionMark.h"
#include "nmDataOutline.h"
#include "nmDataFracture.h"
#include "nmDataFault.h"
#include "nmDataTimeStepSetting.h"
#include "nmCalculationPebiGrid.h"
#include "nmCalculationUtils.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataPvtParaForPebi.h"
#include <QDebug>
#include <QSet>
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <algorithm>
#include <stdexcept>
#include <cmath>
#include <float.h>
#include <string>
#include <vtkSmartPointer.h>
#include <vtkDoubleArray.h>
namespace {
// PVT数组读到时才覆盖读不到时保留pch.h中的默认参数
void assignPvtVectorIfNotEmpty(dVec1& target, const QVector<double>& source)
{
if(!source.isEmpty()) {
target = dVec1(source.toStdVector());
}
}
bool isFiniteSolverNumber(double value)
{
#ifdef _MSC_VER
return _finite(value) != 0;
#else
return std::isfinite(value);
#endif
}
// 将启用的数据组插值到全部网格单元中心,并覆盖对应的求解器属性数组.
bool applyPropertyInterpolation(
HX_NWTM_MODEL_INPUT& modelInput,
const QVector<nmPropertyInterpolationDataSet>& dataSets,
const QString& licensePath,
QString& errorMessage)
{
bool hasEnabledDataSet = false;
for(int i = 0; i < dataSets.size(); ++i) {
if(dataSets[i].useForCalculation) {
hasEnabledDataSet = true;
break;
}
}
if(!hasEnabledDataSet) {
return true;
}
QVector<QPointF> targetPoints;
targetPoints.reserve(static_cast<int>(modelInput.GRID.Trinodexy.size()));
for(size_t cellIndex = 0;
cellIndex < modelInput.GRID.Trinodexy.size(); ++cellIndex) {
const dVec1& cellPosition = modelInput.GRID.Trinodexy[cellIndex];
if(cellPosition.size() < 2) {
errorMessage = QString("Grid cell %1 has no valid center coordinate.")
.arg(static_cast<qulonglong>(cellIndex));
return false;
}
targetPoints.append(QPointF(cellPosition[0], cellPosition[1]));
}
bool kApplied = false;
bool phiApplied = false;
bool hApplied = false;
for(int dataSetIndex = 0; dataSetIndex < dataSets.size(); ++dataSetIndex) {
const nmPropertyInterpolationDataSet& dataSet = dataSets[dataSetIndex];
if(!dataSet.useForCalculation) {
continue;
}
dVec1* solverValues = NULL;
bool* propertyApplied = NULL;
if(dataSet.property == "k") {
solverValues = &modelInput.Base.k;
propertyApplied = &kApplied;
} else if(dataSet.property == "phi") {
solverValues = &modelInput.Base.phi;
propertyApplied = &phiApplied;
} else if(dataSet.property == "h") {
solverValues = &modelInput.Base.h;
propertyApplied = &hApplied;
} else {
errorMessage = QString("Dataset '%1' has an unknown property.")
.arg(dataSet.name);
return false;
}
if(*propertyApplied) {
errorMessage = QString(
"More than one dataset is enabled for property %1.")
.arg(dataSet.property);
return false;
}
QVector<QPointF> measurementPoints;
QVector<double> measurementValues;
measurementPoints.reserve(dataSet.points.size());
measurementValues.reserve(dataSet.points.size());
for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) {
const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex];
measurementPoints.append(QPointF(point.x, point.y));
measurementValues.append(point.value);
}
QVector<double> interpolationValues;
QString calculationError;
if(!nmCalculationUtils::calculateKriging(
targetPoints,
measurementPoints,
measurementValues,
dataSet.nugget,
dataSet.sill,
dataSet.range,
dataSet.model,
licensePath,
interpolationValues,
&calculationError)) {
errorMessage = QString("Dataset '%1': %2")
.arg(dataSet.name)
.arg(calculationError);
return false;
}
solverValues->resize(interpolationValues.size());
for(int valueIndex = 0; valueIndex < interpolationValues.size(); ++valueIndex) {
// 属性插值数据层以mD保存渗透率在写入PEBI数组时转换为D。
(*solverValues)[valueIndex] = dataSet.property == "k"
? nmCalculationUtils::milliDarcyToDarcy(
interpolationValues[valueIndex])
: interpolationValues[valueIndex];
}
*propertyApplied = true;
}
return true;
}
bool isReasonableLogLogValue(double value)
{
const double maxReasonableAbsValue = 1.0e12;
return isFiniteSolverNumber(value)
&& value >= -maxReasonableAbsValue
&& value <= maxReasonableAbsValue;
}
bool isValidLogLogPoint(const Point& pt)
{
return isReasonableLogLogValue(pt.x)
&& isReasonableLogLogValue(pt.y)
&& isReasonableLogLogValue(pt.z)
&& pt.x > 0.0
&& pt.z >= DBL_EPSILON;
}
bool isValidSemiLogPoint(const Point& pt)
{
return isReasonableLogLogValue(pt.x)
&& pt.x > 0.0
&& !pt.pointData.empty()
&& isReasonableLogLogValue(pt.pointData[0]);
}
typedef bool (*CalPseudoPressure)(double, double&, int);
// mAlgPseudo.dll 是主界面拟压力算法所在模块.
// 此处直接解析 iAlgPseuCaller::calPS 的导出符号, 复用主界面的同一套转换实现.
// 函数参数依次为原始压力、输出拟压力和拟压力分区编号.
CalPseudoPressure getPseudoPressureConverter()
{
// 静态保存模块和函数地址, 避免每个粒子、每个压力点重复加载和解析 DLL.
// configPsAbouts() 初始化的数据及模式也保存在同一个 mAlgPseudo.dll 模块中.
static HMODULE module = LoadLibraryW(L"mAlgPseudo.dll");
static CalPseudoPressure converter = module
? reinterpret_cast<CalPseudoPressure>(GetProcAddress(
module, "?calPS@iAlgPseuCaller@@SA_NNAANH@Z"))
: nullptr;
return converter;
}
/// @brief 将常数PVT单值扩展为200元素数组PEBI求解器要求数组输入
/// @param dValue 常数PVT值
/// @param nSize 数组大小默认200
/// @return 包含nSize个dValue元素的dVec1数组
dVec1 expandConstPvt(double dValue, int nSize = 200)
{
return dVec1(nSize, dValue);
}
/// @brief 生成线性占位压力数组常数PVT无曲线数据时使用
/// @param nSize 数组大小默认200
/// @return [1, 2, ..., nSize] 的 dVec1 数组
dVec1 generateDummyPressure(int nSize = 200)
{
dVec1 vecP(nSize, 0);
for(int i = 0; i < nSize; ++i) { vecP[i] = (i + 1.0); }
return vecP;
}
/// @brief 根据求解器模型类型填充PVT输入数据
/// 常数PVT模型从 reservoir 读单值并扩展为200元素数组
/// 变化PVT模型从 pebiPvtPara 读曲线数组
/// @param input PEBI求解器输入结构
/// @param eModelType 求解器模型类型
/// @param pPvt PEBI PVT参数对象变化PVT时提供曲线数组
/// @param pRes 油藏数据对象常数PVT时提供单值
void fillPvtInputByModel(HX_NWTM_MODEL_INPUT& input,
NM_SOLVER_MODEL_TYPE eModelType,
nmDataPvtParaForPebi* pPvt,
nmDataReservoir* pRes)
{
switch(eModelType) {
case SMT_Oil_ConstPvt:
// 油相常数PVT不依赖pPvt生成占位压力数组 + 单值扩展为200元素数组
input.PVT.p = generateDummyPressure();
if(pRes != nullptr) {
input.PVT.Bo = expandConstPvt(pRes->getBo().getValue().toDouble());
input.PVT.miuo = expandConstPvt(pRes->getMiuo().getValue().toDouble());
}
break;
case SMT_Oil_VariablePvt:
// 油相变化PVT从 pebiPvtPara 读曲线数组
if(pPvt != nullptr) {
assignPvtVectorIfNotEmpty(input.PVT.p, pPvt->getPressure());
assignPvtVectorIfNotEmpty(input.PVT.Bo, pPvt->getBo());
assignPvtVectorIfNotEmpty(input.PVT.Co, pPvt->getCo());
assignPvtVectorIfNotEmpty(input.PVT.miuo, pPvt->getMiuo());
}
break;
case SMT_Water_ConstPvt:
// 水相常数PVT不依赖pPvt生成占位压力数组 + 单值扩展为200元素数组
input.PVT.p = generateDummyPressure();
if(pRes != nullptr) {
input.PVT.Bw = expandConstPvt(pRes->getBw().getValue().toDouble());
input.PVT.miuw = expandConstPvt(pRes->getMiuw().getValue().toDouble());
}
break;
case SMT_Water_VariablePvt:
// 水相变化PVT从 pebiPvtPara 读曲线数组
if(pPvt != nullptr) {
assignPvtVectorIfNotEmpty(input.PVT.p, pPvt->getPressure());
assignPvtVectorIfNotEmpty(input.PVT.Bw, pPvt->getBw());
assignPvtVectorIfNotEmpty(input.PVT.Cw, pPvt->getCw());
assignPvtVectorIfNotEmpty(input.PVT.miuw, pPvt->getMiuw());
}
break;
case SMT_Gas_VariablePvt:
case SMT_Gas_PseudoPressure:
// 气相变化PVT/拟压力:从 pebiPvtPara 读曲线数组
if(pPvt != nullptr) {
assignPvtVectorIfNotEmpty(input.PVT.p, pPvt->getPressure());
assignPvtVectorIfNotEmpty(input.PVT.Bg, pPvt->getBg());
assignPvtVectorIfNotEmpty(input.PVT.Cg, pPvt->getCg());
assignPvtVectorIfNotEmpty(input.PVT.miug, pPvt->getMiug());
}
break;
case SMT_Oil_Water_TwoPhase:
// 油水两相:从 pebiPvtPara 读油+水曲线数组 + 相渗数据
if(pPvt != nullptr) {
assignPvtVectorIfNotEmpty(input.PVT.p, pPvt->getPressure());
assignPvtVectorIfNotEmpty(input.PVT.Bo, pPvt->getBo());
assignPvtVectorIfNotEmpty(input.PVT.miuo, pPvt->getMiuo());
assignPvtVectorIfNotEmpty(input.PVT.Bw, pPvt->getBw());
assignPvtVectorIfNotEmpty(input.PVT.miuw, pPvt->getMiuw());
// 相渗与饱和度数据PEBI油水两相不需要PVT.Sw数组初始含水饱和度通过Base.Swi传递
assignPvtVectorIfNotEmpty(input.PVT.So, pPvt->getSo());
assignPvtVectorIfNotEmpty(input.PVT.Kro, pPvt->getKro());
assignPvtVectorIfNotEmpty(input.PVT.Krw, pPvt->getKrw());
}
break;
default:
break;
}
}
}
nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
QString sPostprocessingDir,
nmDataAnalyzeManager* pDataManager,
QObject *parent):
QThread(parent),
m_sPostprocessingDir(sPostprocessingDir),
m_pDataManager(pDataManager != nullptr
? pDataManager
: nmDataAnalyzeManager::getCurrentInstance()),
m_bManagerUseActive(false),
m_nGridInputRevision(0),
m_nResultInputRevision(0),
m_lastRunSucceeded(false),
m_nSolveTimeMs(-1),
m_nPebiCount(-1),
m_dPendingScalarMin(0.0),
m_dPendingScalarMax(0.0),
m_bPendingFullResultReady(false)
{
// 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。
if(m_pDataManager != nullptr) {
m_pDataManager->beginBackgroundUse();
m_bManagerUseActive = true;
}
// 第二步:任务创建时冻结两个输入版本,提交前还会再次核对。
if(m_pDataManager != nullptr &&
m_pDataManager->getNumericalAnalysisCase() != nullptr) {
m_nGridInputRevision =
m_pDataManager->getNumericalAnalysisCase()->getGridInputRevision();
m_nResultInputRevision =
m_pDataManager->getNumericalAnalysisCase()->getResultInputRevision();
}
}
nmCalculationDllPebiSolverTask::~nmCalculationDllPebiSolverTask()
{
// Qt 4.8 没有可靠的外部 DLL 协作取消接口。强制 terminate() 可能让 DLL、
// VTK 或 STL 对象停在持锁/半析构状态,因此析构只能等待 run() 正常退出。
if(isRunning()) {
wait();
}
releaseDataManagerUse();
}
void nmCalculationDllPebiSolverTask::run()
{
m_nSolveTimeMs = -1;
m_nPebiCount = -1;
m_lastRunSucceeded = this->execute();
// 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。
releaseDataManagerUse();
emit sig_calculateDone(m_lastRunSucceeded);
}
void nmCalculationDllPebiSolverTask::releaseDataManagerUse()
{
if(m_bManagerUseActive && m_pDataManager != nullptr) {
m_pDataManager->endBackgroundUse();
m_bManagerUseActive = false;
}
}
bool nmCalculationDllPebiSolverTask::wasSuccessful() const
{
return m_lastRunSucceeded;
}
int nmCalculationDllPebiSolverTask::getSolveTimeMs() const
{
return m_nSolveTimeMs;
}
int nmCalculationDllPebiSolverTask::getPebiCount() const
{
return m_nPebiCount;
}
void nmCalculationDllPebiSolverTask::setAutoFitTargetWell(const QString& wellName)
{
nmDataWellBase* pWellData = m_pDataManager != nullptr
? m_pDataManager->findWellByName(wellName) : nullptr;
m_sAutoFitTargetWellCode = pWellData != nullptr
? pWellData->getWellCode() : QString();
}
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 this->execPebiMode();
}
bool nmCalculationDllPebiSolverTask::execPebiMode()
{
nmDataAnalyzeManager* pDataInstance = m_pDataManager;
nmCalculationPebiGrid* pGridInstance = nmCalculationPebiGrid::getInstance();
if(pDataInstance == nullptr || pGridInstance == nullptr) {
return false;
}
// 第一步:任务排队期间输入已经变化时直接作废,不启动一次注定过期的 DLL 求解。
const nmDataNumericalAnalysisCase* pAnalysisCase =
pDataInstance->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr ||
pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision) {
return false;
}
// 旧结果在新结果完整生成并通过主线程版本校验前保持不变。失败任务只丢弃
// 自己的局部快照,不能提前清空上一轮仍然完整的成果。
m_bPendingFullResultReady = false;
m_vecPendingWellResults.clear();
m_mapPendingTimeSteps.clear();
m_pPendingResultGrid = nullptr;
// 载入Pebi动态库
HMODULE dll = LoadLibrary(L"HX_NWTM.dll");
if(dll) {
// 定义函数指针类型
typedef void (*HX_NWTM_GRID_Func)(HX_NWTM_GRID_OUTPUT1&, HX_NWTM_GRID_OUTPUT2&, const HX_NWTM_GRID_INPUT&, std::string);
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 HX_NWTM_MODEL = (HX_NWTM_MODEL_Fun)GetProcAddress(dll, "HX_NWTM_MODEL");
SetIntValueFunc setSolverType = (SetIntValueFunc)GetProcAddress(dll, "set_solvetype");
SetIntValueFunc setOmpThreads = (SetIntValueFunc)GetProcAddress(dll, "set_omp_threads");
SetIntValueFunc setIluReuseSteps = (SetIntValueFunc)GetProcAddress(dll, "set_ilu_reuse_steps");
GetIntValueFunc getSolveTime = (GetIntValueFunc)GetProcAddress(dll, "getsolvetime");
if(HX_NWTM_MODEL == nullptr || setSolverType == nullptr
|| setOmpThreads == nullptr || setIluReuseSteps == nullptr
|| getSolveTime == nullptr) {
QString sLogMessage = "Failed to resolve PEBI solver configuration interface.";
qWarning() << sLogMessage;
zxLogInstance::getInstance()->writeLogF(sLogMessage);
FreeLibrary(dll);
return false;
}
if(HX_NWTM_MODEL) {
HX_NWTM_GRID_OUTPUT1 oGridOutput1;
HX_NWTM_GRID_OUTPUT2 oGridOutput2;
int nGridPebiCount = -1;
// 第一步:网格无效时按当前分析方案完整重建 DLL 输出和 VTK 网格,
// 不能只生成 p1/p2 后把缺少 VTK 对应关系的半成品交给求解器。
if(!pGridInstance->isGridCurrentFor(pDataInstance)) {
if(!pGridInstance->meshGenPebi(pDataInstance)) {
FreeLibrary(dll);
return false;
}
}
// 第二步:在同一把网格锁内复制完整快照,后续求解不再读取可变单例输出。
if(!pGridInstance->copyCurrentGridFor(pDataInstance,
oGridOutput1,
oGridOutput2,
nGridPebiCount)) {
FreeLibrary(dll);
return false;
}
// 参数定义,使用默认参数
HX_NWTM_MODEL_OUTPUT p1;
HX_NWTM_MODEL_INPUT p0(oGridOutput2);
nmDataReservoir* pReservoirData = pDataInstance->getReservoirData();
// 根据PVT参数获取结果选择求解器模型类型
const int modelType = static_cast<int>(pDataInstance->getSolverModelType());
p0.T = modelType;
// 获取PEBI网格存入井的顺序
QVector<nmSolverWellRef> vecWellsOrder =
pDataInstance->getSolverWellOrder();
p0.CS.C.resize(vecWellsOrder.size());
p0.CS.S.resize(vecWellsOrder.size());
p0.Rate.t.resize(vecWellsOrder.size());
p0.Rate.qo.resize(vecWellsOrder.size());
p0.Rate.qw.resize(vecWellsOrder.size());
p0.Rate.qg.resize(vecWellsOrder.size());
// 遍历井数据
for(int i = 0; i < vecWellsOrder.size(); ++i) {
const nmSolverWellRef& oWellRef = vecWellsOrder[i];
NM_WELL_MODEL wellType = oWellRef.m_eWellType;
// 手工裂缝只占据 DLL 数组下标,不具有井筒参数和产量制度。
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
// 清空裂缝的数据,因为头文件内部有五口井的数据,会传递给裂缝
p0.CS.C[i] = 0;
p0.CS.S[i] = 0;
p0.Rate.t[i].clear();
p0.Rate.qo[i].clear();
p0.Rate.qw[i].clear();
p0.Rate.qg[i].clear();
continue;
}
nmDataWellBase* pBaseWellData =
pDataInstance->findWellByCode(oWellRef.m_sWellCode);
if(pBaseWellData == nullptr) {
qWarning() << "Solver well code is missing from Map:"
<< oWellRef.m_sWellCode;
FreeLibrary(dll);
return false;
}
// 主动井必须有至少一个去除零点后的产量段;观察井允许完全没有产量制度。
const NM_CASE_WELL_MODE eWellMode =
pDataInstance->getCalculationWellMode(
oWellRef.m_sWellCode);
if(eWellMode == NM_CaseWell_RateControlled &&
pBaseWellData->getFlowPoints().size() < 2) {
qWarning() << "Rate-controlled well has no valid rate schedule:"
<< oWellRef.m_sWellCode;
FreeLibrary(dll);
return false;
}
// 处理直井
if(wellType == NM_WELL_MODEL::Vertical_Well) {
nmDataVerticalWell* pWellData =
dynamic_cast<nmDataVerticalWell*>(pBaseWellData);
if(pWellData == nullptr) {
return false;
}
// 井筒储集系数
p0.CS.C[i] = pWellData->getWellboreStorage().getValue().toDouble();
// 表皮系数
//p0.CS.S[i] = pWellData->getSkin().getValue().toDouble();
// 获取第一段射孔的表皮系数
p0.CS.S[i] = pWellData->getPerforation(0)->getSkin().getValue().toDouble();
// 流量数据
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
// 分离时间(x)和流量(y)数据
QVector<double> timeDataF;
QVector<double> rateData;
foreach(const QPointF& point, vecTimeQ) {
timeDataF.append(point.x()); // 时间数据
rateData.append(point.y()); // 流量数据
}
// 删除 timeDataF 中的第一个元素
if(!timeDataF.isEmpty()) {
timeDataF.remove(0);
}
// 删除 rateData 中的第一个元素
if(!rateData.isEmpty()) {
rateData.remove(0);
}
//timeDataF.append(2000); // 时间数据
//rateData.append(2000); // 流量数据
// TODO:根据井类型来分类,现在将油气水三个流量设置为一致
// 油井 - 填充到 qo
dVec1& timeValues = p0.Rate.t[i];
timeValues.resize(timeDataF.size());
std::copy(timeDataF.begin(), timeDataF.end(), timeValues.begin());
p0.Rate.qo[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qo[i].begin());
// 气井 - 填充到 qg
p0.Rate.qg[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qg[i].begin());
// 水井 - 填充到 qw
p0.Rate.qw[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qw[i].begin());
}
else if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* pWellData =
dynamic_cast<nmDataVerticalFracturedWell*>(pBaseWellData);
if(pWellData == nullptr) {
return false;
}
// 井筒储集系数
p0.CS.C[i] = pWellData->getWellboreStorage().getValue().toDouble();
// 表皮系数
//p0.CS.S[i] = pWellData->getSkin().getValue().toDouble();
// 获取第一段射孔的表皮系数
p0.CS.S[i] = pWellData->getPerforation(0)->getSkin().getValue().toDouble();
// 流量数据
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
// 分离时间(x)和流量(y)数据
QVector<double> timeDataF;
QVector<double> rateData;
foreach(const QPointF& point, vecTimeQ) {
timeDataF.append(point.x()); // 时间数据
rateData.append(point.y()); // 流量数据
}
// 删除 timeDataF 中的第一个元素
if(!timeDataF.isEmpty()) {
timeDataF.remove(0);
}
// 删除 rateData 中的第一个元素
if(!rateData.isEmpty()) {
rateData.remove(0);
}
//timeDataF.append(2000); // 时间数据
//rateData.append(2000); // 流量数据
// TODO:根据井类型来分类,现在将油气水三个流量设置为一致
// 油井 - 填充到 qo
dVec1& timeValues = p0.Rate.t[i];
timeValues.resize(timeDataF.size());
std::copy(timeDataF.begin(), timeDataF.end(), timeValues.begin());
p0.Rate.qo[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qo[i].begin());
// 气井 - 填充到 qg
p0.Rate.qg[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qg[i].begin());
// 水井 - 填充到 qw
p0.Rate.qw[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qw[i].begin());
}
else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* pWellData =
dynamic_cast<nmDataHorizontalFracturedWell*>(pBaseWellData);
if(pWellData == nullptr) {
return false;
}
// 井筒储集系数
p0.CS.C[i] = pWellData->getWellboreStorage().getValue().toDouble();
// 表皮系数
//p0.CS.S[i] = pWellData->getSkin().getValue().toDouble();
// 获取第一段射孔的表皮系数
p0.CS.S[i] = pWellData->getPerforation(0)->getSkin().getValue().toDouble();
// 流量数据
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
// 分离时间(x)和流量(y)数据
QVector<double> timeDataF;
QVector<double> rateData;
foreach(const QPointF& point, vecTimeQ) {
timeDataF.append(point.x()); // 时间数据
rateData.append(point.y()); // 流量数据
}
// 删除 timeDataF 中的第一个元素
if(!timeDataF.isEmpty()) {
timeDataF.remove(0);
}
// 删除 rateData 中的第一个元素
if(!rateData.isEmpty()) {
rateData.remove(0);
}
//timeDataF.append(2000); // 时间数据
//rateData.append(2000); // 流量数据
// TODO:根据井类型来分类,现在将油气水三个流量设置为一致
// 油井 - 填充到 qo
dVec1& timeValues = p0.Rate.t[i];
timeValues.resize(timeDataF.size());
std::copy(timeDataF.begin(), timeDataF.end(), timeValues.begin());
p0.Rate.qo[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qo[i].begin());
// 气井 - 填充到 qg
p0.Rate.qg[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qg[i].begin());
// 水井 - 填充到 qw
p0.Rate.qw[i].resize(rateData.size());
std::copy(rateData.begin(), rateData.end(), p0.Rate.qw[i].begin());
}
}
// 第三步:统一整理每个外层槽位的产量角色和相别。
// 上面的井型分支只负责提取一条原始产量曲线;这里集中决定它进入 qo/qg/qw
// 避免同一口井同时向三个相写入相同源汇项。
for(int nWellIndex = 0;
nWellIndex < vecWellsOrder.size();
++nWellIndex) {
const nmSolverWellRef& oWellRef = vecWellsOrder[nWellIndex];
const bool bObservationWell =
oWellRef.m_eEntryKind == NM_SolverEntry_Well &&
pDataInstance->getCalculationWellMode(
oWellRef.m_sWellCode) == NM_CaseWell_Observation;
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
bObservationWell) {
// 观察井和手工裂缝保留 Rate[i] 槽位,但不向压力方程提供源汇项。
p0.Rate.t[nWellIndex].clear();
p0.Rate.qo[nWellIndex].clear();
p0.Rate.qg[nWellIndex].clear();
p0.Rate.qw[nWellIndex].clear();
continue;
}
const dVec1 vecRate = p0.Rate.qo[nWellIndex];
p0.Rate.qo[nWellIndex].assign(vecRate.size(), 0.0);
p0.Rate.qg[nWellIndex].assign(vecRate.size(), 0.0);
p0.Rate.qw[nWellIndex].assign(vecRate.size(), 0.0);
switch(static_cast<NM_SOLVER_MODEL_TYPE>(modelType)) {
case SMT_Gas_VariablePvt:
case SMT_Gas_PseudoPressure:
p0.Rate.qg[nWellIndex] = vecRate;
break;
case SMT_Water_ConstPvt:
case SMT_Water_VariablePvt:
p0.Rate.qw[nWellIndex] = vecRate;
break;
default:
// 当前界面只提供一条总产量曲线,多相模型暂按定产油量解释。
p0.Rate.qo[nWellIndex] = vecRate;
break;
}
}
// PVT数据根据求解器模型类型填充PVT输入数据
nmDataPvtParaForPebi* pebiPvtPara = pDataInstance->getPebiPvtPara();
fillPvtInputByModel(p0, static_cast<NM_SOLVER_MODEL_TYPE>(p0.T), pebiPvtPara, pReservoirData);
// 基础数据(储层参数?)
if(pReservoirData != nullptr) {
p0.Base.Pi = pReservoirData->getInitialPressure().getValue().toDouble(); // 初始压力
p0.Base.Cti = pReservoirData->getCt().getValue().toDouble(); // 综合压缩系数
p0.Base.Cf = pReservoirData->getCf().getValue().toDouble(); // 岩石压缩系数
p0.Base.Soi = pReservoirData->getSoi().getValue().toDouble(); // 初始含油饱和度
p0.Base.Sgi = pReservoirData->getSgi().getValue().toDouble(); // 初始含气饱和度
p0.Base.Swi = pReservoirData->getSwi().getValue().toDouble(); // 初始含水饱和度
// 根据网格单元数设置每个单元的参数
size_t cellCount = oGridOutput2.Trinodexy.size();
// 数据层以mD保存渗透率PEBI输入数组要求使用D。
const double dPermeabilityDarcy =
nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability().getValue().toDouble());
p0.Base.k = dVec1(cellCount, dPermeabilityDarcy);
p0.Base.phi = dVec1(cellCount, pReservoirData->getPorosity().getValue().toDouble());
p0.Base.h = dVec1(cellCount, pReservoirData->getThickness().getValue().toDouble());
}
// 未启用的属性保留上面的单值数组;启用后才使用所选数据组覆盖.
QString interpolationError;
if(!applyPropertyInterpolation(
p0,
pDataInstance->getPropertyInterpolationDataSets(),
pDataInstance->getLicensePath(),
interpolationError)) {
QString logMessage = QString("Property interpolation failed: %1")
.arg(interpolationError);
qWarning() << logMessage;
zxLogInstance::getInstance()->writeLogF(logMessage);
FreeLibrary(dll);
return false;
}
// 获取时间步长
nmDataTimeStepSetting* pTimeStepSetting = pDataInstance->getTimeStep();
if(pTimeStepSetting) {
p0.Base.d = pTimeStepSetting->getTimeGrowthExponent().getValue().toDouble();
p0.Base.dt_Min = pTimeStepSetting->getMinDeltaTAttribute().getValue().toDouble();
p0.Base.dt_Max = pTimeStepSetting->getMaxDeltaTAttribute().getValue().toDouble();
}
try {
nmDataAnalyzeManager* pDataManager = m_pDataManager;
QString licensePath = pDataManager->getLicensePath();
std::string licensePathStd = licensePath.toStdString();
int nSolverType = pDataManager->getPebiSolverType();
setSolverType(nSolverType);
if(nSolverType == nmDataAnalyzeManager::PebiSolverCpuAccelerated) {
setOmpThreads(pDataManager->getPebiOmpThreads());
setIluReuseSteps(pDataManager->getPebiIluReuseSteps());
}
HX_NWTM_MODEL(p1, p0, licensePathStd);
m_nPebiCount = nGridPebiCount;
m_nSolveTimeMs = getSolveTime();
if(m_sAutoFitTargetWellCode.isEmpty()) {
QString sSolveLog = QString("PEBI count: %1, solve time: %2 ms")
.arg(m_nPebiCount).arg(m_nSolveTimeMs);
qDebug() << sSolveLog;
zxLogInstance::getInstance()->writeLogF(sSolveLog);
}
} catch(const std::exception& e) {
// 捕获 C++ 标准异常
qDebug() << QString("C++ Exception during HX_NWTM_MODEL call: %1").arg(e.what());
// 输出到日志窗口
logHX_NWTM_MODEL_INPUT_Simplified(p0);
// 生成一个带有时间戳的文件名
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString logFileName = QString("AAA_HX_NWTM_MODEL_INPUT_error_%1.txt").arg(timestamp);
QString appDirPath = QCoreApplication::applicationDirPath();
QString logFilePath = QDir(appDirPath).filePath(logFileName);
// 将 p0 的内容写入文件
this->saveHX_NWTM_MODEL_INPUT_ToTxt(p0, logFilePath);
FreeLibrary(dll); // 异常发生,释放 DLL
return false;
} catch(...) {
// 捕获结构化异常 (SEH)
qDebug() << "SEH Exception Occurred during HX_NWTM_MODEL call";
// 输出到日志窗口
logHX_NWTM_MODEL_INPUT_Simplified(p0);
// 生成一个带有时间戳的文件名
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString logFileName = QString("AAA_HX_NWTM_MODEL_INPUT_error_%1.txt").arg(timestamp);
QString appDirPath = QCoreApplication::applicationDirPath();
QString logFilePath = QDir(appDirPath).filePath(logFileName);
// 将 p0 的内容写入文件
this->saveHX_NWTM_MODEL_INPUT_ToTxt(p0, logFilePath);
FreeLibrary(dll); // 异常发生,释放 DLL
return false;
}
// DLL 返回后再次检查版本,求解期间发生的井位、角色或参数编辑会使本任务作废。
pAnalysisCase = pDataInstance->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr ||
pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision ||
!pAnalysisCase->isGridValid()) {
FreeLibrary(dll);
return false;
}
// 只构造任务局部结果;完整求解由主线程在完成回调中一次性提交。
bool success = this->buildPebiModeResult(p1,
modelType,
oGridOutput1);
FreeLibrary(dll); // 卸载 DLL
return success;
} else {
DWORD error = GetLastError();
printf("Failed to resolve HX_NWTM_GRID function. Error code: %lu\n", error);
}
FreeLibrary(dll); // 卸载 DLL
} else {
DWORD error = GetLastError();
printf("Failed to load DLL. Error code: %lu\n", error);
}
return false;
}
std::vector<double> HX_logderivative(const std::vector<double>& x, const std::vector<double>& y, const int& n)
{
// 功能: 对数导数函数
// 作者: 何辉
// 日期: 2024.07.16
// 单位: 西安华线石油科技有限公司(西安石油大学)
std::vector<double> d; d.resize(n - 1);
d[0] = 0.5 * (y[1] - y[0]) / (x[1] - x[0]) * (x[1] + x[0]);
d[0] = max(d[0], DBL_EPSILON);
for (int i = 1; i < n - 2; ++i)
{
d[i] = (y[i - 1] * (x[i] - x[i + 1]) / ((x[i - 1] - x[i]) * (x[i - 1] - x[i + 1])) +
y[i] * (2 * x[i] - x[i - 1] - x[i + 1]) / ((x[i] - x[i - 1]) * (x[i] - x[i + 1])) +
y[i + 1] * (x[i] - x[i - 1]) / ((x[i - 1] - x[i + 1]) * (x[i] - x[i + 1]))) * x[i];
d[i] = max(d[i], DBL_EPSILON);
}
d[n - 2] = 0.5 * (y[n - 2] - y[n - 3]) / (x[n - 2] - x[n - 3]) * (x[n - 2] + x[n - 3]);
d[n - 2] = max(d[n - 2], DBL_EPSILON);
return d;
}
bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
HX_NWTM_MODEL_OUTPUT& p1,
int modelType,
const HX_NWTM_GRID_OUTPUT1& oGridOutput)
{
nmDataAnalyzeManager* pDataInstance = m_pDataManager;
if(pDataInstance == nullptr) {
return false;
}
const bool autoFitTargetOnly = !m_sAutoFitTargetWellCode.isEmpty();
bool autoFitTargetFound = false;
m_autoFitResultPressure.clear();
m_autoFitResultLogLog.clear();
m_autoFitResultSemiLog.clear();
QVector<QVector<double>> vvecPressure;
QVector<QVector<double>> vvecLogLog;
QVector<QVector<double>> vvecSemiLog;
// p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure.
// 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力.
const bool usePseudoPressure = (modelType == static_cast<int>(SMT_Gas_VariablePvt));
CalPseudoPressure calPseudoPressure = usePseudoPressure
? getPseudoPressureConverter()
: nullptr;
if(usePseudoPressure && calPseudoPressure == nullptr) {
qWarning() << "Failed to load the gas pseudo-pressure converter.";
return false;
}
// 获取参与求解的井的顺序
QVector<nmSolverWellRef> vecWellsOrder =
pDataInstance->getSolverWellOrder();
// 第一步:真实井必须逐口具有与公共时间轴等长的井底压力。
// 观察井虽然没有源汇项,也必须由 DLL 返回压力,否则不能形成有效多井结果。
if(vecWellsOrder.isEmpty() || p1.t.empty()) {
return false;
}
for(size_t nTimeIndex = 0; nTimeIndex < p1.t.size(); ++nTimeIndex) {
if(!isFiniteSolverNumber(p1.t[nTimeIndex])) {
qWarning() << "PEBI returned a non-finite time at index:"
<< static_cast<int>(nTimeIndex);
return false;
}
}
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
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() ||
pDataInstance->findWellByCode(oWellRef.m_sWellCode) == nullptr) {
qWarning() << "Incomplete well pressure returned for WellCode:"
<< oWellRef.m_sWellCode;
return false;
}
for(size_t nTimeIndex = 0;
nTimeIndex < p1.pw[nIndex].size();
++nTimeIndex) {
if(!isFiniteSolverNumber(p1.pw[nIndex][nTimeIndex])) {
qWarning() << "PEBI returned a non-finite pressure for WellCode:"
<< oWellRef.m_sWellCode
<< "time index:" << static_cast<int>(nTimeIndex);
return false;
}
}
}
// 遍历每口井,处理其数据
for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) {
const nmSolverWellRef& oWellRef = vecWellsOrder[wellIdx];
if(autoFitTargetOnly &&
oWellRef.m_sWellCode != m_sAutoFitTargetWellCode) {
continue;
}
// 手工裂缝不是结果井,只用于保持 DLL 下标与网格输入一致。
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
continue;
}
// 3.1 填充井底压力数据到局部变量
QVector<double> currentWellTime;
QVector<double> currentWellPressure;
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
currentWellTime.append(p1.t[i]);
currentWellPressure.append(p1.pw[wellIdx][i]);
}
vvecPressure.clear(); // 清空上次循环的数据,为当前井准备
vvecPressure.append(currentWellTime);
vvecPressure.append(currentWellPressure);
// 3.2 计算双对数和半对数曲线数据并存储到局部变量
nmDataWellBase* pWellData =
pDataInstance->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
return false;
}
const bool bRateControlled =
pDataInstance->getCalculationWellMode(
oWellRef.m_sWellCode) == NM_CaseWell_RateControlled &&
pWellData->getFlowPoints().size() >= 2;
vvecLogLog.clear();
vvecSemiLog.clear();
if(bRateControlled) {
// 准备压力数据 (用于传递给外部 DLL)
std::vector<Point> wellPressureDataForDll;
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
Point pt;
pt.x = p1.t[i];
// 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力.
double pressureForLog = p1.pw[wellIdx][i];
if(usePseudoPressure
&& (!isFiniteSolverNumber(pressureForLog)
// -1 与主界面调用一致, 表示使用 configPsAbouts() 配置的当前模式.
|| !calPseudoPressure(pressureForLog, pressureForLog, -1)
|| !isFiniteSolverNumber(pressureForLog))) {
qWarning() << "Failed to convert gas pressure to pseudo-pressure:"
<< p1.pw[wellIdx][i]
<< "well code:" << oWellRef.m_sWellCode;
return false;
}
pt.y = pressureForLog;
pt.z = 0.0;
wellPressureDataForDll.push_back(pt);
}
// 准备流量段数据
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
// 防御空流量数据,避免 -1 转成 std::vector 的巨大无符号长度。
int nTimeNumQ = qMax(0, vecTimeQ.size() - 1); // 移除第一个 0 点
std::vector<double> timeQ(nTimeNumQ);
std::vector<double> q(nTimeNumQ);
for(int i = 0; i < nTimeNumQ; ++i) {
timeQ[i] = vecTimeQ[i + 1].x();
q[i] = vecTimeQ[i + 1].y();
}
// 调用外部 DLL 计算双对数曲线
std::vector<Point> logPreResultFromDll; // 存储 DLL 的计算结果
int iSectionFlowIndex = pWellData->getIndexF();
// 第一步:无产量观察井只接收压力结果,不调用依赖产量制度的曲线 DLL。
HMODULE hMod_solver = nTimeNumQ > 0
? LoadLibrary(L"singlePhaseSolverDll.dll") : nullptr;
if(nTimeNumQ <= 0) {
// 第二步:保持固定的数据列结构,便于结果保存和后续读取;
// 各列为空明确表示该观察井没有可展示的双对数、半对数结果。
vvecLogLog.clear();
vvecLogLog.append(QVector<double>());
vvecLogLog.append(QVector<double>());
vvecLogLog.append(QVector<double>());
vvecSemiLog.clear();
vvecSemiLog.append(QVector<double>());
vvecSemiLog.append(QVector<double>());
} else if(hMod_solver) {
typedef bool (*PreLog)(const std::vector<Point>&, const int&, double*, double*, int, std::vector<Point>&);
PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre");
if(nullptr == preLogFun) {
FreeLibrary(hMod_solver);
std::cout << "preLogFun failed!\n";
return false;
}
// 气井传入拟压力序列, 油井和水井仍传入原始压力序列.
// 计算失败必须向上返回, 避免 PSO 使用空曲线或上一粒子的旧曲线.
if(!preLogFun(wellPressureDataForDll,
iSectionFlowIndex,
timeQ.data(),
q.data(),
nTimeNumQ,
logPreResultFromDll)) {
FreeLibrary(hMod_solver);
return false;
}
// 填充双对数曲线数据到局部变量
QVector<double> logX, logY, logZ;
// 检查结果是否为空,并跳过第一个点
if (logPreResultFromDll.size() > 1) {
// 从索引 1 开始遍历,跳过索引 0 的第一个点
for (size_t i = 1; i < logPreResultFromDll.size(); ++i) {
const auto& pt = logPreResultFromDll[i];
if(!isValidLogLogPoint(pt)) {
continue;
}
logX.append(pt.x);
logY.append(pt.y);
logZ.append(pt.z);
}
}
vvecLogLog.clear(); // 清空上次循环的数据
vvecLogLog.append(logX);
vvecLogLog.append(logY);
vvecLogLog.append(logZ);
// 填充半对数曲线数据到局部变量
QVector<double> semiLogX, semiLogY;
// 检查结果是否为空,并跳过第一个点
if (logPreResultFromDll.size() > 1) {
// 半对数曲线也应该同步跳过第一个点
for (size_t i = 1; i < logPreResultFromDll.size(); ++i) {
const auto& pt = logPreResultFromDll[i];
if(!isValidSemiLogPoint(pt)) {
continue;
}
semiLogX.append(pt.x);
semiLogY.append(pt.pointData[0]);
}
}
vvecSemiLog.clear(); // 清空上次循环的数据
vvecSemiLog.append(semiLogX);
vvecSemiLog.append(semiLogY);
FreeLibrary(hMod_solver);
} else {
qWarning() << "Failed to load singlePhaseSolverDll.dll.";
return false;
}
}
if(autoFitTargetOnly) {
// 粒子评价结果保存在任务对象内,避免反复改写 DataManager.
m_autoFitResultPressure = vvecPressure;
m_autoFitResultLogLog = vvecLogLog;
m_autoFitResultSemiLog = vvecSemiLog;
autoFitTargetFound = true;
break;
} else {
// 所有真实井先写入任务局部快照;观察井的双对数和半对数保持为空。
// 任意后续步骤失败时,整个快照会被丢弃,旧成果不会被局部覆盖。
nmPebiWellResultSnapshot oWellResult;
oWellResult.m_sWellCode = oWellRef.m_sWellCode;
oWellResult.m_vecPressure = vvecPressure;
oWellResult.m_vecLogLog = vvecLogLog;
oWellResult.m_vecSemiLog = vvecSemiLog;
oWellResult.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
pWellData->getY().getValue().toDouble());
m_vecPendingWellResults.append(oWellResult);
}
}
if(autoFitTargetOnly) {
const bool pressureValid = autoFitTargetFound
&& m_autoFitResultPressure.size() >= 2
&& !m_autoFitResultPressure[0].isEmpty()
&& m_autoFitResultPressure[0].size() == m_autoFitResultPressure[1].size();
const bool logLogValid = m_autoFitResultLogLog.size() >= 3
&& !m_autoFitResultLogLog[0].isEmpty()
&& m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[1].size()
&& m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[2].size();
return pressureValid && logLogValid;
}
// 第四步:完整求解还必须具有与时间轴、网格绘图单元严格对应的场压力。
if(p1.p.size() != p1.t.size() ||
oGridOutput.PEBI_cell.isplot.empty()) {
return false;
}
// 计算有效单元数量
size_t actualPlotCellsCount = 0;
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) {
if(oGridOutput.PEBI_cell.isplot[i] == 1) {
actualPlotCellsCount++;
}
}
if(actualPlotCellsCount == 0) {
return false;
}
for(size_t nTimeIndex = 0; nTimeIndex < p1.p.size(); ++nTimeIndex) {
if(p1.p[nTimeIndex].size() < oGridOutput.PEBI_cell.isplot.size()) {
qWarning() << "Incomplete field pressure returned for time index:"
<< static_cast<int>(nTimeIndex);
return false;
}
for(size_t nCellIndex = 0;
nCellIndex < oGridOutput.PEBI_cell.isplot.size();
++nCellIndex) {
if(oGridOutput.PEBI_cell.isplot[nCellIndex] == 1 &&
!isFiniteSolverNumber(p1.p[nTimeIndex][nCellIndex])) {
qWarning() << "PEBI returned a non-finite field pressure at time/cell:"
<< static_cast<int>(nTimeIndex)
<< static_cast<int>(nCellIndex);
return false;
}
}
}
// 第五步:在局部时间步映射中构造场压力,同时计算全局标量范围。
// 此处不触碰 DataManager较短的新时间轴会在主线程整体替换时自然清除旧帧。
m_mapPendingTimeSteps.clear();
double dMinP = DBL_MAX;
double dMaxP = -DBL_MAX;
// 为每个时间步生成完整的 VTK 压力数组。
for(size_t timeIdx = 0; timeIdx < p1.p.size(); ++timeIdx) {
// 获取当前时间步的时间值
double currentTime = p1.t[timeIdx];
// 创建 vtkDoubleArray 来存储当前时间步的压力数据
vtkSmartPointer<vtkDoubleArray> pressureData = vtkSmartPointer<vtkDoubleArray>::New();
pressureData->SetName("p"); // 设置标量数据的名称
// 直接使用外面计算好的 actualPlotCellsCount
pressureData->SetNumberOfValues(actualPlotCellsCount);
size_t destIdx = 0; // 目标数组的索引
double dCurrentTimeMin = DBL_MAX; // 当前时间步的最小值
double dCurrentTimeMax = -DBL_MAX; // 当前时间步的最大值
// 遍历原始数据,并根据 isplot 填充到 pressureData
for(size_t i = 0; i < oGridOutput.PEBI_cell.isplot.size(); ++i) {
if(oGridOutput.PEBI_cell.isplot[i] == 1) { // 只有当 isplot 为1时才考虑这个单元格
pressureData->SetValue(destIdx, p1.p[timeIdx][i]);
// 更新当前时间步的范围
dCurrentTimeMin = qMin(dCurrentTimeMin, p1.p[timeIdx][i]);
dCurrentTimeMax = qMax(dCurrentTimeMax, p1.p[timeIdx][i]);
destIdx++;
}
}
// 更新全局范围
dMinP = qMin(dMinP, dCurrentTimeMin);
dMaxP = qMax(dMaxP, dCurrentTimeMax);
m_mapPendingTimeSteps.insert(currentTime, pressureData);
}
// QMap 使用时间作为唯一键;重复时间会覆盖旧帧,因此必须检查数量一致。
if(m_mapPendingTimeSteps.size() != static_cast<int>(p1.t.size())) {
return false;
}
// 第六步:复制与本次场数据对应的基础网格,并在最后才发布“快照就绪”。
m_pPendingResultGrid =
pDataInstance->getUnstructuredGridCopy();
if(m_pPendingResultGrid == nullptr ||
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
return false;
}
int nExpectedWellResultCount = 0;
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
if(vecWellsOrder[nIndex].m_eEntryKind == NM_SolverEntry_Well) {
++nExpectedWellResultCount;
}
}
if(m_vecPendingWellResults.size() != nExpectedWellResultCount) {
return false;
}
m_dPendingScalarMin = dMinP;
m_dPendingScalarMax = dMaxP;
m_bPendingFullResultReady = true;
return true;
}
bool nmCalculationDllPebiSolverTask::commitResult(
nmDataAnalyzeManager* pDataManager)
{
// 第一步:自动拟合只返回目标曲线,不允许提交完整成果;手工求解必须回到
// DataManager 所属线程执行,保证界面看不到逐项替换过程中的中间状态。
if(!m_sAutoFitTargetWellCode.isEmpty() ||
!m_bPendingFullResultReady ||
pDataManager == nullptr ||
pDataManager != m_pDataManager ||
QThread::currentThread() != pDataManager->thread()) {
return false;
}
nmDataNumericalAnalysisCase* pAnalysisCase =
pDataManager->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr ||
pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision ||
!pAnalysisCase->isGridValid() ||
m_mapPendingTimeSteps.isEmpty() ||
m_pPendingResultGrid == nullptr ||
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
return false;
}
// 第二步:在改动旧成果前一次性解析全部目标井,并检查 WellCode 不重复。
QVector<nmDataWellBase*> vecTargetWells;
QSet<QString> setWellCodes;
vecTargetWells.reserve(m_vecPendingWellResults.size());
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
const nmPebiWellResultSnapshot& oWellResult =
m_vecPendingWellResults[nIndex];
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellResult.m_sWellCode);
if(pWellData == nullptr ||
setWellCodes.contains(oWellResult.m_sWellCode)) {
return false;
}
setWellCodes.insert(oWellResult.m_sWellCode);
vecTargetWells.append(pWellData);
}
// 第三步:最后一次登记版本。登记失败时旧成果仍未被修改;登记成功后当前
// 主线程事件不会被其他编辑操作插入,因此后续替换不存在可恢复失败分支。
if(!pAnalysisCase->markResultsAvailableIfCurrent(
m_nGridInputRevision, m_nResultInputRevision)) {
return false;
}
// 第四步:版本检查和对象解析全部通过后,在当前主线程事件内整体替换。
// 这些 setter 不发事件也不包含可恢复失败分支,外部只能在本函数返回后看到新成果。
pDataManager->clearWellLocations();
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
const nmPebiWellResultSnapshot& oWellResult =
m_vecPendingWellResults[nIndex];
nmDataWellBase* pWellData = vecTargetWells[nIndex];
pWellData->setResultPressure(oWellResult.m_vecPressure);
pWellData->setResultLogLog(oWellResult.m_vecLogLog);
pWellData->setResultSemiLog(oWellResult.m_vecSemiLog);
pDataManager->addWellLocation(oWellResult.m_sWellCode,
oWellResult.m_oLocation);
}
pDataManager->clearTimeSteps();
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator oTimeIt =
m_mapPendingTimeSteps.constBegin();
for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) {
pDataManager->addTimeStep(oTimeIt.key(), oTimeIt.value());
}
pDataManager->setScalarRangeP(m_dPendingScalarMin,
m_dPendingScalarMax);
pDataManager->setResultBaseGrid(m_pPendingResultGrid);
// 第五步:清除待提交标志,防止重复完成信号再次覆盖后续结果。
m_bPendingFullResultReady = false;
return true;
}
//bool nmCalculationDllPebiSolverTask::savePebiModeResult(HX_NWTM_MODEL_OUTPUT& p1)
//{
// nmDataAnalyzeManager* pDataInstance = nmDataAnalyzeManager::getCurrentInstance();
//
// QVector<QVector<double>> vvecPressure;
// QVector<QVector<double>> vvecLogLog;
// QVector<QVector<double>> vvecSemiLog;
//
// // 获取参与求解的井的顺序
// QVector<QPair<NM_WELL_MODEL, QString>> vecWellsOrder = nmDataAnalyzeManager::getCurrentInstance()->getCalculationWells();
//
// // 清空井名和二维位置的映射
// pDataInstance->clearWellLocations();
//
// // 遍历每口井,处理其数据
// for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) {
// NM_WELL_MODEL eWellType = vecWellsOrder[wellIdx].first; // 获取井的类型
// QString sWellName = vecWellsOrder[wellIdx].second; // 获取井的名称
//
// // 跳过裂缝(或未知井类型)
// if(eWellType == NM_WELL_MODEL::Unknow_Well) {
// continue;
// }
//
// // 3.1 填充井底压力数据到局部变量
// QVector<double> currentWellTime;
// QVector<double> currentWellPressure; // P_wf(t)
//
// // 确保 p1.pw[wellIdx] 存在且大小与 p1.t 匹配
// if(wellIdx < p1.pw.size()) {
// for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
// if(i < p1.t.size()) { // 确保时间数据也存在
// currentWellTime.append(p1.t[i]);
// currentWellPressure.append(p1.pw[wellIdx][i]);
// }
// }
// }
//
// vvecPressure.clear(); // 清空上次循环的数据,为当前井准备
// vvecPressure.append(currentWellTime);
// vvecPressure.append(currentWellPressure);
//
// // 3.2 计算双对数和半对数曲线数据并存储到局部变量
// nmDataWellBase* pWellData = nmDataAnalyzeManager::getCurrentInstance()->findWellByName(sWellName);
//
// if(pWellData) {
//
// // --- START: 替换为 HX_logderivative 计算双对数数据 ---
//
// // 1. 获取初始压力 Pi用于计算压力降 DeltaP
// double initialPressure = nmDataAnalyzeManager::getCurrentInstance()->getReservoirData()->getInitialPressure().getValue().toDouble();
//
// // 为了演示,如果无法获取 Pi我们将使用数组中第一个压力点作为初始压力 Pi
// //if (!currentWellPressure.isEmpty()) {
// // // 通常第一个压力点 Pwf(t=0) 就是初始压力 Pi
// // initialPressure = currentWellPressure.first();
// //}
//
// // 2. 准备 t (x) 和 DeltaP (y) 的 std::vector
// std::vector<double> t_vec; // 时间 t (x)
// std::vector<double> deltaP_vec; // 压力降 DeltaP = Pi - Pwf(t) (y)
// int N = currentWellTime.size();
//
// for (int i = 0; i < N; ++i) {
// t_vec.push_back(currentWellTime[i]);
// // 计算压力降
// deltaP_vec.push_back(initialPressure - currentWellPressure[i]);
// }
//
// // 3. 调用 HX_logderivative 计算对数导数 (P')
// std::vector<double> logDerivative_vec;
//
// if (N > 2) { // 确保有足够的数据点 (N-1 个导数点)
// logDerivative_vec = HX_logderivative(t_vec, deltaP_vec, N);
// } else {
// // 数据点不足,无法计算导数
// logDerivative_vec.clear();
// }
//
// // 4. 组装双对数曲线数据 (t, DeltaP, P'),严格遵循绘图规则
// QVector<double> logX, logY, logZ; // logX: t_plot, logY: DeltaP_plot, logZ: P'
// size_t M = logDerivative_vec.size(); // 导数点数量 M = N - 1
//
// if (M >= 1) {
//
// // --- 4.1 第一个点 (索引 i=0) ---
// // 导数时间: t_plot = 0.5 * (x[1] + x[0])
// logX.append(0.5 * (t_vec[1] + t_vec[0]));
// // 压力降 (近似): DeltaP[0] 和 DeltaP[1] 的平均值
// logY.append((deltaP_vec[0] + deltaP_vec[1]) / 2.0);
// // 导数: d[0]
// logZ.append(logDerivative_vec[0]);
//
//
// // --- 4.2 中间点 (索引 i=1 到 N-3) ---
// // 在 logDerivative_vec 中,这些点对应索引 i=1 到 M-1 (即 N-3)
// for (size_t i = 1; i < (size_t)N - 2; ++i) {
// // 导数时间: t_plot = x[i]
// logX.append(t_vec[i]);
// // 压力降: 匹配原始数据 DeltaP[i]
// logY.append(deltaP_vec[i]);
// // 导数: d[i]
// logZ.append(logDerivative_vec[i]);
// }
//
// // --- 4.3 最后一个点 (索引 i=N-2) ---
// if (M >= 2) { // 确保至少有 2 个导数点
// size_t lastIdx = M - 1; // 对应 logDerivative_vec[N-2]
// // 导数时间: t_plot = 0.5 * (x[n-2] + x[n-3])
// // 对应 t_vec[N-2] 和 t_vec[N-3]
// logX.append(0.5 * (t_vec[N - 2] + t_vec[N - 3]));
//
// // 压力降 (近似): DeltaP[N-2] 和 DeltaP[N-3] 的平均值
// logY.append((deltaP_vec[N - 2] + deltaP_vec[N - 3]) / 2.0);
//
// // 导数: d[N-2]
// logZ.append(logDerivative_vec[lastIdx]);
// }
// }
//
// // 将组装好的数据存储到 vvecLogLog
// vvecLogLog.clear(); // 清空上次循环的数据
// vvecLogLog.append(logX); // 时间 (t_plot)
// vvecLogLog.append(logY); // 压力降 (DeltaP_plot)
// vvecLogLog.append(logZ); // 对数导数 (P')
//
//
// // 5. 组装半对数曲线数据
// // 半对数图 Y 轴是井底压力 Pwf(t)X 轴是时间 t
// // vvecSemiLog 存储 [t, Pwf(t)],使用原始数据即可
// vvecSemiLog.clear();
// vvecSemiLog.append(currentWellTime); // X轴: 时间 t
// vvecSemiLog.append(currentWellPressure); // Y轴: 井底压力 Pwf(t)
//
// // --- END: 替换为 HX_logderivative 计算双对数数据 ---
//
// } // end if(pWellData)
//
// // 将计算结果保存到对应的井数据里
// // 压力
// pWellData->setResultPressure(vvecPressure);
// // 双对数
// pWellData->setResultLogLog(vvecLogLog);
// // 半对数
// pWellData->setResultSemiLog(vvecSemiLog);
//
// // 存储当前井名称和二维位置到映射
// QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble());
// pDataInstance->addWellLocation(sWellName, ptWellCoords);
// }
// 主日志函数:打印 HX_NWTM_MODEL_INPUT 的内容
void nmCalculationDllPebiSolverTask::logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0)
{
// 1. Basic Parameters (T)
qDebug() << QString("Parameter T: %1").arg(p0.T);
// 2. GRID Data
qDebug() << "\n--- GRID Data ---";
qDebug() << QString("GRID.Trinodexy: Capacity %1").arg(p0.GRID.Trinodexy.size());
qDebug() << QString("GRID.Area: Capacity %1").arg(p0.GRID.Area.size());
qDebug() << QString("GRID.D: Capacity %1").arg(p0.GRID.D.size());
// 2.1 GRID.ZhiJingNeiBianJie
qDebug() << "\n -- GRID.ZhiJingNeiBianJie --";
qDebug() << QString(" n: %1").arg(p0.GRID.ZhiJingNeiBianJie.n);
qDebug() << QString(" XiLinw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.XiLinw.size());
qDebug() << QString(" lw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.lw.size());
qDebug() << QString(" dw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.dw.size());
qDebug() << QString(" rw: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.rw.size());
qDebug() << QString(" inwell: Capacity %1").arg(p0.GRID.ZhiJingNeiBianJie.inwell.size());
// 2.2 GRID.LieFengJingNeiBianJie
qDebug() << "\n -- GRID.LieFengJingNeiBianJie --";
qDebug() << QString(" n: %1").arg(p0.GRID.LieFengJingNeiBianJie.n);
qDebug() << QString(" XiLinf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.XiLinf.size());
qDebug() << QString(" lf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.lf.size());
qDebug() << QString(" df: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.df.size());
qDebug() << QString(" xf: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.xf.size());
qDebug() << QString(" infra: Capacity %1").arg(p0.GRID.LieFengJingNeiBianJie.infra.size());
// 2.3 GRID.DuoJiYaLieShuiPingJingNeiBianJie
qDebug() << "\n -- GRID.DuoJiYaLieShuiPingJingNeiBianJie --";
qDebug() << QString(" n: %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.n);
qDebug() << QString(" XiLinh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.XiLinh.size());
qDebug() << QString(" lh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.lh.size());
qDebug() << QString(" dh: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dh.size());
qDebug() << QString(" dsxf: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dsxf.size());
qDebug() << QString(" inhor: Capacity %1").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.inhor.size());
// 2.4 GRID.WaiBianJie
qDebug() << "\n -- GRID.WaiBianJie --";
qDebug() << QString(" n: %1").arg(p0.GRID.WaiBianJie.n);
qDebug() << QString(" WaiBianh: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBianh.size());
qDebug() << QString(" WaiBianl: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBianl.size());
qDebug() << QString(" WaiBiand: Capacity %1").arg(p0.GRID.WaiBianJie.WaiBiand.size());
// 2.5 GRID.NeiBuDuanCeng
qDebug() << "\n -- GRID.NeiBuDuanCeng --";
qDebug() << QString(" n: %1").arg(p0.GRID.NeiBuDuanCeng.n);
qDebug() << QString(" faultb1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultb1.size());
qDebug() << QString(" faultb2: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultb2.size());
qDebug() << QString(" faultl1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultl1.size());
qDebug() << QString(" faultd1: Capacity %1").arg(p0.GRID.NeiBuDuanCeng.faultd1.size());
// 2.6 GRID.YuChuLiJuZhen
qDebug() << "\n -- GRID.YuChuLiJuZhen --";
qDebug() << QString(" ia: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.ia.size());
qDebug() << QString(" ja: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.ja.size());
qDebug() << QString(" nzeros: Capacity %1").arg(p0.GRID.YuChuLiJuZhen.nzeros.size());
qDebug() << QString(" numk: %1").arg(p0.GRID.YuChuLiJuZhen.numk);
// 3. Rate Data
qDebug() << "\n--- Rate Data ---";
qDebug() << QString("Rate.t: Contains %1 data sets").arg(p0.Rate.t.size());
qDebug() << QString("Rate.qo: Contains %1 data sets").arg(p0.Rate.qo.size());
qDebug() << QString("Rate.qg: Contains %1 data sets").arg(p0.Rate.qg.size());
qDebug() << QString("Rate.qw: Contains %1 data sets").arg(p0.Rate.qw.size());
// Optional: Print capacity of each inner vector, e.g.:
// for (int i = 0; i < p0.Rate.t.size(); ++i) {
// zxLogInstance::getInstance()->writeLogF(QString(" Rate.t[%1] Capacity: %2").arg(i).arg(p0.Rate.t[i].size()));
// }
// 4. Pressure Data
qDebug() << "\n--- Pressure Data ---";
qDebug() << QString("Pressure.t: Contains %1 data sets").arg(p0.Pressure.t.size());
qDebug() << QString("Pressure.p: Contains %1 data sets").arg(p0.Pressure.p.size());
// 5. CS Wellbore Storage and Skin Data
qDebug() << "\n--- CS Data ---";
qDebug() << QString("CS.C: Capacity %1").arg(p0.CS.C.size());
qDebug() << QString("CS.S: Capacity %1").arg(p0.CS.S.size());
// 6. PVT Fluid Property Data
qDebug() << "\n--- PVT Data ---";
qDebug() << QString("PVT.p: Capacity %1").arg(p0.PVT.p.size());
qDebug() << QString("PVT.Rso: Capacity %1").arg(p0.PVT.Rso.size());
qDebug() << QString("PVT.Bo: Capacity %1").arg(p0.PVT.Bo.size());
qDebug() << QString("PVT.Co: Capacity %1").arg(p0.PVT.Co.size());
qDebug() << QString("PVT.miuo: Capacity %1").arg(p0.PVT.miuo.size());
qDebug() << QString("PVT.rouo: Capacity %1").arg(p0.PVT.rouo.size());
qDebug() << QString("PVT.Rv: Capacity %1").arg(p0.PVT.Rv.size());
qDebug() << QString("PVT.Bg: Capacity %1").arg(p0.PVT.Bg.size());
qDebug() << QString("PVT.Cg: Capacity %1").arg(p0.PVT.Cg.size());
qDebug() << QString("PVT.miug: Capacity %1").arg(p0.PVT.miug.size());
qDebug() << QString("PVT.roug: Capacity %1").arg(p0.PVT.roug.size());
qDebug() << QString("PVT.Z: Capacity %1").arg(p0.PVT.Z.size());
qDebug() << QString("PVT.Rsw: Capacity %1").arg(p0.PVT.Rsw.size());
qDebug() << QString("PVT.Bw: Capacity %1").arg(p0.PVT.Bw.size());
qDebug() << QString("PVT.Cw: Capacity %1").arg(p0.PVT.Cw.size());
qDebug() << QString("PVT.miuw: Capacity %1").arg(p0.PVT.miuw.size());
qDebug() << QString("PVT.rouw: Capacity %1").arg(p0.PVT.rouw.size());
qDebug() << QString("PVT.V: Capacity %1").arg(p0.PVT.V.size());
qDebug() << QString("PVT.k_kinitial: Capacity %1").arg(p0.PVT.k_kinitial.size());
qDebug() << QString("PVT.Cf_Cfinitial: Capacity %1").arg(p0.PVT.Cf_Cfinitial.size());
qDebug() << QString("PVT.So: Capacity %1").arg(p0.PVT.So.size());
qDebug() << QString("PVT.Kro: Capacity %1").arg(p0.PVT.Kro.size());
qDebug() << QString("PVT.Sg: Capacity %1").arg(p0.PVT.Sg.size());
qDebug() << QString("PVT.Krg: Capacity %1").arg(p0.PVT.Krg.size());
qDebug() << QString("PVT.Sw: Capacity %1").arg(p0.PVT.Sw.size());
qDebug() << QString("PVT.Krw: Capacity %1").arg(p0.PVT.Krw.size());
// 7. Base Data
qDebug() << "\n--- Base Data ---";
qDebug() << QString("Base.Pi: %1").arg(p0.Base.Pi);
qDebug() << QString("Base.Cti: %1").arg(p0.Base.Cti);
qDebug() << QString("Base.Cf: %1").arg(p0.Base.Cf);
qDebug() << QString("Base.Soi: %1").arg(p0.Base.Soi);
qDebug() << QString("Base.Sgi: %1").arg(p0.Base.Sgi);
qDebug() << QString("Base.Swi: %1").arg(p0.Base.Swi);
qDebug() << QString("Base.k: Capacity %1").arg(p0.Base.k.size());
qDebug() << QString("Base.phi: Capacity %1").arg(p0.Base.phi.size());
qDebug() << QString("Base.h: Capacity %1").arg(p0.Base.h.size());
qDebug() << "\n--- End of HX_NWTM_MODEL_INPUT Content ---";
}
bool nmCalculationDllPebiSolverTask::saveHX_NWTM_MODEL_INPUT_ToTxt(const HX_NWTM_MODEL_INPUT& p0, const QString& filePath)
{
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
// Log an error if the file cannot be opened
qDebug() << QString("ERROR: Could not open file for writing: %1").arg(filePath);
return false;
}
QTextStream out(&file);
out << "--- Start Printing HX_NWTM_MODEL_INPUT Content ---\n";
// 1. Basic Parameters (T)
out << QString("Parameter T: %1\n").arg(p0.T);
// 2. GRID Data
out << "\n--- GRID Data ---\n";
out << QString("GRID.Trinodexy: Capacity %1\n").arg(p0.GRID.Trinodexy.size());
out << QString("GRID.Area: Capacity %1\n").arg(p0.GRID.Area.size());
out << QString("GRID.D: Capacity %1\n").arg(p0.GRID.D.size());
// 2.1 GRID.ZhiJingNeiBianJie
out << "\n -- GRID.ZhiJingNeiBianJie --\n";
out << QString(" n: %1\n").arg(p0.GRID.ZhiJingNeiBianJie.n);
out << QString(" XiLinw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.XiLinw.size());
out << QString(" lw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.lw.size());
out << QString(" dw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.dw.size());
out << QString(" rw: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.rw.size());
out << QString(" inwell: Capacity %1\n").arg(p0.GRID.ZhiJingNeiBianJie.inwell.size());
// 2.2 GRID.LieFengJingNeiBianJie
out << "\n -- GRID.LieFengJingNeiBianJie --\n";
out << QString(" n: %1\n").arg(p0.GRID.LieFengJingNeiBianJie.n);
out << QString(" XiLinf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.XiLinf.size());
out << QString(" lf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.lf.size());
out << QString(" df: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.df.size());
out << QString(" xf: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.xf.size());
out << QString(" infra: Capacity %1\n").arg(p0.GRID.LieFengJingNeiBianJie.infra.size());
// 2.3 GRID.DuoJiYaLieShuiPingJingNeiBianJie
out << "\n -- GRID.DuoJiYaLieShuiPingJingNeiBianJie --\n";
out << QString(" n: %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.n);
out << QString(" XiLinh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.XiLinh.size());
out << QString(" lh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.lh.size());
out << QString(" dh: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dh.size());
out << QString(" dsxf: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.dsxf.size());
out << QString(" inhor: Capacity %1\n").arg(p0.GRID.DuoJiYaLieShuiPingJingNeiBianJie.inhor.size());
// 2.4 GRID.WaiBianJie
out << "\n -- GRID.WaiBianJie --\n";
out << QString(" n: %1\n").arg(p0.GRID.WaiBianJie.n);
out << QString(" WaiBianh: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBianh.size());
out << QString(" WaiBianl: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBianl.size());
out << QString(" WaiBiand: Capacity %1\n").arg(p0.GRID.WaiBianJie.WaiBiand.size());
// 2.5 GRID.NeiBuDuanCeng
out << "\n -- GRID.NeiBuDuanCeng --\n";
out << QString(" n: %1\n").arg(p0.GRID.NeiBuDuanCeng.n);
out << QString(" faultb1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultb1.size());
out << QString(" faultb2: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultb2.size());
out << QString(" faultl1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultl1.size());
out << QString(" faultd1: Capacity %1\n").arg(p0.GRID.NeiBuDuanCeng.faultd1.size());
// 2.6 GRID.YuChuLiJuZhen
out << "\n -- GRID.YuChuLiJuZhen --\n";
out << QString(" ia: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.ia.size());
out << QString(" ja: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.ja.size());
out << QString(" nzeros: Capacity %1\n").arg(p0.GRID.YuChuLiJuZhen.nzeros.size());
out << QString(" numk: %1\n").arg(p0.GRID.YuChuLiJuZhen.numk);
// 3. Rate Data
out << "\n--- Rate Data ---\n";
out << QString("Rate.t: Contains %1 data sets\n").arg(p0.Rate.t.size());
out << QString("Rate.qo: Contains %1 data sets\n").arg(p0.Rate.qo.size());
out << QString("Rate.qg: Contains %1 data sets\n").arg(p0.Rate.qg.size());
out << QString("Rate.qw: Contains %1 data sets\n").arg(p0.Rate.qw.size());
// Optional: Print capacity of each inner vector, e.g.:
// for (int i = 0; i < p0.Rate.t.size(); ++i) {
// out << QString(" Rate.t[%1] Capacity: %2\n").arg(i).arg(p0.Rate.t[i].size());
// }
// 4. Pressure Data
out << "\n--- Pressure Data ---\n";
out << QString("Pressure.t: Contains %1 data sets\n").arg(p0.Pressure.t.size());
out << QString("Pressure.p: Contains %1 data sets\n").arg(p0.Pressure.p.size());
// 5. CS Wellbore Storage and Skin Data
out << "\n--- CS Data ---\n";
out << QString("CS.C: Capacity %1\n").arg(p0.CS.C.size());
out << QString("CS.S: Capacity %1\n").arg(p0.CS.S.size());
// 6. PVT Fluid Property Data
out << "\n--- PVT Data ---\n";
out << QString("PVT.p: Capacity %1\n").arg(p0.PVT.p.size());
out << QString("PVT.Rso: Capacity %1\n").arg(p0.PVT.Rso.size());
out << QString("PVT.Bo: Capacity %1\n").arg(p0.PVT.Bo.size());
out << QString("PVT.Co: Capacity %1\n").arg(p0.PVT.Co.size());
out << QString("PVT.miuo: Capacity %1\n").arg(p0.PVT.miuo.size());
out << QString("PVT.rouo: Capacity %1\n").arg(p0.PVT.rouo.size());
out << QString("PVT.Rv: Capacity %1\n").arg(p0.PVT.Rv.size());
out << QString("PVT.Bg: Capacity %1\n").arg(p0.PVT.Bg.size());
out << QString("PVT.Cg: Capacity %1\n").arg(p0.PVT.Cg.size());
out << QString("PVT.miug: Capacity %1\n").arg(p0.PVT.miug.size());
out << QString("PVT.roug: Capacity %1\n").arg(p0.PVT.roug.size());
out << QString("PVT.Z: Capacity %1\n").arg(p0.PVT.Z.size());
out << QString("PVT.Rsw: Capacity %1\n").arg(p0.PVT.Rsw.size());
out << QString("PVT.Bw: Capacity %1\n").arg(p0.PVT.Bw.size());
out << QString("PVT.Cw: Capacity %1\n").arg(p0.PVT.Cw.size());
out << QString("PVT.miuw: Capacity %1\n").arg(p0.PVT.miuw.size());
out << QString("PVT.rouw: Capacity %1\n").arg(p0.PVT.rouw.size());
out << QString("PVT.V: Capacity %1\n").arg(p0.PVT.V.size());
out << QString("PVT.k_kinitial: Capacity %1\n").arg(p0.PVT.k_kinitial.size());
out << QString("PVT.Cf_Cfinitial: Capacity %1\n").arg(p0.PVT.Cf_Cfinitial.size());
out << QString("PVT.So: Capacity %1\n").arg(p0.PVT.So.size());
out << QString("PVT.Kro: Capacity %1\n").arg(p0.PVT.Kro.size());
out << QString("PVT.Sg: Capacity %1\n").arg(p0.PVT.Sg.size());
out << QString("PVT.Krg: Capacity %1\n").arg(p0.PVT.Krg.size());
out << QString("PVT.Sw: Capacity %1\n").arg(p0.PVT.Sw.size());
out << QString("PVT.Krw: Capacity %1\n").arg(p0.PVT.Krw.size());
// 7. Base Data
out << "\n--- Base Data ---\n";
out << QString("Base.Pi: %1\n").arg(p0.Base.Pi);
out << QString("Base.Cti: %1\n").arg(p0.Base.Cti);
out << QString("Base.Cf: %1\n").arg(p0.Base.Cf);
out << QString("Base.Soi: %1\n").arg(p0.Base.Soi);
out << QString("Base.Sgi: %1\n").arg(p0.Base.Sgi);
out << QString("Base.Swi: %1\n").arg(p0.Base.Swi);
out << QString("Base.k: Capacity %1\n").arg(p0.Base.k.size());
out << QString("Base.phi: Capacity %1\n").arg(p0.Base.phi.size());
out << QString("Base.h: Capacity %1\n").arg(p0.Base.h.size());
out << QString("Base.d: Capacity %1\n").arg(p0.Base.d);
out << QString("Base.dt_Max: Capacity %1\n").arg(p0.Base.dt_Max);
out << QString("Base.dt_Min: Capacity %1\n").arg(p0.Base.dt_Min);
out << "\n--- End of HX_NWTM_MODEL_INPUT Content ---\n";
file.close();
qDebug() << QString("Successfully saved HX_NWTM_MODEL_INPUT content to: %1").arg(filePath);
return true;
}