|
|
|
|
@ -21,6 +21,7 @@
|
|
|
|
|
#include "nmDataPvtParaForPebi.h"
|
|
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
@ -317,13 +318,23 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
|
|
m_pDataManager(pDataManager != nullptr
|
|
|
|
|
? pDataManager
|
|
|
|
|
: nmDataAnalyzeManager::getCurrentInstance()),
|
|
|
|
|
m_bManagerUseActive(false),
|
|
|
|
|
m_nGridInputRevision(0),
|
|
|
|
|
m_nResultInputRevision(0),
|
|
|
|
|
m_lastRunSucceeded(false),
|
|
|
|
|
m_nSolveTimeMs(-1),
|
|
|
|
|
m_nPebiCount(-1)
|
|
|
|
|
m_nPebiCount(-1),
|
|
|
|
|
m_dPendingScalarMin(0.0),
|
|
|
|
|
m_dPendingScalarMax(0.0),
|
|
|
|
|
m_bPendingFullResultReady(false)
|
|
|
|
|
{
|
|
|
|
|
// 任务创建时冻结两个输入版本;非模态求解期间界面仍可编辑数据。
|
|
|
|
|
// 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。
|
|
|
|
|
if(m_pDataManager != nullptr) {
|
|
|
|
|
m_pDataManager->beginBackgroundUse();
|
|
|
|
|
m_bManagerUseActive = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二步:任务创建时冻结两个输入版本,提交前还会再次核对。
|
|
|
|
|
if(m_pDataManager != nullptr &&
|
|
|
|
|
m_pDataManager->getNumericalAnalysisCase() != nullptr) {
|
|
|
|
|
m_nGridInputRevision =
|
|
|
|
|
@ -335,10 +346,12 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
|
|
|
|
|
|
|
|
|
|
nmCalculationDllPebiSolverTask::~nmCalculationDllPebiSolverTask()
|
|
|
|
|
{
|
|
|
|
|
// Qt 4.8 没有可靠的外部 DLL 协作取消接口。强制 terminate() 可能让 DLL、
|
|
|
|
|
// VTK 或 STL 对象停在持锁/半析构状态,因此析构只能等待 run() 正常退出。
|
|
|
|
|
if(isRunning()) {
|
|
|
|
|
terminate();
|
|
|
|
|
wait(3000);
|
|
|
|
|
wait();
|
|
|
|
|
}
|
|
|
|
|
releaseDataManagerUse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::run()
|
|
|
|
|
@ -346,9 +359,19 @@ void nmCalculationDllPebiSolverTask::run()
|
|
|
|
|
m_nSolveTimeMs = -1;
|
|
|
|
|
m_nPebiCount = -1;
|
|
|
|
|
m_lastRunSucceeded = this->execute();
|
|
|
|
|
// 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。
|
|
|
|
|
releaseDataManagerUse();
|
|
|
|
|
emit sig_calculateDone(m_lastRunSucceeded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void nmCalculationDllPebiSolverTask::releaseDataManagerUse()
|
|
|
|
|
{
|
|
|
|
|
if(m_bManagerUseActive && m_pDataManager != nullptr) {
|
|
|
|
|
m_pDataManager->endBackgroundUse();
|
|
|
|
|
m_bManagerUseActive = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::wasSuccessful() const
|
|
|
|
|
{
|
|
|
|
|
return m_lastRunSucceeded;
|
|
|
|
|
@ -409,10 +432,12 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
|
|
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if(m_sAutoFitTargetWellCode.isEmpty()) {
|
|
|
|
|
// 完整重算开始后立即废弃上一轮有效标记;失败任务不能继续保存旧结果。
|
|
|
|
|
pDataInstance->discardPebiResults();
|
|
|
|
|
}
|
|
|
|
|
// 旧结果在新结果完整生成并通过主线程版本校验前保持不变。失败任务只丢弃
|
|
|
|
|
// 自己的局部快照,不能提前清空上一轮仍然完整的成果。
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
m_vecPendingWellResults.clear();
|
|
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
|
|
m_pPendingResultGrid = nullptr;
|
|
|
|
|
|
|
|
|
|
// 载入Pebi动态库
|
|
|
|
|
HMODULE dll = LoadLibrary(L"HX_NWTM.dll");
|
|
|
|
|
@ -849,10 +874,10 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存计算结果
|
|
|
|
|
bool success = this->savePebiModeResult(p1,
|
|
|
|
|
modelType,
|
|
|
|
|
oGridOutput1);
|
|
|
|
|
// 只构造任务局部结果;完整求解由主线程在完成回调中一次性提交。
|
|
|
|
|
bool success = this->buildPebiModeResult(p1,
|
|
|
|
|
modelType,
|
|
|
|
|
oGridOutput1);
|
|
|
|
|
|
|
|
|
|
FreeLibrary(dll); // 卸载 DLL
|
|
|
|
|
return success;
|
|
|
|
|
@ -892,7 +917,7 @@ std::vector<double> HX_logderivative(const std::vector<double>& x, const std::ve
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
|
|
|
|
|
HX_NWTM_MODEL_OUTPUT& p1,
|
|
|
|
|
int modelType,
|
|
|
|
|
const HX_NWTM_GRID_OUTPUT1& oGridOutput)
|
|
|
|
|
@ -964,11 +989,6 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 粒子评价不修改全局井位置,完整求解仍按原流程重建映射.
|
|
|
|
|
if(!autoFitTargetOnly) {
|
|
|
|
|
pDataInstance->clearWellLocations();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 遍历每口井,处理其数据
|
|
|
|
|
for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) {
|
|
|
|
|
const nmSolverWellRef& oWellRef = vecWellsOrder[wellIdx];
|
|
|
|
|
@ -1039,7 +1059,8 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
|
|
|
|
|
// 准备流量段数据
|
|
|
|
|
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
|
|
|
|
|
int nTimeNumQ = vecTimeQ.size() - 1; // 移除第一个 0 点
|
|
|
|
|
// 防御空流量数据,避免 -1 转成 std::vector 的巨大无符号长度。
|
|
|
|
|
int nTimeNumQ = qMax(0, vecTimeQ.size() - 1); // 移除第一个 0 点
|
|
|
|
|
std::vector<double> timeQ(nTimeNumQ);
|
|
|
|
|
std::vector<double> q(nTimeNumQ);
|
|
|
|
|
|
|
|
|
|
@ -1052,9 +1073,22 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
std::vector<Point> logPreResultFromDll; // 存储 DLL 的计算结果
|
|
|
|
|
int iSectionFlowIndex = pWellData->getIndexF();
|
|
|
|
|
|
|
|
|
|
HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll");
|
|
|
|
|
|
|
|
|
|
if(hMod_solver) {
|
|
|
|
|
// 第一步:无产量观察井只接收压力结果,不调用依赖产量制度的曲线 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");
|
|
|
|
|
|
|
|
|
|
@ -1135,14 +1169,17 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
autoFitTargetFound = true;
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// 所有真实井都保存压力;观察井的双对数和半对数保持为空。
|
|
|
|
|
pWellData->setResultPressure(vvecPressure);
|
|
|
|
|
pWellData->setResultLogLog(vvecLogLog);
|
|
|
|
|
pWellData->setResultSemiLog(vvecSemiLog);
|
|
|
|
|
|
|
|
|
|
QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble());
|
|
|
|
|
pDataInstance->addWellLocation(oWellRef.m_sWellCode,
|
|
|
|
|
ptWellCoords);
|
|
|
|
|
// 所有真实井先写入任务局部快照;观察井的双对数和半对数保持为空。
|
|
|
|
|
// 任意后续步骤失败时,整个快照会被丢弃,旧成果不会被局部覆盖。
|
|
|
|
|
nmPebiWellResultSnapshot oWellResult;
|
|
|
|
|
oWellResult.m_sWellCode = oWellRef.m_sWellCode;
|
|
|
|
|
oWellResult.m_vecPressure = vvecPressure;
|
|
|
|
|
oWellResult.m_vecLogLog = vvecLogLog;
|
|
|
|
|
oWellResult.m_vecSemiLog = vvecSemiLog;
|
|
|
|
|
oWellResult.m_oLocation = QPointF(
|
|
|
|
|
pWellData->getX().getValue().toDouble(),
|
|
|
|
|
pWellData->getY().getValue().toDouble());
|
|
|
|
|
m_vecPendingWellResults.append(oWellResult);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1195,14 +1232,13 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新结果提交前清空上一轮时间键,防止较短的新时间轴残留旧帧。
|
|
|
|
|
pDataInstance->clearTimeSteps();
|
|
|
|
|
|
|
|
|
|
// 在保存时间步数据前,先计算标量范围
|
|
|
|
|
// 第五步:在局部时间步映射中构造场压力,同时计算全局标量范围。
|
|
|
|
|
// 此处不触碰 DataManager,较短的新时间轴会在主线程整体替换时自然清除旧帧。
|
|
|
|
|
m_mapPendingTimeSteps.clear();
|
|
|
|
|
double dMinP = DBL_MAX;
|
|
|
|
|
double dMaxP = -DBL_MAX;
|
|
|
|
|
|
|
|
|
|
// 4. 为每个时间步保存压力场到 nmDataAnalyzeManager 的 QMap 中
|
|
|
|
|
// 为每个时间步生成完整的 VTK 压力数组。
|
|
|
|
|
for(size_t timeIdx = 0; timeIdx < p1.p.size(); ++timeIdx) {
|
|
|
|
|
// 获取当前时间步的时间值
|
|
|
|
|
double currentTime = p1.t[timeIdx];
|
|
|
|
|
@ -1235,29 +1271,113 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult(
|
|
|
|
|
dMinP = qMin(dMinP, dCurrentTimeMin);
|
|
|
|
|
dMaxP = qMax(dMaxP, dCurrentTimeMax);
|
|
|
|
|
|
|
|
|
|
// 将时间与对应的压力数据存储到 nmDataAnalyzeManager
|
|
|
|
|
pDataInstance->addTimeStep(currentTime, pressureData);
|
|
|
|
|
m_mapPendingTimeSteps.insert(currentTime, pressureData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存全局范围到管理器
|
|
|
|
|
pDataInstance->setScalarRangeP(dMinP, dMaxP);
|
|
|
|
|
if(pDataInstance->getTimeStepKeys().size() !=
|
|
|
|
|
static_cast<int>(p1.t.size())) {
|
|
|
|
|
// QMap 使用时间作为唯一键;重复时间会覆盖旧帧,因此必须检查数量一致。
|
|
|
|
|
if(m_mapPendingTimeSteps.size() != static_cast<int>(p1.t.size())) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pResultGrid =
|
|
|
|
|
// 第六步:复制与本次场数据对应的基础网格,并在最后才发布“快照就绪”。
|
|
|
|
|
m_pPendingResultGrid =
|
|
|
|
|
pDataInstance->getUnstructuredGridCopy();
|
|
|
|
|
if(pResultGrid == nullptr || pResultGrid->GetNumberOfCells() <= 0) {
|
|
|
|
|
if(m_pPendingResultGrid == nullptr ||
|
|
|
|
|
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int nExpectedWellResultCount = 0;
|
|
|
|
|
for(int nIndex = 0; nIndex < vecWellsOrder.size(); ++nIndex) {
|
|
|
|
|
if(vecWellsOrder[nIndex].m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
|
|
++nExpectedWellResultCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(m_vecPendingWellResults.size() != nExpectedWellResultCount) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_dPendingScalarMin = dMinP;
|
|
|
|
|
m_dPendingScalarMax = dMaxP;
|
|
|
|
|
m_bPendingFullResultReady = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationDllPebiSolverTask::commitResult(
|
|
|
|
|
nmDataAnalyzeManager* pDataManager)
|
|
|
|
|
{
|
|
|
|
|
// 第一步:自动拟合只返回目标曲线,不允许提交完整成果;手工求解必须回到
|
|
|
|
|
// DataManager 所属线程执行,保证界面看不到逐项替换过程中的中间状态。
|
|
|
|
|
if(!m_sAutoFitTargetWellCode.isEmpty() ||
|
|
|
|
|
!m_bPendingFullResultReady ||
|
|
|
|
|
pDataManager == nullptr ||
|
|
|
|
|
pDataManager != m_pDataManager ||
|
|
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
|
|
pDataManager->getNumericalAnalysisCase();
|
|
|
|
|
if(pAnalysisCase == nullptr ||
|
|
|
|
|
pAnalysisCase->getGridInputRevision() != m_nGridInputRevision ||
|
|
|
|
|
pAnalysisCase->getResultInputRevision() != m_nResultInputRevision ||
|
|
|
|
|
!pAnalysisCase->isGridValid() ||
|
|
|
|
|
m_mapPendingTimeSteps.isEmpty() ||
|
|
|
|
|
m_pPendingResultGrid == nullptr ||
|
|
|
|
|
m_pPendingResultGrid->GetNumberOfCells() <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
pDataInstance->setResultBaseGrid(pResultGrid);
|
|
|
|
|
|
|
|
|
|
if(!pDataInstance->getNumericalAnalysisCase()->markResultsAvailableIfCurrent(
|
|
|
|
|
// 第二步:在改动旧成果前一次性解析全部目标井,并检查 WellCode 不重复。
|
|
|
|
|
QVector<nmDataWellBase*> vecTargetWells;
|
|
|
|
|
QSet<QString> setWellCodes;
|
|
|
|
|
vecTargetWells.reserve(m_vecPendingWellResults.size());
|
|
|
|
|
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
|
|
|
|
|
const nmPebiWellResultSnapshot& oWellResult =
|
|
|
|
|
m_vecPendingWellResults[nIndex];
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
|
|
pDataManager->findWellByCode(oWellResult.m_sWellCode);
|
|
|
|
|
if(pWellData == nullptr ||
|
|
|
|
|
setWellCodes.contains(oWellResult.m_sWellCode)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
setWellCodes.insert(oWellResult.m_sWellCode);
|
|
|
|
|
vecTargetWells.append(pWellData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第三步:最后一次登记版本。登记失败时旧成果仍未被修改;登记成功后当前
|
|
|
|
|
// 主线程事件不会被其他编辑操作插入,因此后续替换不存在可恢复失败分支。
|
|
|
|
|
if(!pAnalysisCase->markResultsAvailableIfCurrent(
|
|
|
|
|
m_nGridInputRevision, m_nResultInputRevision)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第四步:版本检查和对象解析全部通过后,在当前主线程事件内整体替换。
|
|
|
|
|
// 这些 setter 不发事件也不包含可恢复失败分支,外部只能在本函数返回后看到新成果。
|
|
|
|
|
pDataManager->clearWellLocations();
|
|
|
|
|
for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) {
|
|
|
|
|
const nmPebiWellResultSnapshot& oWellResult =
|
|
|
|
|
m_vecPendingWellResults[nIndex];
|
|
|
|
|
nmDataWellBase* pWellData = vecTargetWells[nIndex];
|
|
|
|
|
pWellData->setResultPressure(oWellResult.m_vecPressure);
|
|
|
|
|
pWellData->setResultLogLog(oWellResult.m_vecLogLog);
|
|
|
|
|
pWellData->setResultSemiLog(oWellResult.m_vecSemiLog);
|
|
|
|
|
pDataManager->addWellLocation(oWellResult.m_sWellCode,
|
|
|
|
|
oWellResult.m_oLocation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pDataManager->clearTimeSteps();
|
|
|
|
|
QMap<double, vtkSmartPointer<vtkDoubleArray> >::const_iterator oTimeIt =
|
|
|
|
|
m_mapPendingTimeSteps.constBegin();
|
|
|
|
|
for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) {
|
|
|
|
|
pDataManager->addTimeStep(oTimeIt.key(), oTimeIt.value());
|
|
|
|
|
}
|
|
|
|
|
pDataManager->setScalarRangeP(m_dPendingScalarMin,
|
|
|
|
|
m_dPendingScalarMax);
|
|
|
|
|
pDataManager->setResultBaseGrid(m_pPendingResultGrid);
|
|
|
|
|
|
|
|
|
|
// 第五步:清除待提交标志,防止重复完成信号再次覆盖后续结果。
|
|
|
|
|
m_bPendingFullResultReady = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|