diff --git a/3rd/FFmpeg-1.1/DELIVERY-zh-CN.txt b/3rd/FFmpeg-1.1/DELIVERY-zh-CN.txt index 3dcf802d..7e0ea29f 100644 --- a/3rd/FFmpeg-1.1/DELIVERY-zh-CN.txt +++ b/3rd/FFmpeg-1.1/DELIVERY-zh-CN.txt @@ -49,12 +49,9 @@ include 和 compat。直接交付整个目录已经覆盖这些内容。 lib MSVC x86 导入库 bin 运行库及自动部署来源 -nmSubWxs 工程已经默认启用以下构建配置: - - CONFIG += nm_ffmpeg_video_export - -启用后,qmake 会检查头文件、导入库、兼容头和四个运行库;链接完成后 -自动将四个 DLL 复制到当前 Debug 或 Release 输出目录。 +nmSubWxs 工程通过 Src4/ffmpeg.pri 默认启用FFmpeg视频导出并链接上述 +四个导入库。当前工程不在链接完成后自动复制DLL;制作开发或运行环境时, +应将四个运行DLL分别预置到 Bin/Debug 和 Bin/Release 中。 四、不要混入的旧文件 -------------------- diff --git a/3rd/Pebi/bin/HX_NWTM.dll b/3rd/Pebi/bin/HX_NWTM.dll index 7da5d2de..9dccdc2c 100644 Binary files a/3rd/Pebi/bin/HX_NWTM.dll and b/3rd/Pebi/bin/HX_NWTM.dll differ diff --git a/3rd/Pebi/include/accelwt_solver_api.h b/3rd/Pebi/include/accelwt_solver_api.h new file mode 100644 index 00000000..2ee7bccb --- /dev/null +++ b/3rd/Pebi/include/accelwt_solver_api.h @@ -0,0 +1,128 @@ +#pragma once + +#ifdef _WIN32 +# ifdef ACCELWT_DLL_EXPORTS +# define ACCELWT_API __declspec(dllexport) +# else +# define ACCELWT_API __declspec(dllimport) +# endif +#else +# define ACCELWT_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum AccelWTStatus +{ + ACCELWT_STATUS_SUCCESS = 0, + ACCELWT_STATUS_INVALID_ARGUMENT = 1, + ACCELWT_STATUS_CREATE_FAILED = 2, + ACCELWT_STATUS_SETUP_FAILED = 3, + ACCELWT_STATUS_SOLVE_FAILED = 4, + ACCELWT_STATUS_NOT_CONVERGED = 5, + ACCELWT_STATUS_UNSUPPORTED_BACKEND = 6 +}; + +typedef struct AccelWTSolveOptions +{ + int max_iter; + double rel_tol; + double abs_tol; + int ilu_jacobi_iters; + int omp_threads; + int use_initial_guess; +} AccelWTSolveOptions; + +typedef struct AccelWTSolveInfo +{ + int iterations; + double final_rel_residual; + double setup_seconds; + int status; +} AccelWTSolveInfo; + +typedef struct AccelWTDatasetOptions +{ + const char *dataset_dir; + int lagged_ilu_k; + int lagged_ilu_max_iters; + int solve_omp_threads; + int host_residual_interval; + int print_progress; +} AccelWTDatasetOptions; + +typedef struct AccelWTDatasetSummary +{ + int num_steps; + int total_solves; + int early_exit_steps; + int ilu_refresh_steps; + int ilu_lagged_reuse_steps; + int not_converged_steps; + long long total_iterations; + double time_read_t; + double time_read_b; + double time_read_a; + double time_csr; + double time_create; + double time_prepare_step; + double time_solve; + double time_total; + double avg_prepare_ms; + double avg_solve_ms; + int status; +} AccelWTDatasetSummary; + +ACCELWT_API const char *accelwt_backend_name(void); + +ACCELWT_API void accelwt_default_options(AccelWTSolveOptions *options); + +ACCELWT_API void accelwt_default_dataset_options(AccelWTDatasetOptions *options); + +ACCELWT_API int accelwt_solve_csr(int n, + int nnz, + const int *row_ptr, + const int *col_ind, + const double *values, + const double *b, + double *x, + const AccelWTSolveOptions *options, + AccelWTSolveInfo *info); + +ACCELWT_API int accelwt_solve_csr_cpu(int n, + int nnz, + const int *row_ptr, + const int *col_ind, + const double *values, + const double *b, + double *x, + const AccelWTSolveOptions *options, + AccelWTSolveInfo *info); + +ACCELWT_API int accelwt_solve_csr_cuda(int n, + int nnz, + const int *row_ptr, + const int *col_ind, + const double *values, + const double *b, + double *x, + const AccelWTSolveOptions *options, + AccelWTSolveInfo *info); + +ACCELWT_API int accelwt_solve_dataset_cpu(const char *dataset_dir, + const AccelWTDatasetOptions *options, + AccelWTDatasetSummary *summary); + +ACCELWT_API int accelwt_solve_dataset_cuda(const char *dataset_dir, + const AccelWTDatasetOptions *options, + AccelWTDatasetSummary *summary); + +ACCELWT_API int accelwt_solve_dataset(const char *dataset_dir, + const AccelWTDatasetOptions *options, + AccelWTDatasetSummary *summary); + +#ifdef __cplusplus +} +#endif diff --git a/3rd/Pebi/include/pch.h b/3rd/Pebi/include/pch.h index dba643a3..2c49f2ef 100644 --- a/3rd/Pebi/include/pch.h +++ b/3rd/Pebi/include/pch.h @@ -14,13 +14,12 @@ #include #include #include +#include "accelwt_solver_api.h" #include "accelwt_cpu.h" #include - #ifndef M_PI const double M_PI = acos(-1.0); #endif - typedef std::vector>>dVec3; //ÈýάÊý×é:double typedef std::vector>dVec2; //¶þάÊý×é:double typedef std::vector>iVec2; //¶þάÊý×é:int @@ -169,7 +168,7 @@ struct HX_NWTM_GRID_INPUT d[0] = -200; d[1] = -200; d[2] = 200; d[3] = -200; d[4] = 0.05; d[5] = 0; FractureVerticalWell[0] = d; MultistageFracturedHorizontalWell.resize(1); - MultistageFracturedHorizontalWell[0].resize(3, dVec1(5)); + MultistageFracturedHorizontalWell[0].resize(3, dVec1(6)); d[0] = -600; d[1] = 600; d[2] = -400; d[3] = 600; d[4] = 0.1; d[5] = 0; MultistageFracturedHorizontalWell[0][0] = d; d[0] = -600; d[1] = 400; d[2] = -400; d[3] = 400; d[4] = 0.1; d[5] = 0; MultistageFracturedHorizontalWell[0][1] = d; d[0] = -600; d[1] = 200; d[2] = -400; d[3] = 200; d[4] = 0.1; d[5] = 0; MultistageFracturedHorizontalWell[0][2] = d; @@ -473,13 +472,19 @@ struct HX_NWTM_MODEL_OUTPUT ~HX_NWTM_MODEL_OUTPUT() {} }; +struct HX_PLOT_DATA +{ + dVec2 loglog; //˫¶ÔÊýÊý¾Ýloglog[0]:ʱ¼ä²loglog[1]:ѹÁ¦²loglog[2]:ѹÁ¦²Êý + dVec2 semilog; //°ë¶ÔÊýÊý¾Ýsemilog[0]:ʱ¼ä²semilog[1]:ѹÁ¦ +}; + HX_API void HX_NWTM_GRID(HX_NWTM_GRID_OUTPUT1& p1, HX_NWTM_GRID_OUTPUT2& p2, const HX_NWTM_GRID_INPUT& p0, std::string LIC); //ÊýֵÊԾ®Íø¸ñ½ӿÚ HX_API void HX_NWTM_KRINGING(HX_KRING_OUTPUT& p1, const HX_KRING_INPUT p0, std::string LIC); //ÊýֵÊԾ®·ǾùÖÊÐԼÆËã½ӿÚ HX_API void HX_NWTM_MODEL(HX_NWTM_MODEL_OUTPUT& p1, const HX_NWTM_MODEL_INPUT& p0, std::string LIC); //ÊýֵÊԾ®ģÐÍÇó½âÆ÷½ӿÚ +HX_API void HX_NWTM_PLOT(HX_PLOT_DATA& res, const dVec2& Rate, const dVec2& Press, const double tmin, const double tmax, std::string LIC);//ÊýֵÊԾ®»æͼ½ӿÚ HX_API void set_omp_threads(int n); HX_API void set_ilu_reuse_steps(int n); HX_API int getPEBInum(); HX_API int getsolvetime(); HX_API void set_solvetype(int n); - diff --git a/3rd/Pebi/lib/AccelWT_CPU.lib b/3rd/Pebi/lib/AccelWT_CPU.lib new file mode 100644 index 00000000..06ee83a7 Binary files /dev/null and b/3rd/Pebi/lib/AccelWT_CPU.lib differ diff --git a/3rd/Pebi/lib/HX_NWTM.lib b/3rd/Pebi/lib/HX_NWTM.lib new file mode 100644 index 00000000..d44704f3 Binary files /dev/null and b/3rd/Pebi/lib/HX_NWTM.lib differ diff --git a/3rd/Pebi/src/main.cpp b/3rd/Pebi/src/main.cpp index de701d61..ffa917e3 100644 --- a/3rd/Pebi/src/main.cpp +++ b/3rd/Pebi/src/main.cpp @@ -148,7 +148,7 @@ int main() std::cout << solvetypename[type] << "\n"; int threads ; if (solvetype[type] == 1) { - threads = 5; + threads = 1; } else if (solvetype[type] == 2) { threads = 1; @@ -157,7 +157,7 @@ int main() { set_omp_threads(omp_threads[thr]); std::cout << omp_threads[thr] << "Ï̲߳âÊÔ\n"; - for (int iiii = 0; iiii < 8; ++iiii) + for (int iiii = 0; iiii < 1; ++iiii) { welltype = WT[iiii]; flowtype = FT[iiii]; @@ -442,7 +442,7 @@ int main() p3.CS.C.resize(1); p3.CS.C[0] = 0.1; p3.CS.S.resize(1); - p3.CS.S[0] = 0.1; + p3.CS.S[0] = -0.87; p3.PVT.p = dVec1(200, 0); for (int i = 0; i < 200; ++i) { p3.PVT.p[i] = (i + 1.0); } p3.PVT.Bw = dVec1(200, 1.05);//ËùÓÐÊýΪһ¸öֵ p3.PVT.miuw = dVec1(200, 0.8);//ËùÓÐÊýΪһ¸öֵ @@ -624,18 +624,18 @@ int main() std::cout << " " << name[iiii] << ":" << getPEBInum() << "," << getsolvetime() << "ms\n"; //Êý¾ݵ¼³ö - /*dVec2 pwf; - for (int i = 0; i < p4.t.size(); ++i) { - dVec1 a; - a.resize(p4.pw.size() + 1); - a[0] = p4.t[i]; - for (int j = 1; j < a.size(); ++j) { - a[j] = p4.pw[j - 1][i]; - } - pwf.push_back(a); - } - std::string filename = solvetypename[type] + name[iiii] + "pwf.csv"; - Write2DVectorToCSV(pwf, filename);*/ + //dVec2 pwf; + //for (int i = 0; i < p4.t.size(); ++i) { + // dVec1 a; + // a.resize(p4.pw.size() + 1); + // a[0] = p4.t[i]; + // for (int j = 1; j < a.size(); ++j) { + // a[j] = p4.pw[j - 1][i]; + // } + // pwf.push_back(a); + //} + //std::string filename = solvetypename[type] + name[iiii] + "pwf.csv"; + //Write2DVectorToCSV(pwf, filename); /* Write1DVectorToCSV(p4.t, "t.csv"); Write2DVectorToCSV(p4.pw, "pw.csv"); diff --git a/Include/nmNum/nmData/nmDataUtils.h b/Include/nmNum/nmData/nmDataUtils.h index 1c5d7292..29994cc1 100644 --- a/Include/nmNum/nmData/nmDataUtils.h +++ b/Include/nmNum/nmData/nmDataUtils.h @@ -2,9 +2,11 @@ #include "ZxDataObject.h" #include "nmData_global.h" +#include class ZxDataProject; class ZxDataWell; +class QMutex; // 数值试井数据对象如何进行初始加载的方式 class NM_DATA_EXPORT nmDataUtils : public QObject @@ -23,6 +25,28 @@ public: static bool deleteChildrenOfWell(ZxDataWell* pDataWell); static QStringList readNmDataFile(const QString &filePath); + + /** @brief 返回所有 HX_NWTM.dll 接口共用的进程级互斥锁。 */ + static QMutex* getHxNwtmDllMutex(); + + /** + * @brief 调用 HX_NWTM_PLOT 生成双对数和半对数曲线。 + * + * flowDurations 保存各流动段持续时间;flowSectionIndex 是一基索引。 + * 本方法会把当前流动段换算成累计的 tmin/tmax,并统一完成 DLL 加载、 + * 接口探测、输入转换和输出校验。 + */ + static bool calculateHxNwtmPlot( + const QVector& flowDurations, + const QVector& rates, + const QVector& pressureTimes, + const QVector& pressures, + int flowSectionIndex, + const QString& licensePath, + QVector >& logLog, + QVector >& semiLog, + bool* interfaceAvailable = 0, + QString* errorMessage = 0); }; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 40c387fe..c0b61d6e 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -1,5 +1,4 @@ #include "nmCalculationDllPebiSolverTask.h" -#include "singlePhaseSolver.h" #include "zxLogInstance.h" #include "nmDataAnalyzeManager.h" #include "nmDataWellBase.h" @@ -14,6 +13,7 @@ #include "nmDataFracture.h" #include "nmDataFault.h" #include "nmDataTimeStepSetting.h" +#include "nmDataUtils.h" #include "nmPebiResultSnapshotBuilder.h" #include "nmPebiCellMapping.h" #include "nmPebiStaticPropertyCalculator.h" @@ -224,9 +224,9 @@ bool captureResultWellMetadata( return false; } - // 发布结果前用当前配置重算气井历史曲线,替换旧项目中未转拟压力的缓存。 - if(pDataManager->getSolverModelType() == SMT_Gas_VariablePvt - && !pDataManager->updateWellHistoryData(pWellData)) { + // 正式求解前统一重算历史双对数和半对数曲线,确保油井、水井和气井的 + // 曲线签名都与本次输入一致。 + if(!pDataManager->updateWellHistoryData(pWellData)) { return false; } @@ -578,31 +578,6 @@ bool buildModelInputFromSnapshot( 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 是主界面拟压力算法所在模块. @@ -1649,16 +1624,15 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( vvecSemiLog.clear(); if(bRateControlled) { - // 准备压力数据 (用于传递给外部 DLL) - std::vector wellPressureDataForDll; + // 准备传给 HX_NWTM_PLOT 的压力或气井拟压力序列。 + QVector vecPressureForPlot; + vecPressureForPlot.reserve( + static_cast(p1.pw[wellIdx].size())); for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) { if((i % 256) == 0 && isCancelRequested()) { return false; } - Point pt; - pt.x = p1.t[i]; - // 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力. double pressureForLog = p1.pw[wellIdx][i]; if(usePseudoPressure @@ -1671,126 +1645,36 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( << "well code:" << oWellRef.m_sWellCode; return fail(tr("Failed to convert gas pressure to pseudo-pressure.")); } - - pt.y = pressureForLog; - pt.z = 0.0; - wellPressureDataForDll.push_back(pt); + vecPressureForPlot.append(pressureForLog); } - // 准备流量段数据 - // 输入快照已经移除可选 (0,0) 占位点,此处逐段传给曲线 DLL。 - const int nTimeNumQ = - oWellInput.m_vecFlowDurations.size(); - std::vector timeQ(nTimeNumQ); - std::vector q(nTimeNumQ); - - for(int i = 0; i < nTimeNumQ; ++i) { - timeQ[i] = oWellInput.m_vecFlowDurations[i]; - q[i] = (*pReferenceRates)[i]; + // HX_NWTM.dll 直接返回双对数三列和半对数两列, + // 数值后处理不再加载旧曲线库。 + bool bHxPlotInterfaceAvailable = false; + QString sHxPlotError; + const bool bCalculatedByHxPlot = + nmDataUtils::calculateHxNwtmPlot( + oWellInput.m_vecFlowDurations, + *pReferenceRates, + currentWellTime, + vecPressureForPlot, + oWellInput.m_nFlowSectionIndex, + m_pInputSnapshot->m_oGridInput.m_sLicensePath, + vvecLogLog, + vvecSemiLog, + &bHxPlotInterfaceAvailable, + &sHxPlotError); + if(!bCalculatedByHxPlot) { + // 旧曲线库已下线,接口缺失或计算失败时直接返回, + // 避免继续使用空曲线或上一轮计算残留的数据。 + qWarning() << (bHxPlotInterfaceAvailable + ? "HX_NWTM_PLOT failed for well" + : "HX_NWTM_PLOT is not exported for well") + << oWellRef.m_sWellCode << ":" << sHxPlotError; + return fail(tr("Failed to calculate pressure curves.")); } - - // 调用外部 DLL 计算双对数曲线 - std::vector logPreResultFromDll; // 存储 DLL 的计算结果 - const int iSectionFlowIndex = - oWellInput.m_nFlowSectionIndex; - - // 第一步:无产量观察井只接收压力结果,不调用依赖产量制度的曲线 DLL。 - HMODULE hMod_solver = nTimeNumQ > 0 - ? LoadLibrary(L"singlePhaseSolverDll.dll") : nullptr; - - if(nTimeNumQ <= 0) { - // 第二步:保持固定的数据列结构,便于结果保存和后续读取; - // 各列为空明确表示该观察井没有可展示的双对数、半对数结果。 - vvecLogLog.clear(); - vvecLogLog.append(QVector()); - vvecLogLog.append(QVector()); - vvecLogLog.append(QVector()); - vvecSemiLog.clear(); - vvecSemiLog.append(QVector()); - vvecSemiLog.append(QVector()); - } else if(hMod_solver) { - typedef bool (*PreLog)(const std::vector&, const int&, double*, double*, int, std::vector&); - PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre"); - - if(nullptr == preLogFun) { - FreeLibrary(hMod_solver); - std::cout << "preLogFun failed!\n"; - return fail(tr("The pressure curve interface is incomplete.")); - } - - // 气井传入拟压力序列, 油井和水井仍传入原始压力序列. - // 计算失败必须向上返回, 避免 PSO 使用空曲线或上一粒子的旧曲线. - if(!preLogFun(wellPressureDataForDll, - iSectionFlowIndex, - timeQ.data(), - q.data(), - nTimeNumQ, - logPreResultFromDll)) { - FreeLibrary(hMod_solver); - return fail(tr("Failed to calculate pressure curves.")); - } - - if(isCancelRequested()) { - FreeLibrary(hMod_solver); - return false; - } - - // 填充双对数曲线数据到局部变量 - QVector logX, logY, logZ; - - // 检查结果是否为空,并跳过第一个点 - if (logPreResultFromDll.size() > 1) { - // 从索引 1 开始遍历,跳过索引 0 的第一个点 - for (size_t i = 1; i < logPreResultFromDll.size(); ++i) { - if((i % 256) == 0 && isCancelRequested()) { - FreeLibrary(hMod_solver); - return false; - } - 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 semiLogX, semiLogY; - - // 检查结果是否为空,并跳过第一个点 - if (logPreResultFromDll.size() > 1) { - // 半对数曲线也应该同步跳过第一个点 - for (size_t i = 1; i < logPreResultFromDll.size(); ++i) { - if((i % 256) == 0 && isCancelRequested()) { - FreeLibrary(hMod_solver); - return false; - } - 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 fail(tr("Failed to load singlePhaseSolverDll.dll.")); + if(isCancelRequested()) { + return false; } } diff --git a/Src/nmNum/nmCalculation/nmCalculationUtils.cpp b/Src/nmNum/nmCalculation/nmCalculationUtils.cpp index 321ea545..1058c4e7 100644 --- a/Src/nmNum/nmCalculation/nmCalculationUtils.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationUtils.cpp @@ -1,5 +1,7 @@ #include "nmCalculationUtils.h" +#include "nmDataUtils.h" + #include #include #include @@ -24,9 +26,6 @@ namespace { -// HX_NWTM.dll 的配置和结果查询接口使用进程级共享状态,所有入口共用此锁。 -QMutex s_oHxNwtmDllMutex; - bool isKrigingCancellationRequested(const QAtomicInt* pCancelRequested) { return pCancelRequested != NULL && @@ -299,7 +298,8 @@ void nmCalculationUtils::cleanupStaleAutoFitTemporaryDirectories( QMutex* nmCalculationUtils::getHxNwtmDllMutex() { - return &s_oHxNwtmDllMutex; + // 锁由更底层的 nmData 模块持有,保证数据预处理和求解模块调用的是同一把锁。 + return nmDataUtils::getHxNwtmDllMutex(); } void nmCalculationUtils::cleanupPebiGridDebugFiles() diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 12a526cc..380cb5fd 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -65,7 +65,6 @@ #include #include #include -#include "singlePhaseSolver.h" #include "nmDataTimeStepSetting.h" @@ -2202,6 +2201,7 @@ void nmDataAnalyzeManager::initCurWellData() static bool calculateLogDataFromGaugeInput( const nmWellGaugeInputData& oGaugeInput, bool bUsePseudoPressure, void* pFitting, + const QString& sLicensePath, QVector>& vvecHistoryData, QVector>& vvecLogPreData, QVector>& vvecSemiLogPreData) @@ -2218,18 +2218,12 @@ static bool calculateLogDataFromGaugeInput( // 准备压力数据 const QVector& vecPressure = oGaugeInput.vecPressurePoints; - std::vector wellPressureData; + QVector vecPressureForPlot; + vecPressureForPlot.reserve(vecPressure.size()); - // 填充 wellPressureData 和 vvecHistoryData + // 同时保留界面显示的原始压力,并准备传给 HX_NWTM_PLOT 的压力纵坐标。 foreach(const QPointF& qpoint, vecPressure) { - // wellPressureData - Point pt; - pt.x = qpoint.x(); - pt.y = qpoint.y(); - pt.z = 0.0; - wellPressureData.push_back(pt); - - // vvecHistoryData + vecPressureForPlot.append(qpoint.y()); vvecHistoryData[0].append(qpoint.x()); // x vvecHistoryData[1].append(qpoint.y()); // y } @@ -2262,60 +2256,31 @@ static bool calculateLogDataFromGaugeInput( return false; } for(int i = 0; i < vecPseudoPressure.size(); ++i) { - wellPressureData[i].y = vecPseudoPressure[i]; - } - } - - const int nTimeNumQ = oGaugeInput.vecFlowDurations.size(); - std::vector timeQ(nTimeNumQ); - std::vector q(nTimeNumQ); - - for(int i = 0; i < nTimeNumQ; ++i) { - timeQ[i] = oGaugeInput.vecFlowDurations[i]; - q[i] = (*pReferenceRates)[i]; - } - - // 调用 DLL 计算双对数曲线 - std::vector logPre; - int iSectionFlowIndex = oGaugeInput.nFlowSectionIndex; - - HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll"); - - if(hMod_solver) { - typedef bool (*PreLog)(const std::vector&, const int&, double*, double*, int, std::vector&); - PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre"); - - if(nullptr == preLogFun) { - FreeLibrary(hMod_solver); - std::cout << "preLogFun failed!\n"; - return false; - } - - bool bCalculated = preLogFun(wellPressureData, iSectionFlowIndex, - timeQ.data(), q.data(), nTimeNumQ, logPre); - if(!bCalculated || logPre.empty()) { - FreeLibrary(hMod_solver); - return false; - } - - // The solver's final point is not part of the plotted result. - for(std::vector::size_type i = 0; i + 1 < logPre.size(); ++i) { - //logFile << logPre[i].x << "\t" << logPre[i].y << "\t" << logPre[i].z << "\t" << std::endl; - vvecLogPreData[0].append(logPre[i].x); // x - vvecLogPreData[1].append(logPre[i].y); // y - vvecLogPreData[2].append(logPre[i].z); // z - } - - // 填充半对数曲线数据 (x, pointData[0]) - foreach(const auto& point, logPre) { - vvecSemiLogPreData[0].append(point.x); // x - double y_value = point.pointData.empty() ? 0.0 : point.pointData[0]; - vvecSemiLogPreData[1].append(y_value); // pointData[0] 或默认值 - } - - FreeLibrary(hMod_solver); - return true; - } + vecPressureForPlot[i] = vecPseudoPressure[i]; + } + } + + bool bHxPlotInterfaceAvailable = false; + QString sHxPlotError; + if(nmDataUtils::calculateHxNwtmPlot( + oGaugeInput.vecFlowDurations, + *pReferenceRates, + vvecHistoryData[0], + vecPressureForPlot, + oGaugeInput.nFlowSectionIndex, + sLicensePath, + vvecLogPreData, + vvecSemiLogPreData, + &bHxPlotInterfaceAvailable, + &sHxPlotError)) { + return true; + } + // SinglePhase 回退已移除。新导出缺失或计算失败时必须明确失败, + // 防止界面及自动拟合继续使用旧曲线或空曲线。 + qWarning() << (bHxPlotInterfaceAvailable + ? "HX_NWTM_PLOT failed while processing history data:" + : "HX_NWTM_PLOT is not exported by HX_NWTM.dll:") + << sHxPlotError; return false; } @@ -3077,7 +3042,8 @@ void nmDataAnalyzeManager::calculationLogData( nmWellGaugeInputData oGaugeInput; pWellData->buildGaugeInputData(oGaugeInput); calculateLogDataFromGaugeInput(oGaugeInput, - getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, vvecHistoryData, + getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, + getLicensePath(), vvecHistoryData, vvecLogPreData, vvecSemiLogPreData); } @@ -3098,7 +3064,8 @@ bool nmDataAnalyzeManager::updateWellHistoryData( QVector > vecHistoryLogLog; QVector > vecHistorySemiLog; const bool bCalculated = calculateLogDataFromGaugeInput(oGaugeInput, - getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, vecHistoryPressure, + getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, + getLicensePath(), vecHistoryPressure, vecHistoryLogLog, vecHistorySemiLog); // 转换或计算失败时清除旧曲线的有效标记,避免继续使用旧的压力曲线。 pWellData->setHistoryData(vecHistoryPressure, vecHistoryLogLog, diff --git a/Src/nmNum/nmData/nmDataUtils.cpp b/Src/nmNum/nmData/nmDataUtils.cpp index 164e0dd2..0bb377ca 100644 --- a/Src/nmNum/nmData/nmDataUtils.cpp +++ b/Src/nmNum/nmData/nmDataUtils.cpp @@ -7,8 +7,54 @@ #include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include + +namespace +{ +typedef std::vector > nmHxPlotMatrix; + +/** + * @brief 与算法方 HX_PLOT_DATA 二进制布局一致的本地结构。 + * + * 正式接口头文件当前使用 GBK 编码。这里保留最小 ABI 镜像,避免为了一个新增 + * 结构整体转码第三方头文件;函数通过 GetProcAddress 动态解析,不参与静态链接。 + */ +struct nmHxPlotData +{ + nmHxPlotMatrix loglog; + nmHxPlotMatrix semilog; +}; + +// HX_NWTM.dll 内部存在进程级共享状态,建网、求解、克里金和绘图必须共用同一把锁。 +QMutex s_oHxNwtmDllMutex; + +void setHxPlotError(QString* pErrorMessage, const QString& sMessage) +{ + if(pErrorMessage != 0) { + *pErrorMessage = sMessage; + } +} + +bool isReasonableHxPlotValue(double dValue) +{ + const double dMaximumAbsoluteValue = 1.0e12; + return _finite(dValue) != 0 && + dValue >= -dMaximumAbsoluteValue && + dValue <= dMaximumAbsoluteValue; +} + +} + nmDataUtils::nmDataUtils() { } @@ -16,6 +62,204 @@ nmDataUtils::~nmDataUtils() { } +QMutex* nmDataUtils::getHxNwtmDllMutex() +{ + return &s_oHxNwtmDllMutex; +} + +bool nmDataUtils::calculateHxNwtmPlot( + const QVector& flowDurations, + const QVector& rates, + const QVector& pressureTimes, + const QVector& pressures, + int flowSectionIndex, + const QString& licensePath, + QVector >& logLog, + QVector >& semiLog, + bool* interfaceAvailable, + QString* errorMessage) +{ + logLog.clear(); + semiLog.clear(); + if(interfaceAvailable != 0) { + *interfaceAvailable = false; + } + if(errorMessage != 0) { + errorMessage->clear(); + } + + // Rate 和 Press 都是两行矩阵;两行长度不一致时不能交给第三方 DLL。 + if(flowDurations.isEmpty() || + flowDurations.size() != rates.size() || + pressureTimes.isEmpty() || + pressureTimes.size() != pressures.size() || + flowSectionIndex < 1 || + flowSectionIndex > flowDurations.size()) { + setHxPlotError(errorMessage, + QString::fromLatin1("Invalid HX_NWTM_PLOT input arrays.")); + return false; + } + // 项目保存的是逐段持续时间,算法接口需要当前段的累计起止时刻。 + double dSectionStart = 0.0; + for(int nIndex = 0; nIndex < flowSectionIndex - 1; ++nIndex) { + if(!isReasonableHxPlotValue(flowDurations[nIndex]) || + flowDurations[nIndex] < 0.0) { + setHxPlotError(errorMessage, + QString::fromLatin1("Invalid flow-section duration.")); + return false; + } + dSectionStart += flowDurations[nIndex]; + } + const double dSelectedDuration = flowDurations[flowSectionIndex - 1]; + if(!isReasonableHxPlotValue(dSelectedDuration) || + dSelectedDuration <= 0.0 || + !isReasonableHxPlotValue(dSectionStart + dSelectedDuration)) { + setHxPlotError(errorMessage, + QString::fromLatin1("Invalid selected flow-section range.")); + return false; + } + const double dSectionEnd = dSectionStart + dSelectedDuration; + + // 算法方约定:Rate[0] 为各段时间,Rate[1] 为对应流量。 + nmHxPlotMatrix oRate(2); + oRate[0].reserve(flowDurations.size()); + oRate[1].reserve(rates.size()); + for(int nIndex = 0; nIndex < flowDurations.size(); ++nIndex) { + if(!isReasonableHxPlotValue(flowDurations[nIndex]) || + !isReasonableHxPlotValue(rates[nIndex])) { + setHxPlotError(errorMessage, + QString::fromLatin1("Invalid rate history value.")); + return false; + } + oRate[0].push_back(flowDurations[nIndex]); + oRate[1].push_back(rates[nIndex]); + } + + // 算法方约定:Press[0] 为压力时间,Press[1] 为压力或气井拟压力。 + nmHxPlotMatrix oPressure(2); + oPressure[0].reserve(pressureTimes.size()); + oPressure[1].reserve(pressures.size()); + for(int nIndex = 0; nIndex < pressureTimes.size(); ++nIndex) { + if(!isReasonableHxPlotValue(pressureTimes[nIndex]) || + !isReasonableHxPlotValue(pressures[nIndex])) { + setHxPlotError(errorMessage, + QString::fromLatin1("Invalid pressure history value.")); + return false; + } + oPressure[0].push_back(pressureTimes[nIndex]); + oPressure[1].push_back(pressures[nIndex]); + } + + QMutexLocker oLocker(getHxNwtmDllMutex()); + HMODULE hModule = LoadLibrary(L"HX_NWTM.dll"); + if(hModule == NULL) { + setHxPlotError(errorMessage, + QString::fromLatin1("Failed to load HX_NWTM.dll.")); + return false; + } + + typedef void (*HxNwtmPlotFunction)( + nmHxPlotData&, + const nmHxPlotMatrix&, + const nmHxPlotMatrix&, + const double, + const double, + std::string); + HxNwtmPlotFunction pfnPlot = + reinterpret_cast( + GetProcAddress(hModule, "HX_NWTM_PLOT")); + if(pfnPlot == NULL) { + // 兼容当前尚未提供绘图接口的旧版 HX_NWTM.dll,调用方可继续走旧算法回退。 + FreeLibrary(hModule); + return false; + } + if(interfaceAvailable != 0) { + *interfaceAvailable = true; + } + // 已确认新导出存在后再校验许可证。这样许可证错误不会被误判成“接口不存在”, + // 调用方也就不会错误地回退到旧算法。 + if(licensePath.isEmpty() || !QFileInfo(licensePath).exists()) { + FreeLibrary(hModule); + setHxPlotError(errorMessage, + QString::fromLatin1("HX_NWTM license file was not found.")); + return false; + } + + nmHxPlotData oResult; + // 当前版本 DLL 会直接访问输出矩阵的固定行号,不会先扩展外层 vector。 + // 按接口约定预建双对数 3 行、半对数 2 行,避免空外层数组导致访问冲突。 + oResult.loglog.resize(3); + oResult.semilog.resize(2); + try { + pfnPlot(oResult, + oRate, + oPressure, + dSectionStart, + dSectionEnd, + licensePath.toStdString()); + } catch(const std::exception& oException) { + FreeLibrary(hModule); + setHxPlotError(errorMessage, + QString::fromLatin1("HX_NWTM_PLOT exception: %1") + .arg(QString::fromLocal8Bit(oException.what()))); + return false; + } catch(...) { + FreeLibrary(hModule); + setHxPlotError(errorMessage, + QString::fromLatin1("HX_NWTM_PLOT raised an unknown exception.")); + return false; + } + FreeLibrary(hModule); + + // 输出按列存放,任何列数或点数不匹配都视为第三方接口返回不完整。 + if(oResult.loglog.size() < 3 || oResult.semilog.size() < 2 || + oResult.loglog[0].size() != oResult.loglog[1].size() || + oResult.loglog[0].size() != oResult.loglog[2].size() || + oResult.semilog[0].size() != oResult.semilog[1].size()) { + setHxPlotError(errorMessage, + QString::fromLatin1("HX_NWTM_PLOT returned malformed arrays.")); + return false; + } + + // 双对数坐标要求时间和导数为正;统一过滤首尾占位点和非数值点。 + logLog.resize(3); + for(size_t nIndex = 0; nIndex < oResult.loglog[0].size(); ++nIndex) { + const double dTime = oResult.loglog[0][nIndex]; + const double dPressureDifference = oResult.loglog[1][nIndex]; + const double dDerivative = oResult.loglog[2][nIndex]; + if(!isReasonableHxPlotValue(dTime) || dTime <= 0.0 || + !isReasonableHxPlotValue(dPressureDifference) || + !isReasonableHxPlotValue(dDerivative) || + dDerivative < DBL_EPSILON) { + continue; + } + logLog[0].append(dTime); + logLog[1].append(dPressureDifference); + logLog[2].append(dDerivative); + } + + semiLog.resize(2); + for(size_t nIndex = 0; nIndex < oResult.semilog[0].size(); ++nIndex) { + const double dTime = oResult.semilog[0][nIndex]; + const double dPressure = oResult.semilog[1][nIndex]; + if(!isReasonableHxPlotValue(dTime) || dTime <= 0.0 || + !isReasonableHxPlotValue(dPressure)) { + continue; + } + semiLog[0].append(dTime); + semiLog[1].append(dPressure); + } + + if(logLog[0].isEmpty() || semiLog[0].isEmpty()) { + logLog.clear(); + semiLog.clear(); + setHxPlotError(errorMessage, + QString::fromLatin1("HX_NWTM_PLOT returned no valid plot points.")); + return false; + } + return true; +} + bool nmDataUtils::extendChildrenOfProj(ZxDataProject* pDataProj) { Q_ASSERT (nullptr != pDataProj); diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 65487f02..d4cbb02d 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -2913,40 +2913,7 @@ void nmSubWndMain::onWellSelected(const QString& sWellInstanceId) // q[i] = vecTimeQ[i + 1].y(); // } // -// // 调用 DLL 计算双对数曲线 -// std::vector logPre; -// int iSectionFlowIndex = pWellData->getIndexF(); -// -// HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll"); -// -// if(hMod_solver) { -// typedef bool (*PreLog)(const std::vector&, const int&, double*, double*, int, std::vector&); -// PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre"); -// -// if(nullptr == preLogFun) { -// FreeLibrary(hMod_solver); -// std::cout << "preLogFun failed!\n"; -// return; -// } -// -// preLogFun(wellPressureData, iSectionFlowIndex, timeQ.data(), q.data(), nTimeNumQ, logPre); -// -// // 不添加最后一个元素 -// for(uint i = 0; i < logPre.size() - 1; i++) { -// //logFile << logPre[i].x << "\t" << logPre[i].y << "\t" << logPre[i].z << "\t" << std::endl; -// vvecLogPreData[0].append(logPre[i].x); // x -// vvecLogPreData[1].append(logPre[i].y); // y -// vvecLogPreData[2].append(logPre[i].z); // z -// } -// -// // 填充半对数曲线数据 (x, pointData[0]) -// foreach(const auto& point, logPre) { -// vvecSemiLogPreData[0].append(point.x); // x -// double y_value = point.pointData.empty() ? 0.0 : point.pointData[0]; -// vvecSemiLogPreData[1].append(y_value); // pointData[0] 或默认值 -// } -// FreeLibrary(hMod_solver); -// } +// // 压力曲线统一由 HX_NWTM_PLOT 计算,旧曲线库调用示例已删除。 //} void nmSubWndMain::onGenerateButtonClicked() diff --git a/Src4/nmNum/nmCalculation/nmCalculation.pro b/Src4/nmNum/nmCalculation/nmCalculation.pro index 458b09f3..812f913c 100644 --- a/Src4/nmNum/nmCalculation/nmCalculation.pro +++ b/Src4/nmNum/nmCalculation/nmCalculation.pro @@ -31,7 +31,6 @@ INCLUDEPATH += \ $${wtInclude}/nmNum/nmData \ $${wtInclude}/iBase/iDefines \ $${wtInclude}/iBase/iLogs \ - $${geoHome}/3rd/SinglePhaseSolver/include \ $${geoHome}/3rd/Pebi/include INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/ diff --git a/Src4/nmNum/nmData/nmData.pro b/Src4/nmNum/nmData/nmData.pro index c0842a33..d0b1fb9c 100644 --- a/Src4/nmNum/nmData/nmData.pro +++ b/Src4/nmNum/nmData/nmData.pro @@ -69,7 +69,6 @@ INCLUDEPATH += $${wtInclude}/nmNum/nmData INCLUDEPATH += $${wtInclude}/nmNum/nmXml INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/ -INCLUDEPATH += $${geoHome}/3rd/SinglePhaseSolver/include INCLUDEPATH += $${geoHome}/3rd/Pebi/include SOURCES += $$files($${wtSrc}/nmNum/nmData/*.cpp)