feat(nmNum): 接入 HX_NWTM 绘图接口并移除旧曲线库

- 使用 HX_NWTM_PLOT 生成双对数和半对数曲线
- 预初始化输出矩阵,修复打开数值解时的访问冲突
- 统一 HX_NWTM 接口调用锁并增加输入输出校验
- 移除 singlePhaseSolverDll 的代码及工程依赖
- 更新 HX_NWTM DLL、头文件和导入库
develop
lh 1 week ago
parent 78d5f934cd
commit dfe628e19b

@ -49,12 +49,9 @@ include 和 compat。直接交付整个目录已经覆盖这些内容。
lib MSVC x86 导入库 lib MSVC x86 导入库
bin 运行库及自动部署来源 bin 运行库及自动部署来源
nmSubWxs 工程已经默认启用以下构建配置: nmSubWxs 工程通过 Src4/ffmpeg.pri 默认启用FFmpeg视频导出并链接上述
四个导入库。当前工程不在链接完成后自动复制DLL;制作开发或运行环境时,
CONFIG += nm_ffmpeg_video_export 应将四个运行DLL分别预置到 Bin/Debug 和 Bin/Release 中。
启用后,qmake 会检查头文件、导入库、兼容头和四个运行库;链接完成后
自动将四个 DLL 复制到当前 Debug 或 Release 输出目录。
四、不要混入的旧文件 四、不要混入的旧文件
-------------------- --------------------

Binary file not shown.

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

@ -14,13 +14,12 @@
#include <iomanip> #include <iomanip>
#include <unordered_set> #include <unordered_set>
#include <Windows.h> #include <Windows.h>
#include "accelwt_solver_api.h"
#include "accelwt_cpu.h" #include "accelwt_cpu.h"
#include <cstdio> #include <cstdio>
#ifndef M_PI #ifndef M_PI
const double M_PI = acos(-1.0); const double M_PI = acos(-1.0);
#endif #endif
typedef std::vector<std::vector<std::vector<double>>>dVec3; //三维数组:double typedef std::vector<std::vector<std::vector<double>>>dVec3; //三维数组:double
typedef std::vector<std::vector<double>>dVec2; //二维数组:double typedef std::vector<std::vector<double>>dVec2; //二维数组:double
typedef std::vector<std::vector<int>>iVec2; //二维数组:int typedef std::vector<std::vector<int>>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; 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.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] = 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] = 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; 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() {} ~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_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_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_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_omp_threads(int n);
HX_API void set_ilu_reuse_steps(int n); HX_API void set_ilu_reuse_steps(int n);
HX_API int getPEBInum(); HX_API int getPEBInum();
HX_API int getsolvetime(); HX_API int getsolvetime();
HX_API void set_solvetype(int n); HX_API void set_solvetype(int n);

Binary file not shown.

Binary file not shown.

