You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp

1377 lines
50 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmCalculationPebiGrid.h"
#include <Windows.h>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QMutex>
#include <QMutexLocker>
#include <QThread>
#include <cmath>
#include <QSet>
#include "nmCalculationUtils.h"
#include "zxLogInstance.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmDataVerticalWell.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataReservoir.h"
#include "nmDataAttribute.h"
#include "nmDataRegion.h"
#include "nmDataRegionMark.h"
#include "nmDataOutline.h"
#include "nmDataFracture.h"
#include "nmDataFault.h"
#include "nmDataBinaryTools.h"
#include "nmDataPvtParaForPebi.h"
#include "nmDataTimeStepSetting.h"
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkType.h>
#include <vtkUnsignedCharArray.h>
namespace {
const int CONST_PVT_POINT_COUNT = 200;
// 该锁只保护 PEBI 网格单例缓存HX_NWTM.dll 的所有入口由独立进程级锁保护。
// 保留递归锁以兼容可能在持锁网格入口中调用缓存查询的旧代码路径。
QMutex s_oPebiGridMutex(QMutex::Recursive);
std::vector<double> buildConstantPvtVector(double value)
{
return std::vector<double>(CONST_PVT_POINT_COUNT, value);
}
std::vector<double> buildConstantPvtPressure()
{
std::vector<double> pressure(CONST_PVT_POINT_COUNT, 0.0);
for(int i = 0; i < CONST_PVT_POINT_COUNT; ++i) {
pressure[i] = i + 1.0;
}
return pressure;
}
QString solverModelFileTag(NM_SOLVER_MODEL_TYPE modelType)
{
switch(modelType) {
case SMT_Oil_ConstPvt:
return "T1_oil_const_pvt";
case SMT_Oil_VariablePvt:
return "T2_oil_variable_pvt";
case SMT_Water_ConstPvt:
return "T3_water_const_pvt";
case SMT_Water_VariablePvt:
return "T4_water_variable_pvt";
case SMT_Gas_VariablePvt:
return "T5_gas_variable_pvt";
case SMT_Oil_Water_TwoPhase:
return "T8_oil_water_two_phase";
default:
return QString("T%1_unsupported").arg(static_cast<int>(modelType));
}
}
void fillScenePvtByModel(nmDataBinaryTools::NM_PEBI_SCENE& scene,
NM_SOLVER_MODEL_TYPE modelType,
nmDataPvtParaForPebi* pvt,
nmDataReservoir* reservoir)
{
switch(modelType) {
case SMT_Oil_ConstPvt:
scene.PVT.p = buildConstantPvtPressure();
if(reservoir != nullptr) {
scene.PVT.Bo = buildConstantPvtVector(reservoir->getBo().getValue().toDouble());
scene.PVT.miuo = buildConstantPvtVector(reservoir->getMiuo().getValue().toDouble());
}
break;
case SMT_Oil_VariablePvt:
if(pvt != nullptr) {
scene.PVT.p = pvt->getPressure().toStdVector();
scene.PVT.Bo = pvt->getBo().toStdVector();
scene.PVT.Co = pvt->getCo().toStdVector();
scene.PVT.miuo = pvt->getMiuo().toStdVector();
}
break;
case SMT_Water_ConstPvt:
scene.PVT.p = buildConstantPvtPressure();
if(reservoir != nullptr) {
scene.PVT.Bw = buildConstantPvtVector(reservoir->getBw().getValue().toDouble());
scene.PVT.miuw = buildConstantPvtVector(reservoir->getMiuw().getValue().toDouble());
}
break;
case SMT_Water_VariablePvt:
if(pvt != nullptr) {
scene.PVT.p = pvt->getPressure().toStdVector();
scene.PVT.Bw = pvt->getBw().toStdVector();
scene.PVT.Cw = pvt->getCw().toStdVector();
scene.PVT.miuw = pvt->getMiuw().toStdVector();
}
break;
case SMT_Gas_VariablePvt:
case SMT_Gas_PseudoPressure:
if(pvt != nullptr) {
scene.PVT.p = pvt->getPressure().toStdVector();
scene.PVT.Bg = pvt->getBg().toStdVector();
scene.PVT.Cg = pvt->getCg().toStdVector();
scene.PVT.miug = pvt->getMiug().toStdVector();
}
break;
case SMT_Oil_Water_TwoPhase:
if(pvt != nullptr) {
scene.PVT.p = pvt->getPressure().toStdVector();
scene.PVT.Bo = pvt->getBo().toStdVector();
scene.PVT.miuo = pvt->getMiuo().toStdVector();
scene.PVT.Bw = pvt->getBw().toStdVector();
scene.PVT.miuw = pvt->getMiuw().toStdVector();
scene.PVT.So = pvt->getSo().toStdVector();
scene.PVT.Kro = pvt->getKro().toStdVector();
scene.PVT.Krw = pvt->getKrw().toStdVector();
}
break;
default:
break;
}
}
void fillScenePseudoPressureTable(nmDataBinaryTools::NM_PEBI_SCENE& scene,
NM_SOLVER_MODEL_TYPE modelType,
nmDataAnalyzeManager* pDataManager)
{
// 第一步:只有 T5 需要把当前井的转换表随场景交给离线 Runner。
if(modelType != SMT_Gas_VariablePvt) {
return;
}
// 第二步:使用本次网格任务显式绑定的数据管理器,禁止从其他窗口取数。
if(pDataManager == nullptr
|| !pDataManager->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
#include <windows.h>
#define DEBUG_OUT(msg) OutputDebugStringA(QString("[Mesh] %1\n").arg(msg).toLocal8Bit().data())
#endif
nmCalculationPebiGrid* nmCalculationPebiGrid::m_instance = nullptr;
nmCalculationPebiGrid* nmCalculationPebiGrid::getInstance()
{
if(m_instance == nullptr) {
m_instance = new nmCalculationPebiGrid();
}
return m_instance;
}
nmCalculationPebiGrid::nmCalculationPebiGrid()
: m_nPebiCount(-1),
m_nCachedGridInputRevision(0),
m_pDataManager(nullptr)
{
// 默认值来自HX_NWTM_GRID_INPUT构造函数
m_dGridControl = p0.GridControl;
// 清空原有PEBI网格输出数据
p1.TRI_cell.p.clear();
p1.TRI_cell.pindex.clear();
p1.TRI_cell.isplot.clear();
// 清空 PEBI_cell
p1.PEBI_cell.p.clear();
p1.PEBI_cell.pindex.clear();
p1.PEBI_cell.isplot.clear();
}
nmCalculationPebiGrid::~nmCalculationPebiGrid()
{
}
void nmCalculationPebiGrid::setGridControl(
double dGridControl,
nmDataAnalyzeManager* pDataManager)
{
QMutexLocker oLocker(&s_oPebiGridMutex);
// 第一步GridControl 必须为正数,非法值保持原有设置。
if(dGridControl <= 0.0) {
return;
}
// 第二步:优先写入所属分析方案,由方案负责版本递增和网格失效。
if(pDataManager != nullptr) {
pDataManager->setPebiGridControl(dGridControl);
}
// 第三步:同步单例缓存仅用于兼容仍直接读取该成员的内部网格输入构造。
m_dGridControl = dGridControl;
}
void nmCalculationPebiGrid::clearGridData(
const nmDataAnalyzeManager* pDataManager)
{
QMutexLocker oLocker(&s_oPebiGridMutex);
// 关闭某个网格窗口时只允许清理它自己的缓存,不能影响另一个成果窗口。
if(pDataManager != nullptr && m_pDataManager != pDataManager) {
return;
}
p0 = HX_NWTM_GRID_INPUT();
p1 = HX_NWTM_GRID_OUTPUT1();
p2 = HX_NWTM_GRID_OUTPUT2();
m_dGridControl = p0.GridControl;
m_nPebiCount = -1;
m_nCachedGridInputRevision = 0;
m_pDataManager = nullptr;
}
bool nmCalculationPebiGrid::copyCurrentGridFor(
const nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
int& nPebiCount) const
{
// 求解任务在主线程构造,不能为了复制缓存等待正在运行的网格 DLL。
// 锁忙时返回 false任务会在后台基于自己的值快照生成局部网格。
if(!s_oPebiGridMutex.tryLock()) {
return false;
}
// 第一步:在复制前同时校验所有权、输入版本和实际网格内容。
if(pDataManager == nullptr ||
m_pDataManager != pDataManager ||
!pDataManager->isPebiGridValid() ||
pDataManager->getNumericalAnalysisCase() == nullptr ||
m_nCachedGridInputRevision !=
pDataManager->getNumericalAnalysisCase()->getGridInputRevision() ||
p1.PEBI_cell.p.empty()) {
s_oPebiGridMutex.unlock();
return false;
}
// 第二步:一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
try {
oGridOutput1 = p1;
oGridOutput2 = p2;
nPebiCount = m_nPebiCount;
} catch(...) {
s_oPebiGridMutex.unlock();
throw;
}
s_oPebiGridMutex.unlock();
return true;
}
void nmCalculationPebiGrid::logInputParameters(const HX_NWTM_GRID_INPUT& input)
{
QString logMsg = "Input Parameters:\n";
logMsg += QString("GridControl: %1\n").arg(input.GridControl);
logMsg += QString("D: %1\n").arg(input.D);
// 记录边界信息
logMsg += QString("Boundary count: %1\n").arg(input.Boundary.size());
for(size_t i = 0; i < input.Boundary.size(); ++i) {
const auto& line = input.Boundary[i];
if(line.size() >= 4) {
logMsg += QString(" Boundary %1: (%2, %3) -> (%4, %5)\n")
.arg(i).arg(line[0]).arg(line[1]).arg(line[2]).arg(line[3]);
}
}
// 记录井信息
logMsg += QString("Vertical Wells count: %1\n").arg(input.VerticalWell.size());
for(size_t i = 0; i < input.VerticalWell.size(); ++i) {
const auto& well = input.VerticalWell[i];
if(well.size() >= 3) {
logMsg += QString(" Well %1: (%2, %3), radius: %4\n")
.arg(i).arg(well[0]).arg(well[1]).arg(well[2]);
}
}
// 记录垂直裂缝井以及裂缝信息
logMsg += QString("Fracture Vertical Wells And Fractures count: %1\n").arg(input.FractureVerticalWell.size());
for(size_t i = 0; i < input.FractureVerticalWell.size(); ++i) {
const auto& frac = input.FractureVerticalWell[i];
if(frac.size() >= 6) {
logMsg += QString(" Fracture %1: (%2, %3) -> (%4, %5), width: %6, FC: %7\n")
.arg(i).arg(frac[0]).arg(frac[1]).arg(frac[2]).arg(frac[3]).arg(frac[4]).arg(frac[5]);
} else if(frac.size() >= 5) {
logMsg += QString(" Fracture %1: (%2, %3) -> (%4, %5), width: %6\n")
.arg(i).arg(frac[0]).arg(frac[1]).arg(frac[2]).arg(frac[3]).arg(frac[4]);
}
}
// 记录多段压裂水平井信息
logMsg += QString("Multistage Fractured Horizontal Wells count: %1\n").arg(input.MultistageFracturedHorizontalWell.size());
for(size_t i = 0; i < input.MultistageFracturedHorizontalWell.size(); ++i) {
const auto& horizontalWell = input.MultistageFracturedHorizontalWell[i];
logMsg += QString(" Horizontal Fractured Well %1 (Fractures count: %2):\n").arg(i).arg(horizontalWell.size());
for(size_t j = 0; j < horizontalWell.size(); ++j) {
const auto& frac = horizontalWell[j];
if(frac.size() >= 6) {
logMsg += QString(" Fracture %1.%2: (%3, %4) -> (%5, %6), width: %7, FC: %8\n")
.arg(i).arg(j).arg(frac[0]).arg(frac[1]).arg(frac[2]).arg(frac[3]).arg(frac[4]).arg(frac[5]);
} else if(frac.size() >= 5) {
logMsg += QString(" Fracture %1.%2: (%3, %4) -> (%5, %6), width: %7\n")
.arg(i).arg(j).arg(frac[0]).arg(frac[1]).arg(frac[2]).arg(frac[3]).arg(frac[4]);
}
}
}
// 记录断层信息 (新增详细输出)
logMsg += QString("Faults count: %1\n").arg(input.Fault.size());
for(size_t i = 0; i < input.Fault.size(); ++i) {
const auto& faultSegment = input.Fault[i];
if(faultSegment.size() >= 4) { // 断层段至少有起点和终点
logMsg += QString(" Fault Segment %1: (%2, %3) -> (%4, %5)\n")
.arg(i).arg(faultSegment[0]).arg(faultSegment[1]).arg(faultSegment[2]).arg(faultSegment[3]);
}
}
qDebug() << logMsg;
}
void nmCalculationPebiGrid::logCurrentState()
{
QString stateMsg = "Current Output P1 State:\n";
// 记录一些关键状态信息
stateMsg += QString("PEBI_cell points count: %1\n").arg(p1.PEBI_cell.p.size());
stateMsg += QString("PEBI_cell pindex count: %1\n").arg(p1.PEBI_cell.pindex.size());
stateMsg += QString("PEBI_cell isplot count: %1\n").arg(p1.PEBI_cell.isplot.size());
qDebug() << stateMsg;
}
bool nmCalculationPebiGrid::meshGenPebiBoundary(
nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj)
{
// 1、从数据中心获取边界数据
nmDataOutline* pOutlineData = pDataManager != nullptr
? pDataManager->getOutlineData() : nullptr;
if(pOutlineData == nullptr) {
return false;
}
inputObj.Boundary.clear();
// 获取边界的点
QVector<QPointF> vecOutlinePoints = pOutlineData->getOutlinePoints();
// 顺时针进行构建
for(int i = 0; i < vecOutlinePoints.size(); i++) {
QPointF& startP = vecOutlinePoints[i];
QPointF endP;
if(i == vecOutlinePoints.size() - 1) {
endP = vecOutlinePoints.first();
} else {
endP = vecOutlinePoints[i + 1];
}
dVec1 line(4);
//line[0] = (int)startP.x();
//line[1] = (int)startP.y();
//line[2] = (int)endP.x();
//line[3] = (int)endP.y();
line[0] = qRound(startP.x()); // 四舍五入
line[1] = qRound(startP.y()); // 四舍五入
line[2] = qRound(endP.x()); // 四舍五入
line[3] = qRound(endP.y()); // 四舍五入
inputObj.Boundary.push_back(line);
}
return true;
}
bool nmCalculationPebiGrid::meshGenPebiWells(
nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT& inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder)
{
// 从数据中心获取井数据
//QVector<nmDataWellBase*> vecDataWells = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList();
//if (vecDataWells.size() == 0) {
// return true;
//}
if(pDataManager == nullptr) {
return false;
}
inputObj.VerticalWell.clear();
inputObj.FractureVerticalWell.clear();
inputObj.MultistageFracturedHorizontalWell.clear();
// 水平井初始化
inputObj.HorizontalWell.clear();
// 斜井
inputObj.InclinedWell.clear();
// 第一步:求解器顺序只写入局部快照,后台成功前不修改分析方案。
vecSolverWellOrder.clear();
QSet<QString> setEffectiveWellCodes;
QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
}
// 获取直井数据
QVector<nmDataVerticalWell*> verticalWells = pDataManager->getVerticalWellData();
// 获取垂直裂缝井数据
QVector<nmDataVerticalFracturedWell*> vFracturedWells = pDataManager->getVerticalFracturedWellData();
// 获取多段压裂水平井数据
QVector<nmDataHorizontalFracturedWell*> hFracturedWells = pDataManager->getHorizontalFracturedWellData();
// 处理直井数据
foreach(nmDataVerticalWell* pVerticalWell, verticalWells) {
if(pVerticalWell != nullptr &&
setEffectiveWellCodes.contains(pVerticalWell->getWellCode())) {
dVec1 well(3);
double dX = pVerticalWell->getX().getValue().toDouble();
double dY = pVerticalWell->getY().getValue().toDouble();
well[0] = dX;
well[1] = dY;
well[2] = pVerticalWell->getRadius().getValue().toDouble();
inputObj.VerticalWell.push_back(well);
// 求解器顺序只保存 WellCode井名不参与数组映射。
vecSolverWellOrder.append(nmSolverWellRef(
-1,
NM_WELL_MODEL::Vertical_Well,
pVerticalWell->getWellCode()));
}
}
// 处理垂直裂缝井数据
foreach(nmDataVerticalFracturedWell* pVerticalFracturedWell, vFracturedWells) {
if(pVerticalFracturedWell != nullptr &&
setEffectiveWellCodes.contains(pVerticalFracturedWell->getWellCode())) {
QVector<QPointF> vFracPoints = pVerticalFracturedWell->getFracs();
if(vFracPoints.size() == 2) {
dVec1 crack(6);
crack[0] = vFracPoints[0].x();
crack[1] = vFracPoints[0].y();
crack[2] = vFracPoints[1].x();
crack[3] = vFracPoints[1].y();
// 裂缝宽度直接取井对象中保存的面板参数。
crack[4] = pVerticalFracturedWell->getWidth().getValue().toDouble();
// FC直接取井对象中保存的裂缝导流能力0表示无限导流。
crack[5] = pVerticalFracturedWell->getDfc().getValue().toDouble();
inputObj.FractureVerticalWell.push_back(crack);
vecSolverWellOrder.append(nmSolverWellRef(
-1,
NM_WELL_MODEL::Vertical_Fractured_Well,
pVerticalFracturedWell->getWellCode()));
}
}
}
// 处理多段压裂水平井数据
foreach(nmDataHorizontalFracturedWell* pHorizontalFracturedWell, hFracturedWells) {
if(pHorizontalFracturedWell != nullptr &&
setEffectiveWellCodes.contains(pHorizontalFracturedWell->getWellCode())) {
QVector<QPair<QPointF, QPointF>> vvecFracPoints = pHorizontalFracturedWell->getFracs();
std::vector<std::vector<double>> vMultistageFracturedHorizontalWell;
for(int j = 0; j < vvecFracPoints.size(); ++j) {
dVec1 crack(6);
QPointF& vStartPoint = vvecFracPoints[j].first; // 起始点
QPointF& vEndPoint = vvecFracPoints[j].second; // 终止点
crack[0] = vStartPoint.x();
crack[1] = vStartPoint.y();
crack[2] = vEndPoint.x();
crack[3] = vEndPoint.y();
// 裂缝宽度直接取井对象中保存的面板参数。
crack[4] = pHorizontalFracturedWell->getWidth().getValue().toDouble();
// FC直接取井对象中保存的裂缝导流能力0表示无限导流。
crack[5] = pHorizontalFracturedWell->getDfc().getValue().toDouble();
vMultistageFracturedHorizontalWell.push_back(crack);
}
inputObj.MultistageFracturedHorizontalWell.push_back(vMultistageFracturedHorizontalWell);
vecSolverWellOrder.append(nmSolverWellRef(
-1,
NM_WELL_MODEL::Horizontal_Fractured_Well,
pHorizontalFracturedWell->getWellCode()));
}
}
return true;
// // 分类处理
// for (int i = 0; i < vecDataWells.size(); i++) {
// nmDataWellBase* pWellData = vecDataWells[i];
// if (pWellData != nullptr) {
//m_vecWellOrder.append(true);
// // 垂直裂缝井
// nmDataVerticalFracturedWell* pVerticalFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWellData);
// if (pVerticalFracturedWell != nullptr) {
// dVec1 crack(5);
// QVector<QPointF> vFracPoints = pVerticalFracturedWell->getFracs();
// if (vFracPoints.size() == 2) {
// crack[0] = (int)vFracPoints[0].x();
// crack[1] = (int)vFracPoints[0].y();
// crack[2] = (int)vFracPoints[1].x();
// crack[3] = (int)vFracPoints[1].y();
// // todo裂缝的宽度
// crack[4] = 1;
// //crack[4] = QString::number(0.050000000000000000000000000000000000000000, 'f', 2).toDouble();
// // 确保裂缝宽度保留两位小数
// //crack[4] = std::round(0.05 * 100) / 100;
// //crack[4] = static_cast<double>(static_cast<int>(0.06 * 100)) / 100;
// //crack[4] = qRound(0.06 * 100) / 100.0;
// qDebug() << crack[0] << crack[1] << crack[2] << crack[3] << crack[4];
// inputObj.FractureVerticalWell.push_back(crack);
// /*dVec1 c(5);
// c[0] = 200; c[1] = 200; c[2] = 400; c[3] = 200; c[4] = 0.05;
// inputObj.FractureVerticalWell.push_back(c);*/
// }
// continue;
// }
// // 多段压裂水平井
// nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWellData);
// if (pHorizontalFracturedWell != nullptr) {
// QVector<QPair<QPointF, QPointF>> vvecFracPoints = pHorizontalFracturedWell->getFracs();
// std::vector<std::vector<double>> vMultistageFracturedHorizontalWell;
// // 遍历 vvecFracPoints每个 QPair 包含一个裂缝的起始点和终止点
// for (int j = 0; j < vvecFracPoints.size(); ++j) {
// dVec1 crack(5);
// QPointF& vStartPoint = vvecFracPoints[j].first; // 起始点
// QPointF& vEndPoint = vvecFracPoints[j].second; // 终止点
// crack[0] = (int)vStartPoint.x();
// crack[1] = (int)vStartPoint.y();
// crack[2] = (int)vEndPoint.x();
// crack[3] = (int)vEndPoint.y();
// // todo裂缝的宽度
// crack[4] = 1;
// vMultistageFracturedHorizontalWell.push_back(crack);
// }
// inputObj.MultistageFracturedHorizontalWell.push_back(vMultistageFracturedHorizontalWell);
// continue;
// }
// // 直井
// nmDataVerticalWell* pVertialWell = dynamic_cast<nmDataVerticalWell*>(pWellData);
// if (pVertialWell != nullptr) {
// dVec1 well(3);
// double dX = pVertialWell->getX().getValue().toDouble();
// double dY = pVertialWell->getY().getValue().toDouble();
// well[0] = dX;
// well[1] = dY;
// well[2] = pVertialWell->getRadius().getValue().toDouble();
// inputObj.VerticalWell.push_back(well);
// continue;
// }
// }
// }
//return true;
}
bool nmCalculationPebiGrid::meshGenPebiFault(
nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT & inputObj)
{
// 从数据中心获取断层数据
QVector<nmDataFault*> vecDataFault = pDataManager != nullptr
? pDataManager->getFaultDataList() : QVector<nmDataFault*>();
inputObj.Fault.clear();
// 断层,没有宽度
for(int i = 0; i < vecDataFault.size(); i++) {
nmDataFault* pFault = vecDataFault[i];
QVector<QPointF> vecPlts = pFault->getFaultPoints();
// 对每个断层进行处理
// 每个断层包括 多段
if(vecPlts.size() > 0) {
for(int j = 0; j < vecPlts.size() - 1; j++) {
//dVec1 frac(5);
dVec1 frac(4);
QPointF& vStartPoint = vecPlts[j];
QPointF& vEndPoint = vecPlts[j + 1];
// 起始点、终止点,没有宽度
frac[0] = vStartPoint.x();
frac[1] = vStartPoint.y();
frac[2] = vEndPoint.x();
frac[3] = vEndPoint.y();
inputObj.Fault.push_back(frac);
}
}
}
return true;
}
bool nmCalculationPebiGrid::meshGenPebiCrack(
nmDataAnalyzeManager* pDataManager,
HX_NWTM_GRID_INPUT &inputObj,
QVector<nmSolverWellRef>& vecSolverWellOrder)
{
// 从数据中心获取裂缝几何数据
QVector<nmDataFracture*> vecDataFracture = pDataManager != nullptr
? pDataManager->getFractureDataList() : QVector<nmDataFracture*>();
// 裂缝,将所有裂缝当一个 裂缝直井来处理
for(int i = 0; i < vecDataFracture.size(); i++) {
nmDataFracture* pFrac = vecDataFracture[i];
QVector<QPointF> vecPlts = pFrac->getFracturePoints();
// 对每个裂缝进行处理
// 每个裂缝包括 多段
if(vecPlts.size() > 0) {
for(int j = 0; j < vecPlts.size() - 1; j++) {
dVec1 crack(6);
QPointF& vStartPoint = vecPlts[j];
QPointF& vEndPoint = vecPlts[j + 1];
// 起始点、终止点,没有宽度
crack[0] = vStartPoint.x();
crack[1] = vStartPoint.y();
crack[2] = vEndPoint.x();
crack[3] = vEndPoint.y();
// todo裂缝的宽度
crack[4] = 1;
// 手动画裂缝目前只支持无限导流FC固定为0。
crack[5] = 0.0;
qDebug() << crack[0] << crack[1] << crack[2] << crack[3] << crack[4] << crack[5];
int iIndex = inputObj.VerticalWell.size() + inputObj.FractureVerticalWell.size();
inputObj.FractureVerticalWell.push_back(crack);
//int iIndex = inputObj.VerticalWell.size() + inputObj.FractureVerticalWell.size();
// 添加到自定义井顺序数组
vecSolverWellOrder.insert(
iIndex,
nmSolverWellRef(iIndex,
NM_WELL_MODEL::Unknow_Well,
QString(),
NM_SolverEntry_ManualFracture));
}
}
}
return true;
}
void nmCalculationPebiGrid::genPebiVTK(const HX_NWTM_GRID_OUTPUT1 &P1, QString vtkDir)
{
// 1. 提取所有点数据
QVector<QPair<double, double> > vPoints;
for(size_t i = 0; i < P1.PEBI_cell.p.size(); ++i) {
vPoints.append(QPair<double, double>(P1.PEBI_cell.p[i].x, P1.PEBI_cell.p[i].y));
}
// 2. 预处理单元数据 - 只处理isplot为1的单元
QVector<QVector<int> > vCells;
QVector<int> vCellTypes; // 存储每个单元的类型
int totalCellDataSize = 0;
for(size_t i = 0; i < P1.PEBI_cell.pindex.size(); ++i) {
// 只处理isplot为1的单元
if(i >= P1.PEBI_cell.isplot.size() || P1.PEBI_cell.isplot[i] != 1) {
continue;
}
const std::vector<int>& vecIndices = P1.PEBI_cell.pindex[i];
if(vecIndices.empty()) continue;
// 确定实际点数(检查首尾是否相同)
int numPoints = vecIndices.size();
if(numPoints > 1 && vecIndices[0] == vecIndices[numPoints - 1]) {
numPoints--;
}
// 跳过无效单元点数小于2
if(numPoints < 2) continue;
// 确定单元类型
int cellType = 0;
if(numPoints == 2) { // 线
cellType = 3; // VTK_LINE
} else if(numPoints == 3) { // 三角形
cellType = 5; // VTK_TRIANGLE
} else if(numPoints == 4) { // 四边形
cellType = 9; // VTK_QUAD
} else { // 多边形
cellType = 7; // VTK_POLYGON
}
// 存储单元数据
QVector<int> cellData;
cellData.append(numPoints);
for(int j = 0; j < numPoints; ++j) {
cellData.append(vecIndices[j]);
}
vCells.append(cellData);
vCellTypes.append(cellType);
totalCellDataSize += (numPoints + 1); // +1 for the numPoints entry
}
// 3. 构建VTK文件内容
QStringList vtkContents;
vtkContents.append("# vtk DataFile Version 4.0");
vtkContents.append("Unstructured Grid");
vtkContents.append("ASCII");
vtkContents.append("DATASET UNSTRUCTURED_GRID");
// 写入点数据
vtkContents.append(QString("POINTS %1 float").arg(vPoints.size()));
for(int i = 0; i < vPoints.size(); ++i) {
vtkContents.append(QString("%1 %2 0.0").arg(vPoints[i].first).arg(vPoints[i].second));
}
// 写入单元数据
vtkContents.append(QString("\nCELLS %1 %2").arg(vCells.size()).arg(totalCellDataSize));
for(int i = 0; i < vCells.size(); ++i) {
QString line;
const QVector<int>& cellData = vCells[i];
for(int j = 0; j < cellData.size(); ++j) {
line += QString::number(cellData[j]);
if(j < cellData.size() - 1) {
line += " ";
}
}
vtkContents.append(line);
}
// 写入单元类型
vtkContents.append(QString("\nCELL_TYPES %1").arg(vCellTypes.size()));
for(int i = 0; i < vCellTypes.size(); ++i) {
vtkContents.append(QString::number(vCellTypes[i]));
}
// 写入文件
nmCalculationUtils::writeFile(vtkContents, vtkDir + "/pebi.vtk");
}
HX_NWTM_GRID_OUTPUT1 nmCalculationPebiGrid::getGridOutput1()
{
QMutexLocker oLocker(&s_oPebiGridMutex);
return p1;
}
HX_NWTM_GRID_OUTPUT2 nmCalculationPebiGrid::getGridOutput2()
{
QMutexLocker oLocker(&s_oPebiGridMutex);
return p2;
}
int nmCalculationPebiGrid::getPebiCount() const
{
QMutexLocker oLocker(&s_oPebiGridMutex);
return m_nPebiCount;
}
vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructuredGrid(const HX_NWTM_GRID_OUTPUT1& P1)
{
vtkSmartPointer<vtkUnstructuredGrid> pUnstructuredGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
vtkSmartPointer<vtkPoints> pPoints = vtkSmartPointer<vtkPoints>::New();
// 用于存储所有有效单元引用的唯一点索引
QSet<int> setUniquePointIndices;
// 1. 预处理单元数据,确定哪些单元是有效的,并收集这些单元引用的所有唯一点索引
// 这一步先不向 vtkPoints 添加点,而是收集需要添加的点的索引。
for(size_t i = 0; i < P1.PEBI_cell.pindex.size(); ++i) {
// 只处理 isplot 为 1 的单元
if(i >= P1.PEBI_cell.isplot.size() || P1.PEBI_cell.isplot[i] != 1) {
continue;
}
const std::vector<int>& vecIndices = P1.PEBI_cell.pindex[i];
if(vecIndices.empty()) continue;
// 确定实际点数(检查首尾是否相同)
int nPointsInCell = vecIndices.size();
if(nPointsInCell > 1 && vecIndices[0] == vecIndices[nPointsInCell - 1]) {
nPointsInCell--;
}
if(nPointsInCell < 2) continue; // 过滤掉无效单元点数小于2
// 将此有效单元引用的所有点索引添加到 setUniquePointIndices 集合中
for(int j = 0; j < nPointsInCell; ++j) {
setUniquePointIndices.insert(vecIndices[j]);
}
}
// 创建一个映射表,将原始点索引映射到 VTK 中的新点索引
QMap<int, vtkIdType> mapOriginalToVtkPointId;
vtkIdType currentVtkPointId = 0;
// 2. 根据收集到的唯一点索引,将这些点添加到 vtkPoints
pPoints->SetNumberOfPoints(setUniquePointIndices.size());
// 遍历 setUniquePointIndices
foreach(int nOriginalIdx, setUniquePointIndices) {
if(nOriginalIdx >= 0 && nOriginalIdx < P1.PEBI_cell.p.size()) {
pPoints->SetPoint(currentVtkPointId, P1.PEBI_cell.p[nOriginalIdx].x, P1.PEBI_cell.p[nOriginalIdx].y, 0.0);
mapOriginalToVtkPointId[nOriginalIdx] = currentVtkPointId;
currentVtkPointId++;
} else {
// 处理异常情况:如果 setUniquePointIndices 中包含了无效的原始点索引
qDebug() << "Warning: Invalid original point index" << nOriginalIdx << "found in setUniquePointIndices.";
}
}
pUnstructuredGrid->SetPoints(pPoints);
// 3. 再次遍历单元数据,这次是根据新的 VTK 点索引来插入单元
for(size_t i = 0; i < P1.PEBI_cell.pindex.size(); ++i) {
// 再次检查 isplot 标志,确保只处理有效单元
if(i >= P1.PEBI_cell.isplot.size() || P1.PEBI_cell.isplot[i] != 1) {
continue;
}
const std::vector<int>& vecIndices = P1.PEBI_cell.pindex[i];
if(vecIndices.empty()) continue;
// 检查首尾是否重复
int nPointsInCell = vecIndices.size();
if(nPointsInCell > 1 && vecIndices[0] == vecIndices[nPointsInCell - 1]) {
nPointsInCell--;
}
if(nPointsInCell < 2) continue; // 过滤掉无效单元
int vtkCellType = 0;
if(nPointsInCell == 2) {
vtkCellType = VTK_LINE;
} else if(nPointsInCell == 3) {
vtkCellType = VTK_TRIANGLE;
} else if(nPointsInCell == 4) {
vtkCellType = VTK_QUAD;
} else {
vtkCellType = VTK_POLYGON;
}
// 使用映射表将原始索引转换为新的 VTK 索引
vtkIdType* pts = new vtkIdType[nPointsInCell];
for(int j = 0; j < nPointsInCell; ++j) {
if(mapOriginalToVtkPointId.contains(vecIndices[j])) { // 确保点在映射表中
pts[j] = mapOriginalToVtkPointId[vecIndices[j]];
} else {
qDebug() << "Error: Point" << vecIndices[j] << "for cell" << i << "not found in map. This should not happen!";
// 可以选择跳过此单元或进行其他错误处理
delete[] pts;
return nullptr;
}
}
pUnstructuredGrid->InsertNextCell(vtkCellType, nPointsInCell, pts);
delete[] pts;
}
return pUnstructuredGrid;
}
bool nmCalculationPebiGrid::captureInputSnapshot(
nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot)
{
// 使用具名常量触发复制赋值,兼容 Qt 4.8 配套的 VS2010 运行库 ABI。
const nmPebiGridInputSnapshot oEmptySnapshot;
oSnapshot = oEmptySnapshot;
if(pDataManager == nullptr) {
return false;
}
// DataManager 及井对象都由所属线程维护。快照只允许在该线程创建,后台任务
// 随后只读取复制出的 STL/Qt 值类型,不能再次访问这些可变对象。
if(QThread::currentThread() != pDataManager->thread()) {
qWarning() << "PEBI input snapshot must be captured on the DataManager thread.";
return false;
}
const nmDataNumericalAnalysisCase* pAnalysisCase =
pDataManager->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr) {
return false;
}
// 第一步:先记录版本并复制网格控制参数。调用方必须在启动后台线程前完成本函数。
oSnapshot.m_nGridInputRevision =
pAnalysisCase->getGridInputRevision();
oSnapshot.m_oGridInput = HX_NWTM_GRID_INPUT();
oSnapshot.m_oGridInput.GridControl =
pDataManager->getPebiGridControl();
// 第二步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。
if(!meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput) ||
!meshGenPebiWells(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder) ||
!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) ||
!meshGenPebiCrack(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder)) {
return false;
}
for(int nIndex = 0;
nIndex < oSnapshot.m_vecSolverWellOrder.size();
++nIndex) {
oSnapshot.m_vecSolverWellOrder[nIndex].m_nSolverIndex = nIndex;
}
// 第三步:真实井必须与本次有效计算井一一对应;手工裂缝只占槽位,不参与集合比较。
QSet<QString> setEffectiveWellCodes;
const QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
}
QSet<QString> setOrderedWellCodes;
bool bSolverOrderValid = !setEffectiveWellCodes.isEmpty();
for(int nIndex = 0;
bSolverOrderValid &&
nIndex < oSnapshot.m_vecSolverWellOrder.size();
++nIndex) {
const nmSolverWellRef& oWellRef =
oSnapshot.m_vecSolverWellOrder[nIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
continue;
}
bSolverOrderValid =
oWellRef.m_eEntryKind == NM_SolverEntry_Well &&
setEffectiveWellCodes.contains(oWellRef.m_sWellCode) &&
!setOrderedWellCodes.contains(oWellRef.m_sWellCode);
if(bSolverOrderValid) {
setOrderedWellCodes.insert(oWellRef.m_sWellCode);
}
}
bSolverOrderValid = bSolverOrderValid &&
setOrderedWellCodes == setEffectiveWellCodes;
if(!bSolverOrderValid) {
qWarning() << "PEBI solver order does not match effective wells.";
return false;
}
// 第四步:场景和授权路径同样在捕获阶段读取,后台不再接触任何井对象。
if(!buildPebiScene(pDataManager,
oSnapshot.m_oGridInput,
oSnapshot.m_vecSolverWellOrder,
oSnapshot.m_oScene)) {
return false;
}
oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
oSnapshot.m_bValid = true;
return true;
}
bool nmCalculationPebiGrid::buildPebiScene(
nmDataAnalyzeManager* pDataManager,
const HX_NWTM_GRID_INPUT& oGridInput,
const QVector<nmSolverWellRef>& vecSolverWellOrder,
nmDataBinaryTools::NM_PEBI_SCENE& oScene)
{
if(pDataManager == nullptr) {
return false;
}
// 第一步:复制网格基础数据和井显示信息。
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
oScene = oEmptyScene;
oScene.version = 1;
oScene.D = oGridInput.D;
oScene.GridControl = oGridInput.GridControl;
oScene.Boundary = oGridInput.Boundary;
oScene.VerticalWell = oGridInput.VerticalWell;
oScene.HorizontalWell = oGridInput.HorizontalWell;
oScene.FractureVerticalWell = oGridInput.FractureVerticalWell;
oScene.MultistageFracturedHorizontalWell =
oGridInput.MultistageFracturedHorizontalWell;
oScene.InclinedWell = oGridInput.InclinedWell;
oScene.Fault = oGridInput.Fault;
oScene.wellType.clear();
oScene.wellName.clear();
oScene.wellType.reserve(vecSolverWellOrder.size());
oScene.wellName.reserve(vecSolverWellOrder.size());
for(int nIndex = 0; nIndex < vecSolverWellOrder.size(); ++nIndex) {
const nmSolverWellRef& oWellRef = vecSolverWellOrder[nIndex];
oScene.wellType.push_back(static_cast<int>(oWellRef.m_eWellType));
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
oScene.wellName.push_back(pWellData != nullptr
? pWellData->getWellName()
: QString());
}
// 第二步:按求解器模型填充每个槽位的产量制度。
const NM_SOLVER_MODEL_TYPE eSolverModelType =
pDataManager->getSolverModelType();
oScene.solverType = static_cast<int>(eSolverModelType);
oScene.Rate.t.resize(vecSolverWellOrder.size());
oScene.Rate.qo.resize(vecSolverWellOrder.size());
oScene.Rate.qg.resize(vecSolverWellOrder.size());
oScene.Rate.qw.resize(vecSolverWellOrder.size());
for(int nWellIndex = 0;
nWellIndex < vecSolverWellOrder.size();
++nWellIndex) {
const nmSolverWellRef& oWellRef =
vecSolverWellOrder[nWellIndex];
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
pDataManager->getCalculationWellMode(
oWellRef.m_sWellCode) == NM_CaseWell_Observation) {
continue;
}
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
continue;
}
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
if(!vecTimeQ.isEmpty() &&
vecTimeQ[0].x() == 0.0 &&
vecTimeQ[0].y() == 0.0) {
vecTimeQ.remove(0);
}
std::vector<double> vecTime;
std::vector<double> vecRate;
vecTime.reserve(vecTimeQ.size());
vecRate.reserve(vecTimeQ.size());
for(int nPointIndex = 0;
nPointIndex < vecTimeQ.size();
++nPointIndex) {
vecTime.push_back(vecTimeQ[nPointIndex].x());
vecRate.push_back(vecTimeQ[nPointIndex].y());
}
oScene.Rate.t[nWellIndex] = vecTime;
oScene.Rate.qo[nWellIndex].assign(vecRate.size(), 0.0);
oScene.Rate.qg[nWellIndex].assign(vecRate.size(), 0.0);
oScene.Rate.qw[nWellIndex].assign(vecRate.size(), 0.0);
if(eSolverModelType == SMT_Gas_VariablePvt ||
eSolverModelType == SMT_Gas_PseudoPressure) {
oScene.Rate.qg[nWellIndex] = vecRate;
} else if(eSolverModelType == SMT_Water_ConstPvt ||
eSolverModelType == SMT_Water_VariablePvt) {
oScene.Rate.qw[nWellIndex] = vecRate;
} else {
oScene.Rate.qo[nWellIndex] = vecRate;
}
}
// 油水两相当前按定产油量处理,气、水产量数组保持等长零值。
if(eSolverModelType == SMT_Oil_Water_TwoPhase) {
for(size_t nWellIndex = 0;
nWellIndex < oScene.Rate.t.size();
++nWellIndex) {
oScene.Rate.qg[nWellIndex].assign(
oScene.Rate.t[nWellIndex].size(), 0.0);
oScene.Rate.qw[nWellIndex].assign(
oScene.Rate.t[nWellIndex].size(), 0.0);
}
}
// 第三步:复制井筒储集、表皮和流量段索引。
oScene.CS.C.resize(vecSolverWellOrder.size());
oScene.CS.S.resize(vecSolverWellOrder.size());
oScene.wellFlowSectionIndex.resize(vecSolverWellOrder.size());
for(int nWellIndex = 0;
nWellIndex < vecSolverWellOrder.size();
++nWellIndex) {
const nmSolverWellRef& oWellRef =
vecSolverWellOrder[nWellIndex];
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
oScene.CS.C[nWellIndex] = 0.0;
oScene.CS.S[nWellIndex] = 0.0;
oScene.wellFlowSectionIndex[nWellIndex] = 1;
continue;
}
nmDataWellBase* pWellData =
pDataManager->findWellByCode(oWellRef.m_sWellCode);
if(pWellData == nullptr) {
oScene.wellFlowSectionIndex[nWellIndex] = 1;
continue;
}
oScene.CS.C[nWellIndex] =
pWellData->getWellboreStorage().getValue().toDouble();
oScene.CS.S[nWellIndex] =
pWellData->getPerforationCount() > 0
? pWellData->getPerforation(0)->getSkin()
.getValue().toDouble()
: 0.0;
oScene.wellFlowSectionIndex[nWellIndex] =
pWellData->getIndexF();
}
// 第四步:复制 PVT、拟压力、储层和时间步参数。
nmDataReservoir* pReservoirData =
pDataManager->getReservoirData();
nmDataPvtParaForPebi* pPvtData =
pDataManager->getPebiPvtPara();
fillScenePvtByModel(oScene,
eSolverModelType,
pPvtData,
pReservoirData);
fillScenePseudoPressureTable(oScene,
eSolverModelType,
pDataManager);
if(pReservoirData != nullptr) {
oScene.Base.Pi = pReservoirData->getInitialPressure()
.getValue().toDouble();
oScene.Base.Cti = pReservoirData->getCt()
.getValue().toDouble();
oScene.Base.Cf = pReservoirData->getCf()
.getValue().toDouble();
oScene.Base.Soi = pReservoirData->getSoi()
.getValue().toDouble();
oScene.Base.Sgi = pReservoirData->getSgi()
.getValue().toDouble();
oScene.Base.Swi = pReservoirData->getSwi()
.getValue().toDouble();
oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(
pReservoirData->getPermeability()
.getValue().toDouble());
oScene.Base.phi_ref = pReservoirData->getPorosity()
.getValue().toDouble();
oScene.Base.h_ref = pReservoirData->getThickness()
.getValue().toDouble();
}
nmDataTimeStepSetting* pTimeStepSetting =
pDataManager->getTimeStep();
if(pTimeStepSetting != nullptr) {
oScene.Base.d = pTimeStepSetting->getTimeGrowthExponent()
.getValue().toDouble();
oScene.Base.dt_Min = pTimeStepSetting->getMinDeltaTAttribute()
.getValue().toDouble();
oScene.Base.dt_Max = pTimeStepSetting->getMaxDeltaTAttribute()
.getValue().toDouble();
}
return true;
}
bool nmCalculationPebiGrid::calculateSnapshot(
const nmPebiGridInputSnapshot& oSnapshot,
nmPebiGridResult& oResult,
bool bCreateUnstructuredGrid)
{
QMutexLocker oLocker(&s_oPebiGridMutex);
const nmPebiGridResult oEmptyResult;
oResult = oEmptyResult;
if(!oSnapshot.m_bValid) {
return false;
}
// 建网、模型求解和 Kriging 共用 DLL 全局状态,加载至读取结果期间必须串行。
QMutexLocker oDllLocker(
nmCalculationUtils::getHxNwtmDllMutex());
HMODULE hGridModule = LoadLibrary(L"HX_NWTM.dll");
if(hGridModule == nullptr) {
qWarning() << "Failed to load HX_NWTM.dll. Error code:"
<< GetLastError();
return false;
}
typedef void (*HX_NWTM_GRID_Func)(
HX_NWTM_GRID_OUTPUT1&,
HX_NWTM_GRID_OUTPUT2&,
const HX_NWTM_GRID_INPUT&,
std::string);
typedef int (*GetIntValueFunc)();
HX_NWTM_GRID_Func pfnGenerateGrid =
reinterpret_cast<HX_NWTM_GRID_Func>(
GetProcAddress(hGridModule, "HX_NWTM_GRID"));
GetIntValueFunc pfnGetPebiCount =
reinterpret_cast<GetIntValueFunc>(
GetProcAddress(hGridModule, "getPEBInum"));
if(pfnGenerateGrid == nullptr || pfnGetPebiCount == nullptr) {
qWarning() << "Failed to resolve HX_NWTM grid functions. Error code:"
<< GetLastError();
FreeLibrary(hGridModule);
return false;
}
try {
// 第一步:后台写出已经值化的场景,不再回查当前成果或井对象。
QDir oAppDir(QCoreApplication::applicationDirPath());
QDir oDataDir(oAppDir.filePath(
"../../ML/nmWTAI-ML/data/temp"));
if(!oDataDir.exists()) {
oDataDir.mkpath(".");
}
const NM_SOLVER_MODEL_TYPE eSolverModelType =
static_cast<NM_SOLVER_MODEL_TYPE>(
oSnapshot.m_oScene.solverType);
const QString sScenePath = oDataDir.filePath(
QString("scene_%1.bin")
.arg(solverModelFileTag(eSolverModelType)));
if(!nmDataBinaryTools::savePebiSceneBin(
sScenePath, oSnapshot.m_oScene)) {
zxLogInstance::getInstance()->writeLogF(
"savePebiSceneBin failed: " +
nmDataBinaryTools::getLastError());
FreeLibrary(hGridModule);
return false;
}
zxLogInstance::getInstance()->writeLogF(
"scene exported: " + sScenePath);
// 第二步DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。
pfnGenerateGrid(oResult.m_oGridOutput1,
oResult.m_oGridOutput2,
oSnapshot.m_oGridInput,
oSnapshot.m_sLicensePath.toStdString());
oResult.m_nPebiCount = pfnGetPebiCount();
// 后续 VTK 构造只读取本次局部输出,无需继续占用进程级 DLL 锁。
FreeLibrary(hGridModule);
hGridModule = nullptr;
oDllLocker.unlock();
// 自动拟合只需要 DLL 数组,可跳过 VTK 构造;网格任务必须生成完整 VTK。
if(bCreateUnstructuredGrid) {
oResult.m_pUnstructuredGrid =
createPebiUnstructuredGrid(
oResult.m_oGridOutput1);
if(oResult.m_pUnstructuredGrid == nullptr ||
oResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
return false;
}
}
oResult.m_bSucceeded = true;
} catch(const std::exception& e) {
zxLogInstance::getInstance()->writeLogF(
QString("C++ Exception: %1").arg(e.what()));
logInputParameters(oSnapshot.m_oGridInput);
if(hGridModule != nullptr) {
FreeLibrary(hGridModule);
}
return false;
} catch(...) {
zxLogInstance::getInstance()->writeLogF(
"SEH Exception Occurred");
logInputParameters(oSnapshot.m_oGridInput);
if(hGridModule != nullptr) {
FreeLibrary(hGridModule);
}
return false;
}
return true;
}
void nmCalculationPebiGrid::commitOutputCache(
nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
const nmPebiGridResult& oResult)
{
p0 = oSnapshot.m_oGridInput;
p1 = oResult.m_oGridOutput1;
p2 = oResult.m_oGridOutput2;
m_dGridControl = oSnapshot.m_oGridInput.GridControl;
m_nPebiCount = oResult.m_nPebiCount;
m_nCachedGridInputRevision = oSnapshot.m_nGridInputRevision;
m_pDataManager = pDataManager;
}
bool nmCalculationPebiGrid::commitSnapshotResult(
nmDataAnalyzeManager* pDataManager,
const nmPebiGridInputSnapshot& oSnapshot,
const nmPebiGridResult& oResult)
{
QMutexLocker oLocker(&s_oPebiGridMutex);
if(pDataManager == nullptr ||
!oSnapshot.m_bValid ||
!oResult.m_bSucceeded ||
oResult.m_pUnstructuredGrid == nullptr) {
return false;
}
// 第一步DataManager 在主线程内按版本整体替换井顺序、有效状态和 VTK 网格。
if(!pDataManager->commitPebiGridResult(
oSnapshot.m_nGridInputRevision,
oSnapshot.m_vecSolverWellOrder,
oResult.m_pUnstructuredGrid)) {
return false;
}
// 第二步:只有成果提交成功后才更新进程级 DLL 缓存,旧任务不能覆盖新网格。
commitOutputCache(pDataManager, oSnapshot, oResult);
return true;
}