From bfbcfd2f3b58f479184acd532902acbdac70bfc1 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Tue, 28 Jul 2026 15:15:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=B0=94=E4=BA=95=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=9B=86=E6=8B=9F=E5=8E=8B=E5=8A=9B=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 接入当前气井的压力与拟压力转换表 - 仅在T5场景和数据集中保存转换表 - Runner将气井求解压力转换为拟压力后生成曲线 - 增加转换表完整性和单调性检查 --- .../nmData/nmDataAnalyzeContextProvider.h | 3 + Include/nmNum/nmData/nmDataAnalyzeManager.h | 4 + Include/nmNum/nmData/nmDataBinaryTools.h | 5 +- .../nmSubWnd/nmSubWndDataAnalyzeController.h | 3 + ML/Training/Runner/runner_main.cpp | 84 ++++++++++++++++++- ML/Training/Training/DatasetIO.cpp | 10 +++ ML/Training/Training/SceneIO.cpp | 7 +- ML/Training/Training/SceneIO.h | 2 + .../nmCalculation/nmCalculationPebiGrid.cpp | 18 ++++ Src/nmNum/nmData/nmDataAnalyzeManager.cpp | 23 +++++ Src/nmNum/nmData/nmDataBinaryTools.cpp | 10 ++- .../nmSubWndDataAnalyzeController.cpp | 77 +++++++++++++++++ 12 files changed, 242 insertions(+), 4 deletions(-) diff --git a/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h b/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h index 54661c4..28de168 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h +++ b/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h @@ -36,6 +36,9 @@ class NM_DATA_EXPORT nmDataAnalyzeContextProvider { // 获取Diffusion页面中指定子项的计算结果 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; }; diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h index 00e08b7..855ffab 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeManager.h +++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -317,6 +318,9 @@ class NM_DATA_EXPORT nmDataAnalyzeManager : public ZxDataObjectBin // 获取PEBI求解器需要的pvt数据 nmDataPvtParaForPebi* getPebiPvtPara(); + // 获取气单相变化 PVT 求解所需的压力/拟压力转换表。 + bool getPebiPseudoPressureTable(std::vector& pressure, + std::vector& pseudoPressure); // 获取混合结果参数 nmDataMixedResults* getMixedResults(); diff --git a/Include/nmNum/nmData/nmDataBinaryTools.h b/Include/nmNum/nmData/nmDataBinaryTools.h index 591c8a2..d6abef6 100644 --- a/Include/nmNum/nmData/nmDataBinaryTools.h +++ b/Include/nmNum/nmData/nmDataBinaryTools.h @@ -134,6 +134,9 @@ public: std::vector Krg; // 气相对渗透率 std::vector Sw; // 水饱和度 std::vector Krw; // 水相对渗透率 + // 气单相变化 PVT 使用的压力/拟压力转换表,两列按索引一一对应。 + std::vector pseudoPressureP; + std::vector pseudoPressurePs; } PVT; // Base储层参数 @@ -189,4 +192,4 @@ private: static const int DEFAULT_CHUNK_SIZE = 50000; }; -#endif // NMDATABINARYTOOLS_H \ No newline at end of file +#endif // NMDATABINARYTOOLS_H diff --git a/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h b/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h index 78624d1..1b26f1f 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h +++ b/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h @@ -31,6 +31,9 @@ class NM_SUB_WND_EXPORT nmSubWndDataAnalyzeController : public nmDataAnalyzeCont // 获取Diffusion页面中指定子项的计算结果 bool getDiffusionRstOf(void* pFitting, DiffusionSubOption dso, VVecDouble& vvec); + // 获取当前分析对象通过 mAlgPseudo 计算得到的稠密压力/拟压力转换表。 + bool getPseuRstOf(void* pFitting, VVecDouble& vvec); + // 获取当前结果窗口对应的数值结果保存目录 bool getSaveResultDir(void* pFitting, const QString& sRstCode, QString& sDir); diff --git a/ML/Training/Runner/runner_main.cpp b/ML/Training/Runner/runner_main.cpp index e9ad15f..d0108a6 100644 --- a/ML/Training/Runner/runner_main.cpp +++ b/ML/Training/Runner/runner_main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "pch.h" #include "DatasetIO.h" @@ -34,6 +35,69 @@ static std::string getExeDir() return path; } +static bool isFiniteNumber(double value) +{ + return _finite(value) != 0; +} + +static bool hasValidPseudoPressureTable(const PebiScene& scene) +{ + // 插值要求压力列严格单调,并且压力与拟压力逐点对应。 + const std::vector& p = scene.PVT.pseudoPressureP; + const std::vector& 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& p = scene.PVT.pseudoPressureP; + const std::vector& 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) { @@ -196,13 +260,23 @@ static bool computeLogLogCurves(RunnerResult& rr, const PebiScene& scene, const return true; // 没 dll:loglog 为空,但结构正确 } + const bool usePseudoPressure = (scene.solverType == 5); + if (usePseudoPressure && !hasValidPseudoPressureTable(scene)) { + return false; + } + for (unsigned int w = 0; w < rr.nWells; ++w) { std::vector wellPressureData; wellPressureData.resize(rr.nSteps); for (unsigned int i = 0; i < rr.nSteps; ++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; } @@ -280,6 +354,10 @@ static int runServer(const std::string& datasetPath, std::cerr << "ERROR: loadDataset failed\n"; 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 HX_NWTM_MODEL_INPUT modelInput = buildModelInputFromDataset(scene, gridOutput2); @@ -401,6 +479,10 @@ int main(int argc, char** argv) std::cerr << "ERROR: loadDataset failed\n"; 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); applySampledParamsAndMaybeOverrideRate(modelInput, params); diff --git a/ML/Training/Training/DatasetIO.cpp b/ML/Training/Training/DatasetIO.cpp index adda424..932306c 100644 --- a/ML/Training/Training/DatasetIO.cpp +++ b/ML/Training/Training/DatasetIO.cpp @@ -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.Sw)) 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 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.Sw)) 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 if (!readDouble(fs, s.Base.Pi)) return false; diff --git a/ML/Training/Training/SceneIO.cpp b/ML/Training/Training/SceneIO.cpp index e7b8a7d..6c23f0d 100644 --- a/ML/Training/Training/SceneIO.cpp +++ b/ML/Training/Training/SceneIO.cpp @@ -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.Sw)) 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 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; return true; -} \ No newline at end of file +} diff --git a/ML/Training/Training/SceneIO.h b/ML/Training/Training/SceneIO.h index 35fdbd0..0fd8dca 100644 --- a/ML/Training/Training/SceneIO.h +++ b/ML/Training/Training/SceneIO.h @@ -51,6 +51,8 @@ struct PebiScene { std::vector Rsw, Bw, Cw, miuw, rouw; std::vector V, k_kinitial, Cf_Cfinitial; std::vector So, Kro, Sg, Krg, Sw, Krw; + // 仅T5场景保存该压力/拟压力转换表。 + std::vector pseudoPressureP, pseudoPressurePs; } PVT; // Base 储层参数 diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp index 187d608..6682847 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp @@ -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 @@ -1028,6 +1045,7 @@ bool nmCalculationPebiGrid::generateOutputPara() // 3.5 PVT数据:常数PVT和变化PVT采用与真实求解器相同的填充方式。 nmDataPvtParaForPebi* pvt = dm->getPebiPvtPara(); fillScenePvtByModel(scene, solverModelType, pvt, pReservoirData); + fillScenePseudoPressureTable(scene, solverModelType); // 3.6 Base储层参数 if(pReservoirData) { diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 5dded9b..3e27c1f 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -2416,6 +2416,29 @@ nmDataPvtParaForPebi* nmDataAnalyzeManager::getPebiPvtPara() return m_pebiPvtPara; } +bool nmDataAnalyzeManager::getPebiPseudoPressureTable(std::vector& pressure, + std::vector& 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() { return m_pMixedResults; diff --git a/Src/nmNum/nmData/nmDataBinaryTools.cpp b/Src/nmNum/nmData/nmDataBinaryTools.cpp index 703040e..264f804 100644 --- a/Src/nmNum/nmData/nmDataBinaryTools.cpp +++ b/Src/nmNum/nmData/nmDataBinaryTools.cpp @@ -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.Sw)) { 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数据 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.Sw)) { 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数据 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); return true; -} \ No newline at end of file +} diff --git a/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp b/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp index 31da5e4..50c7248 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp @@ -1,12 +1,15 @@ #include "nmSubWndDataAnalyzeController.h" #include "iSubWndFitting.h" +#include "iAnalRun.h" #include "nmDataAnalyzeContext.h" #include "ZxBaseUtil.h" #include "ZxDataWell.h" #include "ZxRstWnd.h" #include "ZxSegmentInfo.h" +#include + namespace { // 将数据层保存的窗口指针还原为窗口层接口指针 @@ -15,6 +18,18 @@ iSubWndFitting* toFitting(void* pFitting) return static_cast(pFitting); } +typedef bool (*CalPseudoPressure)(double, double&, int); + +CalPseudoPressure getPseudoPressureConverter() +{ + static HMODULE module = LoadLibraryW(L"mAlgPseudo.dll"); + static CalPseudoPressure converter = module + ? reinterpret_cast(GetProcAddress( + module, "?calPS@iAlgPseuCaller@@SA_NNAANH@Z")) + : nullptr; + return converter; +} + // nmSubWnd模块加载时注册上下文提供者,避免在nmSubWndUtils中增加初始化代码 struct nmSubWndDataAnalyzeControllerRegistrar { nmSubWndDataAnalyzeControllerRegistrar() @@ -118,6 +133,68 @@ bool nmSubWndDataAnalyzeController::getDiffusionRstOf(void* pFitting, DiffusionS 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(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) {