支持气井数据集拟压力转换

- 接入当前气井的压力与拟压力转换表
- 仅在T5场景和数据集中保存转换表
- Runner将气井求解压力转换为拟压力后生成曲线
- 增加转换表完整性和单调性检查
feature/Model-20260625
lvjunjie 2 days ago
parent 5ef6a8d9c9
commit bfbcfd2f3b

@ -36,6 +36,9 @@ class NM_DATA_EXPORT nmDataAnalyzeContextProvider {
// 获取Diffusion页面中指定子项的计算结果 // 获取Diffusion页面中指定子项的计算结果
virtual bool getDiffusionRstOf(void* pFitting, DiffusionSubOption dso, VVecDouble& vvec) = 0; virtual bool getDiffusionRstOf(void* pFitting, DiffusionSubOption dso, VVecDouble& vvec) = 0;
// 获取当前分析对象通过 mAlgPseudo 计算得到的稠密压力/拟压力转换表。
virtual bool getPseuRstOf(void* pFitting, VVecDouble& vvec) = 0;
// 获取当前结果窗口对应的数值结果保存目录 // 获取当前结果窗口对应的数值结果保存目录
virtual bool getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir) = 0; virtual bool getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir) = 0;
}; };

@ -19,6 +19,7 @@
#include <QPair> #include <QPair>
#include <QString> #include <QString>
#include <QList> #include <QList>
#include <vector>
#include <vtkUnstructuredGrid.h> #include <vtkUnstructuredGrid.h>
#include <vtkSmartPointer.h> #include <vtkSmartPointer.h>
@ -317,6 +318,9 @@ class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin
// 获取PEBI求解器需要的pvt数据 // 获取PEBI求解器需要的pvt数据
nmDataPvtParaForPebi* getPebiPvtPara(); nmDataPvtParaForPebi* getPebiPvtPara();
// 获取气单相变化 PVT 求解所需的压力/拟压力转换表。
bool getPebiPseudoPressureTable(std::vector<double>& pressure,
std::vector<double>& pseudoPressure);
// 获取混合结果参数 // 获取混合结果参数
nmDataMixedResults* getMixedResults(); nmDataMixedResults* getMixedResults();

@ -134,6 +134,9 @@ public:
std::vector<double> Krg; // 气相对渗透率 std::vector<double> Krg; // 气相对渗透率
std::vector<double> Sw; // 水饱和度 std::vector<double> Sw; // 水饱和度
std::vector<double> Krw; // 水相对渗透率 std::vector<double> Krw; // 水相对渗透率
// 气单相变化 PVT 使用的压力/拟压力转换表,两列按索引一一对应。
std::vector<double> pseudoPressureP;
std::vector<double> pseudoPressurePs;
} PVT; } PVT;
// Base储层参数 // Base储层参数
@ -189,4 +192,4 @@ private:
static const int DEFAULT_CHUNK_SIZE = 50000; static const int DEFAULT_CHUNK_SIZE = 50000;
}; };
#endif // NMDATABINARYTOOLS_H #endif // NMDATABINARYTOOLS_H

@ -31,6 +31,9 @@ class NM_SUB_WND_EXPORT nmSubWndDataAnalyzeController : public nmDataAnalyzeCont
// 获取Diffusion页面中指定子项的计算结果 // 获取Diffusion页面中指定子项的计算结果
bool getDiffusionRstOf(void* pFitting, DiffusionSubOption dso, VVecDouble& vvec); bool getDiffusionRstOf(void* pFitting, DiffusionSubOption dso, VVecDouble& vvec);
// 获取当前分析对象通过 mAlgPseudo 计算得到的稠密压力/拟压力转换表。
bool getPseuRstOf(void* pFitting, VVecDouble& vvec);
// 获取当前结果窗口对应的数值结果保存目录 // 获取当前结果窗口对应的数值结果保存目录
bool getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir); bool getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir);

