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...

1210 lines
49 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 "nmDataAnalyzeManager.h"
#include "nmDataPvtParaForPebi.h"
#include <QDebug>
#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 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]);
}
/// @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,
QObject *parent):
QThread(parent),
m_sPostprocessingDir(sPostprocessingDir)
{
}
nmCalculationDllPebiSolverTask::~nmCalculationDllPebiSolverTask()
{
if(isRunning()) {
terminate();
wait(3000);
}
}
void nmCalculationDllPebiSolverTask::run()
{
bool result = this->execute();
emit sig_calculateDone(result);
}
bool nmCalculationDllPebiSolverTask::execute()
{
return this->execPebiMode();
}
bool nmCalculationDllPebiSolverTask::execPebiMode()
{
nmDataAnalyzeManager* pDataInstance = nmDataAnalyzeManager::getCurrentInstance();
nmCalculationPebiGrid* pGridInstance = nmCalculationPebiGrid::getInstance();
if(pDataInstance == nullptr || pGridInstance == nullptr) {
return false;
}
// 载入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);
// 获取函数地址
HX_NWTM_MODEL_Fun HX_NWTM_MODEL = (HX_NWTM_MODEL_Fun)GetProcAddress(dll, "HX_NWTM_MODEL");
if(HX_NWTM_MODEL) {
if(pGridInstance->getGridOutput1().PEBI_cell.p.size() <= 0
|| pDataInstance->getCalculationWells().isEmpty()) {
// 网格输出存在但井顺序为空时重新生成p2并同步当前DataManager的计算井顺序
if(!pGridInstance->generateOutputPara()) {
FreeLibrary(dll);
return false;
}
}
// 参数定义,使用默认参数
HX_NWTM_MODEL_OUTPUT p1;
HX_NWTM_MODEL_INPUT p0(pGridInstance->getGridOutput2());
nmDataReservoir* pReservoirData = pDataInstance->getReservoirData();
// 根据PVT参数获取结果选择求解器模型类型
p0.T = static_cast<int>(pDataInstance->getSolverModelType());
// 获取PEBI网格存入井的顺序
QVector<QPair<NM_WELL_MODEL, QString>> vecWellsOrder = pDataInstance->getCalculationWells();
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) {
NM_WELL_MODEL wellType = vecWellsOrder[i].first; // 获取井的类型
QString wellName = vecWellsOrder[i].second; // 获取井的名称
// 跳过裂缝
if(wellType == NM_WELL_MODEL::Unknow_Well) {
// 清空裂缝的数据,因为头文件内部有五口井的数据,会传递给裂缝
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;
}
// 处理直井
if(wellType == NM_WELL_MODEL::Vertical_Well) {
nmDataVerticalWell* pWellData = dynamic_cast<nmDataVerticalWell*>(pDataInstance->findWellByName(wellName));
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*>(pDataInstance->findWellByName(wellName));
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*>(pDataInstance->findWellByName(wellName));
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());
}
}
// 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 = pGridInstance->getGridOutput2().Trinodexy.size();
p0.Base.k = dVec1(cellCount, pReservoirData->getPermeability().getValue().toDouble());
p0.Base.phi = dVec1(cellCount, pReservoirData->getPorosity().getValue().toDouble());
p0.Base.h = dVec1(cellCount, pReservoirData->getThickness().getValue().toDouble());
}
// 获取时间步长
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 = nmDataAnalyzeManager::getCurrentInstance();
QString licensePath = pDataManager->getLicensePath();
std::string licensePathStd = licensePath.toStdString();
HX_NWTM_MODEL(p1, p0, licensePathStd);
} 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;
}
// 保存计算结果
bool success = this->savePebiModeResult(p1);
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::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;
// 确保 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 == nullptr) {
continue;
}
{
// 准备压力数据 (用于传递给外部 DLL)
std::vector<Point> wellPressureDataForDll;
if(wellIdx < p1.pw.size()) {
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
Point pt;
if(i < p1.t.size()) {
pt.x = p1.t[i];
} else {
pt.x = 0.0; // 或者进行错误处理
}
pt.y = p1.pw[wellIdx][i];
pt.z = 0.0; // 假设这里的 z 是 0
wellPressureDataForDll.push_back(pt);
}
}
// 准备流量段数据
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
int nTimeNumQ = 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();
HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll");
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;
}
preLogFun(wellPressureDataForDll, iSectionFlowIndex, timeQ.data(), q.data(), nTimeNumQ, logPreResultFromDll);
// 填充双对数曲线数据到局部变量
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);
}
}
// 将计算结果保存到对应的井数据里
// 压力
pWellData->setResultPressure(vvecPressure);
// 双对数
pWellData->setResultLogLog(vvecLogLog);
// 半对数
pWellData->setResultSemiLog(vvecSemiLog);
// 存储当前井名称和二维位置到映射
QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble());
pDataInstance->addWellLocation(sWellName, ptWellCoords);
}
// 计算有效单元数量
nmCalculationPebiGrid* pGridInstance = nmCalculationPebiGrid::getInstance();
HX_NWTM_GRID_OUTPUT1 P1_grid = pGridInstance->getGridOutput1();
size_t actualPlotCellsCount = 0;
for(size_t i = 0; i < P1_grid.PEBI_cell.isplot.size(); ++i) {
if(P1_grid.PEBI_cell.isplot[i] == 1) {
actualPlotCellsCount++;
}
}
// 在保存时间步数据前,先计算标量范围
double dMinP = DBL_MAX;
double dMaxP = -DBL_MAX;
// 4. 为每个时间步保存压力场到 nmDataAnalyzeManager 的 QMap 中
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 < P1_grid.PEBI_cell.isplot.size(); ++i) {
if(P1_grid.PEBI_cell.isplot[i] == 1) { // 只有当 isplot 为1时才考虑这个单元格
if(timeIdx < p1.p.size() && i < p1.p[timeIdx].size()) {
pressureData->SetValue(destIdx, p1.p[timeIdx][i]);
// 更新当前时间步的范围
dCurrentTimeMin = qMin(dCurrentTimeMin, p1.p[timeIdx][i]);
dCurrentTimeMax = qMax(dCurrentTimeMax, p1.p[timeIdx][i]);
} else {
// 处理数据不匹配的情况,例如设置一个默认值或报错
qWarning() << "Data mismatch: timeIdx or cell index out of bounds for time step" << timeIdx << " cell " << i;
pressureData->SetValue(destIdx, 0.0); // 设置一个默认值
}
destIdx++;
}
}
// 更新全局范围
dMinP = qMin(dMinP, dCurrentTimeMin);
dMaxP = qMax(dMaxP, dCurrentTimeMax);
// 将时间与对应的压力数据存储到 nmDataAnalyzeManager
pDataInstance->addTimeStep(currentTime, pressureData);
}
// 保存全局范围到管理器
pDataInstance->setScalarRangeP(dMinP, dMaxP);
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;
}