@ -148,7 +148,7 @@ int main()
std::cout << solvetypename[type] << "\n"; std::cout << solvetypename[type] << "\n";
int threads ; int threads ;
if (solvetype[type] == 1) { if (solvetype[type] == 1) {
threads = 5; threads = 1;
} }
else if (solvetype[type] == 2) { else if (solvetype[type] == 2) {
threads = 1; threads = 1;
@ -157,7 +157,7 @@ int main()
{ {
set_omp_threads(omp_threads[thr]); set_omp_threads(omp_threads[thr]);
std::cout << omp_threads[thr] << "线程测试\n"; std::cout << omp_threads[thr] << "线程测试\n";
for (int iiii = 0; iiii < 8; ++iiii) for (int iiii = 0; iiii < 1; ++iiii)
{ {
welltype = WT[iiii]; welltype = WT[iiii];
flowtype = FT[iiii]; flowtype = FT[iiii];
@ -442,7 +442,7 @@ int main()
p3.CS.C.resize(1); p3.CS.C.resize(1);
p3.CS.C[0] = 0.1; p3.CS.C[0] = 0.1;
p3.CS.S.resize(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.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.Bw = dVec1(200, 1.05);//所有数为一个值
p3.PVT.miuw = dVec1(200, 0.8);//所有数为一个值 p3.PVT.miuw = dVec1(200, 0.8);//所有数为一个值
@ -624,18 +624,18 @@ int main()
std::cout << " " << name[iiii] << ":" << getPEBInum() << "," << getsolvetime() << "ms\n"; std::cout << " " << name[iiii] << ":" << getPEBInum() << "," << getsolvetime() << "ms\n";
//数据导出 //数据导出
/*dVec2 pwf; //dVec2 pwf;
for (int i = 0; i < p4.t.size(); ++i) { //for (int i = 0; i < p4.t.size(); ++i) {
dVec1 a; // dVec1 a;
a.resize(p4.pw.size() + 1); // a.resize(p4.pw.size() + 1);
a[0] = p4.t[i]; // a[0] = p4.t[i];
for (int j = 1; j < a.size(); ++j) { // for (int j = 1; j < a.size(); ++j) {
a[j] = p4.pw[j - 1][i]; // a[j] = p4.pw[j - 1][i];
} // }
pwf.push_back(a); // pwf.push_back(a);
} //}
std::string filename = solvetypename[type] + name[iiii] + "pwf.csv"; //std::string filename = solvetypename[type] + name[iiii] + "pwf.csv";
Write2DVectorToCSV(pwf, filename);*/ //Write2DVectorToCSV(pwf, filename);
/* Write1DVectorToCSV(p4.t, "t.csv"); /* Write1DVectorToCSV(p4.t, "t.csv");
Write2DVectorToCSV(p4.pw, "pw.csv"); Write2DVectorToCSV(p4.pw, "pw.csv");

@ -2,9 +2,11 @@
#include "ZxDataObject.h" #include "ZxDataObject.h"
#include "nmData_global.h" #include "nmData_global.h"
#include <QVector>
class ZxDataProject; class ZxDataProject;
class ZxDataWell; class ZxDataWell;
class QMutex;
// 数值试井数据对象如何进行初始加载的方式 // 数值试井数据对象如何进行初始加载的方式
class NM_DATA_EXPORT nmDataUtils : public QObject class NM_DATA_EXPORT nmDataUtils : public QObject
@ -23,6 +25,28 @@ public:
static bool deleteChildrenOfWell(ZxDataWell* pDataWell); static bool deleteChildrenOfWell(ZxDataWell* pDataWell);
static QStringList readNmDataFile(const QString &filePath); 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<double>& flowDurations,
const QVector<double>& rates,
const QVector<double>& pressureTimes,
const QVector<double>& pressures,
int flowSectionIndex,
const QString& licensePath,
QVector<QVector<double> >& logLog,
QVector<QVector<double> >& semiLog,
bool* interfaceAvailable = 0,
QString* errorMessage = 0);
}; };

@ -1,5 +1,4 @@
#include "nmCalculationDllPebiSolverTask.h" #include "nmCalculationDllPebiSolverTask.h"
#include "singlePhaseSolver.h"
#include "zxLogInstance.h" #include "zxLogInstance.h"
#include "nmDataAnalyzeManager.h" #include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h" #include "nmDataWellBase.h"
@ -14,6 +13,7 @@
#include "nmDataFracture.h" #include "nmDataFracture.h"
#include "nmDataFault.h" #include "nmDataFault.h"
#include "nmDataTimeStepSetting.h" #include "nmDataTimeStepSetting.h"
#include "nmDataUtils.h"
#include "nmPebiResultSnapshotBuilder.h" #include "nmPebiResultSnapshotBuilder.h"
#include "nmPebiCellMapping.h" #include "nmPebiCellMapping.h"
#include "nmPebiStaticPropertyCalculator.h" #include "nmPebiStaticPropertyCalculator.h"
@ -224,9 +224,9 @@ bool captureResultWellMetadata(
return false; return false;
} }
// 发布结果前用当前配置重算气井历史曲线,替换旧项目中未转拟压力的缓存。 // 正式求解前统一重算历史双对数和半对数曲线,确保油井、水井和气井的
if(pDataManager->getSolverModelType() == SMT_Gas_VariablePvt // 曲线签名都与本次输入一致。
&& !pDataManager->updateWellHistoryData(pWellData)) { if(!pDataManager->updateWellHistoryData(pWellData)) {
return false; return false;
} }
@ -578,31 +578,6 @@ bool buildModelInputFromSnapshot(
return true; return true;
} }
bool isReasonableLogLogValue(double value)
{
const double maxReasonableAbsValue = 1.0e12;
return isFiniteSolverNumber(value)
&& value >= -maxReasonableAbsValue
&& value <= maxReasonableAbsValue;
}
bool isValidLogLogPoint(const Point& pt)
{
return isReasonableLogLogValue(pt.x)
&& isReasonableLogLogValue(pt.y)
&& isReasonableLogLogValue(pt.z)
&& pt.x > 0.0
&& pt.z >= DBL_EPSILON;
}
bool isValidSemiLogPoint(const Point& pt)
{
return isReasonableLogLogValue(pt.x)
&& pt.x > 0.0
&& !pt.pointData.empty()
&& isReasonableLogLogValue(pt.pointData[0]);
}
typedef bool (*CalPseudoPressure)(double, double&, int); typedef bool (*CalPseudoPressure)(double, double&, int);
// mAlgPseudo.dll 是主界面拟压力算法所在模块. // mAlgPseudo.dll 是主界面拟压力算法所在模块.
@ -1649,16 +1624,15 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
vvecSemiLog.clear(); vvecSemiLog.clear();
if(bRateControlled) { if(bRateControlled) {
// 准备压力数据 (用于传递给外部 DLL) // 准备传给 HX_NWTM_PLOT 的压力或气井拟压力序列。
std::vector<Point> wellPressureDataForDll; QVector<double> vecPressureForPlot;
vecPressureForPlot.reserve(
static_cast<int>(p1.pw[wellIdx].size()));
for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) { for(size_t i = 0; i < p1.pw[wellIdx].size(); ++i) {
if((i % 256) == 0 && isCancelRequested()) { if((i % 256) == 0 && isCancelRequested()) {
return false; return false;
} }
Point pt;
pt.x = p1.t[i];
// 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力. // 使用局部副本转换, 不修改 p1.pw 中需要保存和显示的原始压力.
double pressureForLog = p1.pw[wellIdx][i]; double pressureForLog = p1.pw[wellIdx][i];
if(usePseudoPressure if(usePseudoPressure
@ -1671,126 +1645,36 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
<< "well code:" << oWellRef.m_sWellCode; << "well code:" << oWellRef.m_sWellCode;
return fail(tr("Failed to convert gas pressure to pseudo-pressure.")); return fail(tr("Failed to convert gas pressure to pseudo-pressure."));
} }
vecPressureForPlot.append(pressureForLog);
pt.y = pressureForLog;
pt.z = 0.0;
wellPressureDataForDll.push_back(pt);
} }
// 准备流量段数据 // HX_NWTM.dll 直接返回双对数三列和半对数两列,
// 输入快照已经移除可选 (0,0) 占位点,此处逐段传给曲线 DLL。 // 数值后处理不再加载旧曲线库。
const int nTimeNumQ = bool bHxPlotInterfaceAvailable = false;
oWellInput.m_vecFlowDurations.size(); QString sHxPlotError;
std::vector<double> timeQ(nTimeNumQ); const bool bCalculatedByHxPlot =
std::vector<double> q(nTimeNumQ); nmDataUtils::calculateHxNwtmPlot(
oWellInput.m_vecFlowDurations,
for(int i = 0; i < nTimeNumQ; ++i) { *pReferenceRates,
timeQ[i] = oWellInput.m_vecFlowDurations[i]; currentWellTime,
q[i] = (*pReferenceRates)[i]; 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."));
} }
if(isCancelRequested()) {
// 调用外部 DLL 计算双对数曲线 return false;
std::vector<Point> 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<double>());
vvecLogLog.append(QVector<double>());
vvecLogLog.append(QVector<double>());
vvecSemiLog.clear();
vvecSemiLog.append(QVector<double>());
vvecSemiLog.append(QVector<double>());
} else if(hMod_solver) {
typedef bool (*PreLog)(const std::vector<Point>&, const int&, double*, double*, int, std::vector<Point>&);
PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre");
if(nullptr == preLogFun) {
FreeLibrary(hMod_solver);
std::cout << "preLogFun failed!\n";
return 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<double> 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<double> 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."));
} }
} }

@ -1,5 +1,7 @@
#include "nmCalculationUtils.h" #include "nmCalculationUtils.h"
#include "nmDataUtils.h"
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
@ -24,9 +26,6 @@
namespace namespace
{ {
// HX_NWTM.dll 的配置和结果查询接口使用进程级共享状态,所有入口共用此锁。
QMutex s_oHxNwtmDllMutex;
bool isKrigingCancellationRequested(const QAtomicInt* pCancelRequested) bool isKrigingCancellationRequested(const QAtomicInt* pCancelRequested)
{ {
return pCancelRequested != NULL && return pCancelRequested != NULL &&
@ -299,7 +298,8 @@ void nmCalculationUtils::cleanupStaleAutoFitTemporaryDirectories(
QMutex* nmCalculationUtils::getHxNwtmDllMutex() QMutex* nmCalculationUtils::getHxNwtmDllMutex()
{ {
return &s_oHxNwtmDllMutex; // 锁由更底层的 nmData 模块持有,保证数据预处理和求解模块调用的是同一把锁。
return nmDataUtils::getHxNwtmDllMutex();
} }
void nmCalculationUtils::cleanupPebiGridDebugFiles() void nmCalculationUtils::cleanupPebiGridDebugFiles()

@ -65,7 +65,6 @@
#include <iostream> #include <iostream>
#include <new> #include <new>
#include <vector> #include <vector>
#include "singlePhaseSolver.h"
#include "nmDataTimeStepSetting.h" #include "nmDataTimeStepSetting.h"
@ -2202,6 +2201,7 @@ void nmDataAnalyzeManager::initCurWellData()
static bool calculateLogDataFromGaugeInput( static bool calculateLogDataFromGaugeInput(
const nmWellGaugeInputData& oGaugeInput, const nmWellGaugeInputData& oGaugeInput,
bool bUsePseudoPressure, void* pFitting, bool bUsePseudoPressure, void* pFitting,
const QString& sLicensePath,
QVector<QVector<double>>& vvecHistoryData, QVector<QVector<double>>& vvecHistoryData,
QVector<QVector<double>>& vvecLogPreData, QVector<QVector<double>>& vvecLogPreData,
QVector<QVector<double>>& vvecSemiLogPreData) QVector<QVector<double>>& vvecSemiLogPreData)
@ -2218,18 +2218,12 @@ static bool calculateLogDataFromGaugeInput(
// 准备压力数据 // 准备压力数据
const QVector<QPointF>& vecPressure = oGaugeInput.vecPressurePoints; const QVector<QPointF>& vecPressure = oGaugeInput.vecPressurePoints;
std::vector<Point> wellPressureData; QVector<double> vecPressureForPlot;
vecPressureForPlot.reserve(vecPressure.size());
// 填充 wellPressureData 和 vvecHistoryData // 同时保留界面显示的原始压力,并准备传给 HX_NWTM_PLOT 的压力纵坐标。
foreach(const QPointF& qpoint, vecPressure) { foreach(const QPointF& qpoint, vecPressure) {
// wellPressureData vecPressureForPlot.append(qpoint.y());
Point pt;
pt.x = qpoint.x();
pt.y = qpoint.y();
pt.z = 0.0;
wellPressureData.push_back(pt);
// vvecHistoryData
vvecHistoryData[0].append(qpoint.x()); // x vvecHistoryData[0].append(qpoint.x()); // x
vvecHistoryData[1].append(qpoint.y()); // y vvecHistoryData[1].append(qpoint.y()); // y
} }
@ -2262,60 +2256,31 @@ static bool calculateLogDataFromGaugeInput(
return false; return false;
} }
for(int i = 0; i < vecPseudoPressure.size(); ++i) { for(int i = 0; i < vecPseudoPressure.size(); ++i) {
wellPressureData[i].y = vecPseudoPressure[i]; vecPressureForPlot[i] = vecPseudoPressure[i];
} }
} }
const int nTimeNumQ = oGaugeInput.vecFlowDurations.size(); bool bHxPlotInterfaceAvailable = false;
std::vector<double> timeQ(nTimeNumQ); QString sHxPlotError;
std::vector<double> q(nTimeNumQ); if(nmDataUtils::calculateHxNwtmPlot(
oGaugeInput.vecFlowDurations,
for(int i = 0; i < nTimeNumQ; ++i) { *pReferenceRates,
timeQ[i] = oGaugeInput.vecFlowDurations[i]; vvecHistoryData[0],
q[i] = (*pReferenceRates)[i]; vecPressureForPlot,
} oGaugeInput.nFlowSectionIndex,
sLicensePath,
// 调用 DLL 计算双对数曲线 vvecLogPreData,
std::vector<Point> logPre; vvecSemiLogPreData,
int iSectionFlowIndex = oGaugeInput.nFlowSectionIndex; &bHxPlotInterfaceAvailable,
&sHxPlotError)) {
HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll"); return true;
}
if(hMod_solver) { // SinglePhase 回退已移除。新导出缺失或计算失败时必须明确失败,
typedef bool (*PreLog)(const std::vector<Point>&, const int&, double*, double*, int, std::vector<Point>&); // 防止界面及自动拟合继续使用旧曲线或空曲线。
PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre"); qWarning() << (bHxPlotInterfaceAvailable
? "HX_NWTM_PLOT failed while processing history data:"
if(nullptr == preLogFun) { : "HX_NWTM_PLOT is not exported by HX_NWTM.dll:")
FreeLibrary(hMod_solver); << sHxPlotError;
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<Point>::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;
}
return false; return false;
} }
@ -3077,7 +3042,8 @@ void nmDataAnalyzeManager::calculationLogData(
nmWellGaugeInputData oGaugeInput; nmWellGaugeInputData oGaugeInput;
pWellData->buildGaugeInputData(oGaugeInput); pWellData->buildGaugeInputData(oGaugeInput);
calculateLogDataFromGaugeInput(oGaugeInput, calculateLogDataFromGaugeInput(oGaugeInput,
getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, vvecHistoryData, getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting,
getLicensePath(), vvecHistoryData,
vvecLogPreData, vvecSemiLogPreData); vvecLogPreData, vvecSemiLogPreData);
} }
@ -3098,7 +3064,8 @@ bool nmDataAnalyzeManager::updateWellHistoryData(
QVector<QVector<double> > vecHistoryLogLog; QVector<QVector<double> > vecHistoryLogLog;
QVector<QVector<double> > vecHistorySemiLog; QVector<QVector<double> > vecHistorySemiLog;
const bool bCalculated = calculateLogDataFromGaugeInput(oGaugeInput, const bool bCalculated = calculateLogDataFromGaugeInput(oGaugeInput,
getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting, vecHistoryPressure, getSolverModelType() == SMT_Gas_VariablePvt, m_pOwnerFitting,
getLicensePath(), vecHistoryPressure,
vecHistoryLogLog, vecHistorySemiLog); vecHistoryLogLog, vecHistorySemiLog);
// 转换或计算失败时清除旧曲线的有效标记,避免继续使用旧的压力曲线。 // 转换或计算失败时清除旧曲线的有效标记,避免继续使用旧的压力曲线。
pWellData->setHistoryData(vecHistoryPressure, vecHistoryLogLog, pWellData->setHistoryData(vecHistoryPressure, vecHistoryLogLog,

@ -7,8 +7,54 @@
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QFileInfo>
#include <QMutex>
#include <QMutexLocker>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include <exception>
#include <float.h>
#include <string>
#include <vector>
#include <Windows.h>
namespace
{
typedef std::vector<std::vector<double> > 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() { nmDataUtils::nmDataUtils() {
} }
@ -16,6 +62,204 @@ nmDataUtils::~nmDataUtils() {
} }
QMutex* nmDataUtils::getHxNwtmDllMutex()
{
return &s_oHxNwtmDllMutex;
}
bool nmDataUtils::calculateHxNwtmPlot(
const QVector<double>& flowDurations,
const QVector<double>& rates,
const QVector<double>& pressureTimes,
const QVector<double>& pressures,
int flowSectionIndex,
const QString& licensePath,
QVector<QVector<double> >& logLog,
QVector<QVector<double> >& 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<HxNwtmPlotFunction>(
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) bool nmDataUtils::extendChildrenOfProj(ZxDataProject* pDataProj)
{ {
Q_ASSERT (nullptr != pDataProj); Q_ASSERT (nullptr != pDataProj);

@ -2913,40 +2913,7 @@ void nmSubWndMain::onWellSelected(const QString& sWellInstanceId)
// q[i] = vecTimeQ[i + 1].y(); // q[i] = vecTimeQ[i + 1].y();
// } // }
// //
// // 调用 DLL 计算双对数曲线 // // 压力曲线统一由 HX_NWTM_PLOT 计算,旧曲线库调用示例已删除。
// std::vector<Point> logPre;
// 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;
// }
//
// 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);
// }
//} //}
void nmSubWndMain::onGenerateButtonClicked() void nmSubWndMain::onGenerateButtonClicked()

@ -31,7 +31,6 @@ INCLUDEPATH += \
$${wtInclude}/nmNum/nmData \ $${wtInclude}/nmNum/nmData \
$${wtInclude}/iBase/iDefines \ $${wtInclude}/iBase/iDefines \
$${wtInclude}/iBase/iLogs \ $${wtInclude}/iBase/iLogs \
$${geoHome}/3rd/SinglePhaseSolver/include \
$${geoHome}/3rd/Pebi/include $${geoHome}/3rd/Pebi/include
INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/ INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/

@ -69,7 +69,6 @@ INCLUDEPATH += $${wtInclude}/nmNum/nmData
INCLUDEPATH += $${wtInclude}/nmNum/nmXml INCLUDEPATH += $${wtInclude}/nmNum/nmXml
INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/ INCLUDEPATH += $${geoHome}/3rd/JSON/rapidjson-1.1.0/include/
INCLUDEPATH += $${geoHome}/3rd/SinglePhaseSolver/include
INCLUDEPATH += $${geoHome}/3rd/Pebi/include INCLUDEPATH += $${geoHome}/3rd/Pebi/include
SOURCES += $$files($${wtSrc}/nmNum/nmData/*.cpp) SOURCES += $$files($${wtSrc}/nmNum/nmData/*.cpp)

Loading…
Cancel
Save