@ -6,6 +6,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
#include <float.h>
#include "pch.h" #include "pch.h"
#include "DatasetIO.h" #include "DatasetIO.h"
@ -34,6 +35,69 @@ static std::string getExeDir()
return path; return path;
} }
static bool isFiniteNumber(double value)
{
return _finite(value) != 0;
}
static bool hasValidPseudoPressureTable(const PebiScene& scene)
{
// 插值要求压力列严格单调,并且压力与拟压力逐点对应。
const std::vector<double>& p = scene.PVT.pseudoPressureP;
const std::vector<double>& ps = scene.PVT.pseudoPressurePs;
if (p.size() < 2 || p.size() != ps.size()) return false;
if (!isFiniteNumber(p[0]) || !isFiniteNumber(ps[0]) || p[0] == p[1]) return false;
const bool ascending = p[1] > p[0];
for (size_t i = 1; i < p.size(); ++i) {
if (!isFiniteNumber(p[i]) || !isFiniteNumber(ps[i])) return false;
if (ascending ? p[i] <= p[i - 1] : p[i] >= p[i - 1]) return false;
}
return true;
}
static bool interpolatePseudoPressure(const PebiScene& scene, double pressure, double& pseudoPressure)
{
// 使用相邻两个表格点做线性插值;超出表格范围时沿首尾区间线性外推。
if (!isFiniteNumber(pressure)) return false;
const std::vector<double>& p = scene.PVT.pseudoPressureP;
const std::vector<double>& ps = scene.PVT.pseudoPressurePs;
const bool ascending = p[1] > p[0];
// 二分查找第一个到达或越过目标压力的表格点。
size_t first = 0;
size_t count = p.size();
while (count > 0) {
const size_t step = count / 2;
const size_t index = first + step;
const bool before = ascending ? p[index] < pressure : p[index] > pressure;
if (before) {
first = index + 1;
count -= step + 1;
} else {
count = step;
}
}
size_t left = 0;
size_t right = 1;
if (first == 0) {
left = 0;
right = 1;
} else if (first >= p.size()) {
left = p.size() - 2;
right = p.size() - 1;
} else {
left = first - 1;
right = first;
}
const double ratio = (pressure - p[left]) / (p[right] - p[left]);
pseudoPressure = ps[left] + ratio * (ps[right] - ps[left]);
return isFiniteNumber(pseudoPressure);
}
// =============== 组装模型输入 =============== // =============== 组装模型输入 ===============
static HX_NWTM_MODEL_INPUT buildModelInputFromDataset(const PebiScene& scene, const HX_NWTM_GRID_OUTPUT2& gridOutput2) static HX_NWTM_MODEL_INPUT buildModelInputFromDataset(const PebiScene& scene, const HX_NWTM_GRID_OUTPUT2& gridOutput2)
{ {
@ -196,13 +260,23 @@ static bool computeLogLogCurves(RunnerResult& rr, const PebiScene& scene, const
return true; // 没 dllloglog 为空,但结构正确 return true; // 没 dllloglog 为空,但结构正确
} }
const bool usePseudoPressure = (scene.solverType == 5);
if (usePseudoPressure && !hasValidPseudoPressureTable(scene)) {
return false;
}
for (unsigned int w = 0; w < rr.nWells; ++w) for (unsigned int w = 0; w < rr.nWells; ++w)
{ {
std::vector<Point> wellPressureData; std::vector<Point> wellPressureData;
wellPressureData.resize(rr.nSteps); wellPressureData.resize(rr.nSteps);
for (unsigned int i = 0; i < rr.nSteps; ++i) { for (unsigned int i = 0; i < rr.nSteps; ++i) {
wellPressureData[i].x = rr.t[i]; wellPressureData[i].x = rr.t[i];
wellPressureData[i].y = rr.pw[w][i]; double pressureForLog = rr.pw[w][i];
if (usePseudoPressure
&& !interpolatePseudoPressure(scene, pressureForLog, pressureForLog)) {
return false;
}
wellPressureData[i].y = pressureForLog;
wellPressureData[i].z = 0.0; wellPressureData[i].z = 0.0;
} }
@ -280,6 +354,10 @@ static int runServer(const std::string& datasetPath,
std::cerr << "ERROR: loadDataset failed\n"; std::cerr << "ERROR: loadDataset failed\n";
return 30; return 30;
} }
if (scene.solverType == 5 && !hasValidPseudoPressureTable(scene)) {
std::cerr << "ERROR: T5 dataset has no valid gas pseudo-pressure table; rebuild scene and dataset\n";
return 31;
}
// 2) prepare base input once // 2) prepare base input once
HX_NWTM_MODEL_INPUT modelInput = buildModelInputFromDataset(scene, gridOutput2); HX_NWTM_MODEL_INPUT modelInput = buildModelInputFromDataset(scene, gridOutput2);
@ -401,6 +479,10 @@ int main(int argc, char** argv)
std::cerr << "ERROR: loadDataset failed\n"; std::cerr << "ERROR: loadDataset failed\n";
return 30; return 30;
} }
if (scene.solverType == 5 && !hasValidPseudoPressureTable(scene)) {
std::cerr << "ERROR: T5 dataset has no valid gas pseudo-pressure table; rebuild scene and dataset\n";
return 31;
}
HX_NWTM_MODEL_INPUT modelInput = buildModelInputFromDataset(scene, gridOutput2); HX_NWTM_MODEL_INPUT modelInput = buildModelInputFromDataset(scene, gridOutput2);
applySampledParamsAndMaybeOverrideRate(modelInput, params); applySampledParamsAndMaybeOverrideRate(modelInput, params);

@ -399,6 +399,11 @@ bool DatasetIO::writeScene(std::ofstream& fs, const PebiScene& s)
if (!writeStdVecD(fs, s.PVT.Krg)) return false; if (!writeStdVecD(fs, s.PVT.Krg)) return false;
if (!writeStdVecD(fs, s.PVT.Sw)) return false; if (!writeStdVecD(fs, s.PVT.Sw)) return false;
if (!writeStdVecD(fs, s.PVT.Krw)) return false; if (!writeStdVecD(fs, s.PVT.Krw)) return false;
// 只有气单相变化 PVT 的数据集需要附带压力/拟压力转换表。
if (s.solverType == 5) {
if (!writeStdVecD(fs, s.PVT.pseudoPressureP)) return false;
if (!writeStdVecD(fs, s.PVT.pseudoPressurePs)) return false;
}
// Base // Base
if (!writeDouble(fs, s.Base.Pi)) return false; if (!writeDouble(fs, s.Base.Pi)) return false;
@ -494,6 +499,11 @@ bool DatasetIO::readScene(std::ifstream& fs, PebiScene& s)
if (!readStdVecD(fs, s.PVT.Krg)) return false; if (!readStdVecD(fs, s.PVT.Krg)) return false;
if (!readStdVecD(fs, s.PVT.Sw)) return false; if (!readStdVecD(fs, s.PVT.Sw)) return false;
if (!readStdVecD(fs, s.PVT.Krw)) return false; if (!readStdVecD(fs, s.PVT.Krw)) return false;
// 写入和读取必须使用相同条件,保证后续字段的二进制位置一致。
if (s.solverType == 5) {
if (!readStdVecD(fs, s.PVT.pseudoPressureP)) return false;
if (!readStdVecD(fs, s.PVT.pseudoPressurePs)) return false;
}
// Base // Base
if (!readDouble(fs, s.Base.Pi)) return false; if (!readDouble(fs, s.Base.Pi)) return false;

@ -205,6 +205,11 @@ bool SceneIO::loadScene(const std::string& filename, PebiScene& scene)
if (!readVector1D(fs, scene.PVT.Krg)) return false; if (!readVector1D(fs, scene.PVT.Krg)) return false;
if (!readVector1D(fs, scene.PVT.Sw)) return false; if (!readVector1D(fs, scene.PVT.Sw)) return false;
if (!readVector1D(fs, scene.PVT.Krw)) return false; if (!readVector1D(fs, scene.PVT.Krw)) return false;
// T5 Runner 使用该表把求解压力转换为拟压力,其他相态不读取这两个字段。
if (scene.solverType == 5) {
if (!readVector1D(fs, scene.PVT.pseudoPressureP)) return false;
if (!readVector1D(fs, scene.PVT.pseudoPressurePs)) return false;
}
// Base // Base
if (!readDouble(fs, scene.Base.Pi)) return false; if (!readDouble(fs, scene.Base.Pi)) return false;
@ -223,4 +228,4 @@ bool SceneIO::loadScene(const std::string& filename, PebiScene& scene)
if (!readDouble(fs, scene.Base.h_ref)) return false; if (!readDouble(fs, scene.Base.h_ref)) return false;
return true; return true;
} }

@ -51,6 +51,8 @@ struct PebiScene {
std::vector<double> Rsw, Bw, Cw, miuw, rouw; std::vector<double> Rsw, Bw, Cw, miuw, rouw;
std::vector<double> V, k_kinitial, Cf_Cfinitial; std::vector<double> V, k_kinitial, Cf_Cfinitial;
std::vector<double> So, Kro, Sg, Krg, Sw, Krw; std::vector<double> So, Kro, Sg, Krg, Sw, Krw;
// 仅T5场景保存该压力/拟压力转换表。
std::vector<double> pseudoPressureP, pseudoPressurePs;
} PVT; } PVT;
// Base 储层参数 // Base 储层参数

@ -136,6 +136,23 @@ void fillScenePvtByModel(nmDataBinaryTools::NM_PEBI_SCENE& scene,
} }
} }
void fillScenePseudoPressureTable(nmDataBinaryTools::NM_PEBI_SCENE& scene,
NM_SOLVER_MODEL_TYPE modelType)
{
// 只有T5需要把当前井的转换表随场景交给离线Runner。
if(modelType != SMT_Gas_VariablePvt) {
return;
}
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
if(dataManager == nullptr
|| !dataManager->getPebiPseudoPressureTable(scene.PVT.pseudoPressureP,
scene.PVT.pseudoPressurePs)) {
qWarning() << "Gas pseudo-pressure table is unavailable; T5 Runner data cannot be generated from this scene.";
return;
}
}
} }
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -1028,6 +1045,7 @@ bool nmCalculationPebiGrid::generateOutputPara()
// 3.5 PVT数据常数PVT和变化PVT采用与真实求解器相同的填充方式。 // 3.5 PVT数据常数PVT和变化PVT采用与真实求解器相同的填充方式。
nmDataPvtParaForPebi* pvt = dm->getPebiPvtPara(); nmDataPvtParaForPebi* pvt = dm->getPebiPvtPara();
fillScenePvtByModel(scene, solverModelType, pvt, pReservoirData); fillScenePvtByModel(scene, solverModelType, pvt, pReservoirData);
fillScenePseudoPressureTable(scene, solverModelType);
// 3.6 Base储层参数 // 3.6 Base储层参数
if(pReservoirData) { if(pReservoirData) {

@ -2416,6 +2416,29 @@ nmDataPvtParaForPebi* nmDataAnalyzeManager::getPebiPvtPara()
return m_pebiPvtPara; return m_pebiPvtPara;
} }
bool nmDataAnalyzeManager::getPebiPseudoPressureTable(std::vector<double>& pressure,
std::vector<double>& pseudoPressure)
{
pressure.clear();
pseudoPressure.clear();
nmDataAnalyzeContextProvider* context = nmDataAnalyzeContext::provider();
iSubWndFitting* fitting = nmDataAnalyzeManager::getCurrentFitting();
VVecDouble pseudoResult;
if(context == nullptr
|| fitting == nullptr
|| !context->getPseuRstOf(fitting, pseudoResult)
|| pseudoResult.size() < 2
|| pseudoResult[0].size() < 2
|| pseudoResult[0].size() != pseudoResult[1].size()) {
return false;
}
pressure = pseudoResult[0].toStdVector();
pseudoPressure = pseudoResult[1].toStdVector();
return true;
}
nmDataMixedResults* nmDataAnalyzeManager::getMixedResults() nmDataMixedResults* nmDataAnalyzeManager::getMixedResults()
{ {
return m_pMixedResults; return m_pMixedResults;

@ -413,6 +413,10 @@ bool nmDataBinaryTools::savePebiSceneBin(const QString& filename, const NM_PEBI_
if(!writeVector1D(*ds, s.PVT.Krg)) { closeFile(ds,file); return false; } if(!writeVector1D(*ds, s.PVT.Krg)) { closeFile(ds,file); return false; }
if(!writeVector1D(*ds, s.PVT.Sw)) { closeFile(ds,file); return false; } if(!writeVector1D(*ds, s.PVT.Sw)) { closeFile(ds,file); return false; }
if(!writeVector1D(*ds, s.PVT.Krw)) { closeFile(ds,file); return false; } if(!writeVector1D(*ds, s.PVT.Krw)) { closeFile(ds,file); return false; }
if(s.solverType == 5) {
if(!writeVector1D(*ds, s.PVT.pseudoPressureP)) { closeFile(ds,file); return false; }
if(!writeVector1D(*ds, s.PVT.pseudoPressurePs)) { closeFile(ds,file); return false; }
}
// Base数据 // Base数据
if(!writeDouble(*ds, s.Base.Pi)) { closeFile(ds,file); return false; } if(!writeDouble(*ds, s.Base.Pi)) { closeFile(ds,file); return false; }
@ -521,6 +525,10 @@ bool nmDataBinaryTools::loadPebiSceneBin(const QString& filename, NM_PEBI_SCENE&
if(!readVector1D(*ds, s.PVT.Krg)) { closeFile(ds,file); return false; } if(!readVector1D(*ds, s.PVT.Krg)) { closeFile(ds,file); return false; }
if(!readVector1D(*ds, s.PVT.Sw)) { closeFile(ds,file); return false; } if(!readVector1D(*ds, s.PVT.Sw)) { closeFile(ds,file); return false; }
if(!readVector1D(*ds, s.PVT.Krw)) { closeFile(ds,file); return false; } if(!readVector1D(*ds, s.PVT.Krw)) { closeFile(ds,file); return false; }
if(s.solverType == 5) {
if(!readVector1D(*ds, s.PVT.pseudoPressureP)) { closeFile(ds,file); return false; }
if(!readVector1D(*ds, s.PVT.pseudoPressurePs)) { closeFile(ds,file); return false; }
}
// Base数据 // Base数据
if(!readDouble(*ds, s.Base.Pi)) { closeFile(ds,file); return false; } if(!readDouble(*ds, s.Base.Pi)) { closeFile(ds,file); return false; }
@ -538,4 +546,4 @@ bool nmDataBinaryTools::loadPebiSceneBin(const QString& filename, NM_PEBI_SCENE&
closeFile(ds, file); closeFile(ds, file);
return true; return true;
} }

@ -1,12 +1,15 @@
#include "nmSubWndDataAnalyzeController.h" #include "nmSubWndDataAnalyzeController.h"
#include "iSubWndFitting.h" #include "iSubWndFitting.h"
#include "iAnalRun.h"
#include "nmDataAnalyzeContext.h" #include "nmDataAnalyzeContext.h"
#include "ZxBaseUtil.h" #include "ZxBaseUtil.h"
#include "ZxDataWell.h" #include "ZxDataWell.h"
#include "ZxRstWnd.h" #include "ZxRstWnd.h"
#include "ZxSegmentInfo.h" #include "ZxSegmentInfo.h"
#include <Windows.h>
namespace { namespace {
// 将数据层保存的窗口指针还原为窗口层接口指针 // 将数据层保存的窗口指针还原为窗口层接口指针
@ -15,6 +18,18 @@ iSubWndFitting* toFitting(void* pFitting)
return static_cast<iSubWndFitting*>(pFitting); return static_cast<iSubWndFitting*>(pFitting);
} }
typedef bool (*CalPseudoPressure)(double, double&, int);
CalPseudoPressure getPseudoPressureConverter()
{
static HMODULE module = LoadLibraryW(L"mAlgPseudo.dll");
static CalPseudoPressure converter = module
? reinterpret_cast<CalPseudoPressure>(GetProcAddress(
module, "?calPS@iAlgPseuCaller@@SA_NNAANH@Z"))
: nullptr;
return converter;
}
// nmSubWnd模块加载时注册上下文提供者避免在nmSubWndUtils中增加初始化代码 // nmSubWnd模块加载时注册上下文提供者避免在nmSubWndUtils中增加初始化代码
struct nmSubWndDataAnalyzeControllerRegistrar { struct nmSubWndDataAnalyzeControllerRegistrar {
nmSubWndDataAnalyzeControllerRegistrar() nmSubWndDataAnalyzeControllerRegistrar()
@ -118,6 +133,68 @@ bool nmSubWndDataAnalyzeController::getDiffusionRstOf(void* pFitting, DiffusionS
return pSubWndFitting->getDiffusionRstOf(dso, vvec); return pSubWndFitting->getDiffusionRstOf(dso, vvec);
} }
bool nmSubWndDataAnalyzeController::getPseuRstOf(void* pFitting, VVecDouble& vvec)
{
// 先按当前井和相态配置拟压力算法,确保转换规则与自动拟合界面一致。
iSubWndFitting* pSubWndFitting = toFitting(pFitting);
if(pSubWndFitting == nullptr) {
return false;
}
iAnalRun* pAnalRun = pSubWndFitting->getAnalRun();
if(pAnalRun == nullptr
|| !pAnalRun->configPsAbouts(true,
pSubWndFitting->getModelOption(),
false,
pSubWndFitting->getAllWxPtr())) {
return false;
}
VVecDouble source;
if(!pSubWndFitting->getPseuRstOf(source)
|| source.size() < 2
|| source[0].size() < 2) {
return false;
}
double minPressure = source[0][0];
double maxPressure = source[0][0];
for(int i = 1; i < source[0].size(); ++i) {
minPressure = qMin(minPressure, source[0][i]);
maxPressure = qMax(maxPressure, source[0][i]);
}
if(minPressure >= maxPressure) {
return false;
}
// 在原始压力范围内建立稠密转换表Runner 后续通过线性插值转换每个求解压力点。
const int sampleCount = 4096;
CalPseudoPressure converter = getPseudoPressureConverter();
if(converter == nullptr) {
return false;
}
VecDouble pressureTable;
VecDouble pseudoPressureTable;
pressureTable.reserve(sampleCount);
pseudoPressureTable.reserve(sampleCount);
for(int i = 0; i < sampleCount; ++i) {
const double ratio = static_cast<double>(i) / (sampleCount - 1);
const double pressure = minPressure + ratio * (maxPressure - minPressure);
double pseudoPressure = 0.0;
if(!converter(pressure, pseudoPressure, -1)) {
return false;
}
pressureTable.append(pressure);
pseudoPressureTable.append(pseudoPressure);
}
vvec.clear();
vvec.append(pressureTable);
vvec.append(pseudoPressureTable);
return true;
}
// 组装当前井和当前结果窗口对应的数值结果保存目录 // 组装当前井和当前结果窗口对应的数值结果保存目录
bool nmSubWndDataAnalyzeController::getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir) bool nmSubWndDataAnalyzeController::getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir)
{ {

Loading…
Cancel
Save