|
|
#include "nmCalculationPebiGrid.h"
|
|
|
#include <Windows.h>
|
|
|
#include <QDebug>
|
|
|
#include <QMutex>
|
|
|
#include <QMutexLocker>
|
|
|
#include <QThread>
|
|
|
#include <cmath>
|
|
|
#include <QSet>
|
|
|
#include <QHash>
|
|
|
|
|
|
#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;
|
|
|
|
|
|
typedef void (*HX_NWTM_GRID_Func)(
|
|
|
HX_NWTM_GRID_OUTPUT1&,
|
|
|
HX_NWTM_GRID_OUTPUT2&,
|
|
|
const HX_NWTM_GRID_INPUT&,
|
|
|
std::string);
|
|
|
|
|
|
__declspec(noinline) void invokePebiGridDll(
|
|
|
HX_NWTM_GRID_Func pfnGenerateGrid,
|
|
|
const nmPebiGridInputSnapshot& oSnapshot,
|
|
|
nmPebiGridResult& oResult)
|
|
|
{
|
|
|
// 单独调用网格 DLL,避免字符串参数的构造、析构与异常捕获放在同一函数中导致编译错误。
|
|
|
pfnGenerateGrid(oResult.m_oGridOutput1, oResult.m_oGridOutput2,
|
|
|
oSnapshot.m_oGridInput, oSnapshot.m_sLicensePath.toStdString());
|
|
|
}
|
|
|
|
|
|
bool invokePebiGridDllGuarded(HX_NWTM_GRID_Func pfnGenerateGrid,
|
|
|
const nmPebiGridInputSnapshot& oSnapshot,
|
|
|
nmPebiGridResult& oResult)
|
|
|
{
|
|
|
// 仅拦截已复现的访问违例;普通 C++ 异常继续交给外层 catch,堆损坏不在此恢复。
|
|
|
__try {
|
|
|
invokePebiGridDll(pfnGenerateGrid, oSnapshot, oResult);
|
|
|
} __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION
|
|
|
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 该锁只保护 PEBI 网格单例缓存;HX_NWTM.dll 的所有入口由独立进程级锁保护。
|
|
|
// 保留递归锁以兼容可能在持锁网格入口中调用缓存查询的旧代码路径。
|
|
|
QMutex s_oPebiGridMutex(QMutex::Recursive);
|
|
|
|
|
|
bool isCancellationRequested(const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
return pCancelRequested != NULL &&
|
|
|
static_cast<int>(*pCancelRequested) != 0;
|
|
|
}
|
|
|
|
|
|
class nmInterruptibleMutexLocker
|
|
|
{
|
|
|
public:
|
|
|
nmInterruptibleMutexLocker()
|
|
|
: m_pMutex(NULL),
|
|
|
m_bLocked(false)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
~nmInterruptibleMutexLocker()
|
|
|
{
|
|
|
unlock();
|
|
|
}
|
|
|
|
|
|
bool lock(QMutex* pMutex, const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
if(pMutex == NULL || m_bLocked) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pCancelRequested == NULL) {
|
|
|
pMutex->lock();
|
|
|
} else {
|
|
|
// 等锁阶段不能无限阻塞;锁顺序仍保持“网格缓存锁 -> DLL 全局锁”。
|
|
|
while(!pMutex->tryLock(100)) {
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
m_pMutex = pMutex;
|
|
|
m_bLocked = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool tryLock(QMutex* pMutex)
|
|
|
{
|
|
|
if(pMutex == NULL || m_bLocked || !pMutex->tryLock()) {
|
|
|
return false;
|
|
|
}
|
|
|
m_pMutex = pMutex;
|
|
|
m_bLocked = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void unlock()
|
|
|
{
|
|
|
if(m_bLocked && m_pMutex != NULL) {
|
|
|
m_pMutex->unlock();
|
|
|
m_bLocked = false;
|
|
|
m_pMutex = NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
QMutex* m_pMutex;
|
|
|
bool m_bLocked;
|
|
|
};
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
quint64 nGridInputRevision,
|
|
|
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
|
|
|
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
|
|
|
int& nPebiCount,
|
|
|
const QAtomicInt* pCancelRequested) const
|
|
|
{
|
|
|
nmInterruptibleMutexLocker oGridLocker;
|
|
|
const bool bLocked = pCancelRequested == NULL
|
|
|
? oGridLocker.tryLock(&s_oPebiGridMutex)
|
|
|
: oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested);
|
|
|
if(!bLocked) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 后台只比较捕获时的所有权和版本值,不能再读取 DataManager 内部状态。
|
|
|
if(pDataManager == nullptr ||
|
|
|
m_pDataManager != pDataManager ||
|
|
|
m_nCachedGridInputRevision != nGridInputRevision ||
|
|
|
p1.PEBI_cell.p.empty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
|
|
|
oGridOutput1 = p1;
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
oGridOutput2 = p2;
|
|
|
nPebiCount = m_nPebiCount;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::isCurrentGridAvailableFor(
|
|
|
const nmDataAnalyzeManager* pDataManager,
|
|
|
quint64 nGridInputRevision) const
|
|
|
{
|
|
|
// 该接口在主线程只做常量时间校验,不复制大型网格输出;锁忙时交给后台重建。
|
|
|
if(!s_oPebiGridMutex.tryLock()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const bool bAvailable = pDataManager != nullptr &&
|
|
|
m_pDataManager == pDataManager &&
|
|
|
pDataManager->isPebiGridValid() &&
|
|
|
m_nCachedGridInputRevision == nGridInputRevision &&
|
|
|
!p1.PEBI_cell.p.empty();
|
|
|
s_oPebiGridMutex.unlock();
|
|
|
return bAvailable;
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
const QSet<QString>& setEffectiveWellCodes)
|
|
|
{
|
|
|
|
|
|
// 从数据中心获取井数据
|
|
|
//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();
|
|
|
|
|
|
// 获取直井数据
|
|
|
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,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
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) {
|
|
|
if((i % 256) == 0 && isCancellationRequested(pCancelRequested)) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
// 只处理 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) {
|
|
|
if((i % 256) == 0 && isCancellationRequested(pCancelRequested)) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
// 再次检查 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::beginManualInputSnapshot(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
nmPebiGridInputSnapshot& oSnapshot)
|
|
|
{
|
|
|
const nmPebiGridInputSnapshot oEmptySnapshot;
|
|
|
oSnapshot = oEmptySnapshot;
|
|
|
if(pDataManager == nullptr ||
|
|
|
QThread::currentThread() != pDataManager->thread()) {
|
|
|
qWarning() << "Manual PEBI input 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();
|
|
|
|
|
|
// HX_NWTM_GRID_INPUT构造函数带有演示井,分批追加真实井前必须清空,
|
|
|
// 避免求解前建网混入额外井,并保证网格井槽位与求解井顺序一致。
|
|
|
oSnapshot.m_oGridInput.VerticalWell.clear();
|
|
|
oSnapshot.m_oGridInput.FractureVerticalWell.clear();
|
|
|
oSnapshot.m_oGridInput.MultistageFracturedHorizontalWell.clear();
|
|
|
oSnapshot.m_oGridInput.HorizontalWell.clear();
|
|
|
oSnapshot.m_oGridInput.InclinedWell.clear();
|
|
|
|
|
|
oSnapshot.m_oGridInput.GridControl =
|
|
|
pDataManager->getPebiGridControl();
|
|
|
|
|
|
// 边界只捕获一次;后续井批次只追加值,不重复访问已经完成的对象。
|
|
|
return meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
|
|
|
nmDataWellBase* pWellData,
|
|
|
int nWellMode,
|
|
|
nmPebiGridInputSnapshot& oSnapshot)
|
|
|
{
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
|
|
|
oSnapshot.m_bCaptured) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmSolverWellRef oWellRef;
|
|
|
nmDataHorizontalFracturedWell* pHorizontalFracturedWell =
|
|
|
dynamic_cast<nmDataHorizontalFracturedWell*>(pWellData);
|
|
|
nmDataVerticalFracturedWell* pVerticalFracturedWell =
|
|
|
dynamic_cast<nmDataVerticalFracturedWell*>(pWellData);
|
|
|
nmDataVerticalWell* pVerticalWell =
|
|
|
dynamic_cast<nmDataVerticalWell*>(pWellData);
|
|
|
|
|
|
if(pHorizontalFracturedWell != nullptr) {
|
|
|
const QVector<QPair<QPointF, QPointF> > vecFracPoints =
|
|
|
pHorizontalFracturedWell->getFracs();
|
|
|
std::vector<std::vector<double> > vecFractures;
|
|
|
vecFractures.reserve(vecFracPoints.size());
|
|
|
for(int nIndex = 0; nIndex < vecFracPoints.size(); ++nIndex) {
|
|
|
dVec1 oCrack(6);
|
|
|
oCrack[0] = vecFracPoints[nIndex].first.x();
|
|
|
oCrack[1] = vecFracPoints[nIndex].first.y();
|
|
|
oCrack[2] = vecFracPoints[nIndex].second.x();
|
|
|
oCrack[3] = vecFracPoints[nIndex].second.y();
|
|
|
oCrack[4] = pHorizontalFracturedWell->getWidth()
|
|
|
.getValue().toDouble();
|
|
|
oCrack[5] = pHorizontalFracturedWell->getDfc()
|
|
|
.getValue().toDouble();
|
|
|
vecFractures.push_back(oCrack);
|
|
|
}
|
|
|
oSnapshot.m_oGridInput.MultistageFracturedHorizontalWell
|
|
|
.push_back(vecFractures);
|
|
|
oWellRef = nmSolverWellRef(
|
|
|
-1,
|
|
|
NM_WELL_MODEL::Horizontal_Fractured_Well,
|
|
|
pWellData->getWellCode());
|
|
|
} else if(pVerticalFracturedWell != nullptr) {
|
|
|
const QVector<QPointF> vecFracPoints =
|
|
|
pVerticalFracturedWell->getFracs();
|
|
|
if(vecFracPoints.size() != 2) {
|
|
|
return false;
|
|
|
}
|
|
|
dVec1 oCrack(6);
|
|
|
oCrack[0] = vecFracPoints[0].x();
|
|
|
oCrack[1] = vecFracPoints[0].y();
|
|
|
oCrack[2] = vecFracPoints[1].x();
|
|
|
oCrack[3] = vecFracPoints[1].y();
|
|
|
oCrack[4] = pVerticalFracturedWell->getWidth()
|
|
|
.getValue().toDouble();
|
|
|
oCrack[5] = pVerticalFracturedWell->getDfc()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oGridInput.FractureVerticalWell.push_back(oCrack);
|
|
|
oWellRef = nmSolverWellRef(
|
|
|
-1,
|
|
|
NM_WELL_MODEL::Vertical_Fractured_Well,
|
|
|
pWellData->getWellCode());
|
|
|
} else if(pVerticalWell != nullptr) {
|
|
|
dVec1 oWell(3);
|
|
|
oWell[0] = pVerticalWell->getX().getValue().toDouble();
|
|
|
oWell[1] = pVerticalWell->getY().getValue().toDouble();
|
|
|
oWell[2] = pVerticalWell->getRadius().getValue().toDouble();
|
|
|
oSnapshot.m_oGridInput.VerticalWell.push_back(oWell);
|
|
|
oWellRef = nmSolverWellRef(
|
|
|
-1,
|
|
|
NM_WELL_MODEL::Vertical_Well,
|
|
|
pWellData->getWellCode());
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmPebiWellInputSnapshot oWellInput;
|
|
|
oWellInput.m_bRealWell = true;
|
|
|
oWellInput.m_sWellCode = pWellData->getWellCode();
|
|
|
oWellInput.m_sWellName = pWellData->getWellName();
|
|
|
oWellInput.m_eWellCategory = pWellData->getWellCategory();
|
|
|
oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
|
|
|
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
|
|
|
oWellInput.m_oLocation = QPointF(
|
|
|
pWellData->getX().getValue().toDouble(),
|
|
|
pWellData->getY().getValue().toDouble());
|
|
|
oWellInput.m_dWellboreStorage =
|
|
|
pWellData->getWellboreStorage().getValue().toDouble();
|
|
|
oWellInput.m_dSkin = pWellData->getPerforationCount() > 0
|
|
|
? pWellData->getPerforation(0)->getSkin().getValue().toDouble()
|
|
|
: 0.0;
|
|
|
oWellInput.m_bRateControlled =
|
|
|
nWellMode == static_cast<int>(NM_CaseWell_RateControlled);
|
|
|
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
|
|
|
(oWellInput.m_bRateControlled &&
|
|
|
(oWellInput.m_vecFlowPoints.isEmpty() ||
|
|
|
!pWellData->hasValidFlowSectionIndex()))) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_vecSolverWellOrder.append(oWellRef);
|
|
|
oSnapshot.m_vecWellInputs.append(oWellInput);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::finishManualInputSnapshot(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
const QSet<QString>& setEffectiveWellCodes,
|
|
|
nmPebiGridInputSnapshot& oSnapshot)
|
|
|
{
|
|
|
if(pDataManager == nullptr || setEffectiveWellCodes.isEmpty() ||
|
|
|
QThread::currentThread() != pDataManager->thread() ||
|
|
|
oSnapshot.m_vecSolverWellOrder.size() !=
|
|
|
oSnapshot.m_vecWellInputs.size()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const nmDataNumericalAnalysisCase* pAnalysisCase =
|
|
|
pDataManager->getNumericalAnalysisCase();
|
|
|
if(pAnalysisCase == nullptr ||
|
|
|
pAnalysisCase->getGridInputRevision() !=
|
|
|
oSnapshot.m_nGridInputRevision) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QHash<QString, nmPebiWellInputSnapshot> mapWellInputs;
|
|
|
for(int nIndex = 0; nIndex < oSnapshot.m_vecWellInputs.size(); ++nIndex) {
|
|
|
const nmPebiWellInputSnapshot& oWellInput =
|
|
|
oSnapshot.m_vecWellInputs[nIndex];
|
|
|
if(oWellInput.m_sWellCode.isEmpty() ||
|
|
|
mapWellInputs.contains(oWellInput.m_sWellCode)) {
|
|
|
return false;
|
|
|
}
|
|
|
mapWellInputs.insert(oWellInput.m_sWellCode, oWellInput);
|
|
|
}
|
|
|
|
|
|
if(!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) ||
|
|
|
!meshGenPebiCrack(pDataManager,
|
|
|
oSnapshot.m_oGridInput,
|
|
|
oSnapshot.m_vecSolverWellOrder)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<nmPebiWellInputSnapshot> vecOrderedWellInputs;
|
|
|
vecOrderedWellInputs.reserve(oSnapshot.m_vecSolverWellOrder.size());
|
|
|
QSet<QString> setOrderedWellCodes;
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < oSnapshot.m_vecSolverWellOrder.size();
|
|
|
++nIndex) {
|
|
|
nmSolverWellRef& oWellRef = oSnapshot.m_vecSolverWellOrder[nIndex];
|
|
|
oWellRef.m_nSolverIndex = nIndex;
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
vecOrderedWellInputs.append(nmPebiWellInputSnapshot());
|
|
|
continue;
|
|
|
}
|
|
|
if(oWellRef.m_eEntryKind != NM_SolverEntry_Well ||
|
|
|
!setEffectiveWellCodes.contains(oWellRef.m_sWellCode) ||
|
|
|
setOrderedWellCodes.contains(oWellRef.m_sWellCode) ||
|
|
|
!mapWellInputs.contains(oWellRef.m_sWellCode)) {
|
|
|
return false;
|
|
|
}
|
|
|
setOrderedWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
vecOrderedWellInputs.append(mapWellInputs.value(oWellRef.m_sWellCode));
|
|
|
}
|
|
|
if(setOrderedWellCodes != setEffectiveWellCodes) {
|
|
|
return false;
|
|
|
}
|
|
|
oSnapshot.m_vecWellInputs = vecOrderedWellInputs;
|
|
|
|
|
|
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
|
|
|
oSnapshot.m_oScene = oEmptyScene;
|
|
|
const NM_SOLVER_MODEL_TYPE eSolverModelType =
|
|
|
pDataManager->getSolverModelType();
|
|
|
oSnapshot.m_oScene.solverType =
|
|
|
static_cast<int>(eSolverModelType);
|
|
|
|
|
|
nmDataReservoir* pReservoirData =
|
|
|
pDataManager->getReservoirData();
|
|
|
nmDataPvtParaForPebi* pPvtData =
|
|
|
pDataManager->getPebiPvtPara();
|
|
|
fillScenePvtByModel(oSnapshot.m_oScene,
|
|
|
eSolverModelType,
|
|
|
pPvtData,
|
|
|
pReservoirData);
|
|
|
fillScenePseudoPressureTable(oSnapshot.m_oScene,
|
|
|
eSolverModelType,
|
|
|
pDataManager);
|
|
|
|
|
|
if(pReservoirData != nullptr) {
|
|
|
oSnapshot.m_oScene.Base.Pi = pReservoirData->getInitialPressure()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Cti = pReservoirData->getCt()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Cf = pReservoirData->getCf()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Soi = pReservoirData->getSoi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Sgi = pReservoirData->getSgi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Swi = pReservoirData->getSwi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.k_ref =
|
|
|
nmCalculationUtils::milliDarcyToDarcy(
|
|
|
pReservoirData->getPermeability()
|
|
|
.getValue().toDouble());
|
|
|
oSnapshot.m_oScene.Base.phi_ref = pReservoirData->getPorosity()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.h_ref = pReservoirData->getThickness()
|
|
|
.getValue().toDouble();
|
|
|
}
|
|
|
|
|
|
nmDataTimeStepSetting* pTimeStepSetting =
|
|
|
pDataManager->getTimeStep();
|
|
|
if(pTimeStepSetting != nullptr) {
|
|
|
oSnapshot.m_oScene.Base.d =
|
|
|
pTimeStepSetting->getTimeGrowthExponent()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.dt_Min =
|
|
|
pTimeStepSetting->getMinDeltaTAttribute()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.dt_Max =
|
|
|
pTimeStepSetting->getMaxDeltaTAttribute()
|
|
|
.getValue().toDouble();
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
|
|
|
oSnapshot.m_bCaptured = true;
|
|
|
oSnapshot.m_bValid = false;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::captureInputSnapshot(
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
nmPebiGridInputSnapshot& oSnapshot,
|
|
|
bool bDeferPreparation)
|
|
|
{
|
|
|
// 使用具名常量触发复制赋值,兼容 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();
|
|
|
|
|
|
// 第二步:有效井集合只计算一次,后续几何、角色和结果井快照共同复用。
|
|
|
const QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
pDataManager->getEffectiveCalculationWells();
|
|
|
QSet<QString> setEffectiveWellCodes;
|
|
|
QHash<QString, int> mapWellModes;
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
setEffectiveWellCodes.insert(vecEffectiveWells[nIndex].m_sWellCode);
|
|
|
mapWellModes.insert(vecEffectiveWells[nIndex].m_sWellCode,
|
|
|
static_cast<int>(vecEffectiveWells[nIndex].m_eMode));
|
|
|
}
|
|
|
|
|
|
// 第三步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。
|
|
|
if(!meshGenPebiBoundary(pDataManager, oSnapshot.m_oGridInput) ||
|
|
|
!meshGenPebiWells(pDataManager,
|
|
|
oSnapshot.m_oGridInput,
|
|
|
oSnapshot.m_vecSolverWellOrder,
|
|
|
setEffectiveWellCodes) ||
|
|
|
!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> 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;
|
|
|
}
|
|
|
|
|
|
// 第五步:建立 WellCode 索引并一次复制每口井,避免五十口井时反复线性查找。
|
|
|
QHash<QString, nmDataWellBase*> mapWellsByCode;
|
|
|
const QVector<nmDataWellBase*> vecAllWells =
|
|
|
pDataManager->getWellDataList();
|
|
|
for(int nIndex = 0; nIndex < vecAllWells.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = vecAllWells[nIndex];
|
|
|
if(pWellData != nullptr && !pWellData->getWellCode().isEmpty()) {
|
|
|
mapWellsByCode.insert(pWellData->getWellCode(), pWellData);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_vecWellInputs.clear();
|
|
|
oSnapshot.m_vecWellInputs.reserve(
|
|
|
oSnapshot.m_vecSolverWellOrder.size());
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < oSnapshot.m_vecSolverWellOrder.size();
|
|
|
++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef =
|
|
|
oSnapshot.m_vecSolverWellOrder[nIndex];
|
|
|
nmPebiWellInputSnapshot oWellInput;
|
|
|
oWellInput.m_sWellCode = oWellRef.m_sWellCode;
|
|
|
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
oSnapshot.m_vecWellInputs.append(oWellInput);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
mapWellsByCode.value(oWellRef.m_sWellCode, nullptr);
|
|
|
if(pWellData == nullptr || !mapWellModes.contains(oWellRef.m_sWellCode)) {
|
|
|
qWarning() << "Solver well code is missing from Map:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
oWellInput.m_bRealWell = true;
|
|
|
oWellInput.m_sWellName = pWellData->getWellName();
|
|
|
oWellInput.m_eWellCategory = pWellData->getWellCategory();
|
|
|
oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
|
|
|
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
|
|
|
oWellInput.m_oLocation = QPointF(
|
|
|
pWellData->getX().getValue().toDouble(),
|
|
|
pWellData->getY().getValue().toDouble());
|
|
|
oWellInput.m_dWellboreStorage =
|
|
|
pWellData->getWellboreStorage().getValue().toDouble();
|
|
|
oWellInput.m_dSkin = pWellData->getPerforationCount() > 0
|
|
|
? pWellData->getPerforation(0)->getSkin()
|
|
|
.getValue().toDouble()
|
|
|
: 0.0;
|
|
|
oWellInput.m_bRateControlled =
|
|
|
mapWellModes.value(oWellRef.m_sWellCode) ==
|
|
|
static_cast<int>(NM_CaseWell_RateControlled);
|
|
|
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory)) {
|
|
|
qWarning() << "Solver well has an invalid category:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
if(oWellInput.m_bRateControlled &&
|
|
|
(oWellInput.m_vecFlowPoints.isEmpty() ||
|
|
|
!pWellData->hasValidFlowSectionIndex())) {
|
|
|
qWarning() << "Rate-controlled well has no valid rate schedule:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
oSnapshot.m_vecWellInputs.append(oWellInput);
|
|
|
}
|
|
|
|
|
|
// 第六步:PVT、储层和时间步仍在主线程复制为值;较重的井制度和场景数组
|
|
|
// 组装可由手工求解任务延后到后台执行。
|
|
|
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
|
|
|
oSnapshot.m_oScene = oEmptyScene;
|
|
|
const NM_SOLVER_MODEL_TYPE eSolverModelType =
|
|
|
pDataManager->getSolverModelType();
|
|
|
oSnapshot.m_oScene.solverType =
|
|
|
static_cast<int>(eSolverModelType);
|
|
|
|
|
|
nmDataReservoir* pReservoirData =
|
|
|
pDataManager->getReservoirData();
|
|
|
nmDataPvtParaForPebi* pPvtData =
|
|
|
pDataManager->getPebiPvtPara();
|
|
|
fillScenePvtByModel(oSnapshot.m_oScene,
|
|
|
eSolverModelType,
|
|
|
pPvtData,
|
|
|
pReservoirData);
|
|
|
fillScenePseudoPressureTable(oSnapshot.m_oScene,
|
|
|
eSolverModelType,
|
|
|
pDataManager);
|
|
|
|
|
|
if(pReservoirData != nullptr) {
|
|
|
oSnapshot.m_oScene.Base.Pi = pReservoirData->getInitialPressure()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Cti = pReservoirData->getCt()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Cf = pReservoirData->getCf()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Soi = pReservoirData->getSoi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Sgi = pReservoirData->getSgi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.Swi = pReservoirData->getSwi()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.k_ref =
|
|
|
nmCalculationUtils::milliDarcyToDarcy(
|
|
|
pReservoirData->getPermeability()
|
|
|
.getValue().toDouble());
|
|
|
oSnapshot.m_oScene.Base.phi_ref = pReservoirData->getPorosity()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.h_ref = pReservoirData->getThickness()
|
|
|
.getValue().toDouble();
|
|
|
}
|
|
|
|
|
|
nmDataTimeStepSetting* pTimeStepSetting =
|
|
|
pDataManager->getTimeStep();
|
|
|
if(pTimeStepSetting != nullptr) {
|
|
|
oSnapshot.m_oScene.Base.d =
|
|
|
pTimeStepSetting->getTimeGrowthExponent()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.dt_Min =
|
|
|
pTimeStepSetting->getMinDeltaTAttribute()
|
|
|
.getValue().toDouble();
|
|
|
oSnapshot.m_oScene.Base.dt_Max =
|
|
|
pTimeStepSetting->getMaxDeltaTAttribute()
|
|
|
.getValue().toDouble();
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
|
|
|
oSnapshot.m_bCaptured = true;
|
|
|
|
|
|
return bDeferPreparation
|
|
|
? true
|
|
|
: prepareInputSnapshot(oSnapshot);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::prepareInputSnapshot(
|
|
|
nmPebiGridInputSnapshot& oSnapshot,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
oSnapshot.m_bValid = false;
|
|
|
if(!oSnapshot.m_bCaptured ||
|
|
|
oSnapshot.m_vecWellInputs.size() !=
|
|
|
oSnapshot.m_vecSolverWellOrder.size() ||
|
|
|
isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return buildPebiScene(oSnapshot, pCancelRequested);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
nmPebiGridInputSnapshot& oSnapshot,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
const HX_NWTM_GRID_INPUT& oGridInput = oSnapshot.m_oGridInput;
|
|
|
const QVector<nmSolverWellRef>& vecSolverWellOrder =
|
|
|
oSnapshot.m_vecSolverWellOrder;
|
|
|
const QVector<nmPebiWellInputSnapshot>& vecWellInputs =
|
|
|
oSnapshot.m_vecWellInputs;
|
|
|
nmDataBinaryTools::NM_PEBI_SCENE& oScene = oSnapshot.m_oScene;
|
|
|
|
|
|
// 第一步:复制网格基础数据。PVT、储层和时间步已在主线程值化。
|
|
|
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) {
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
const nmSolverWellRef& oWellRef = vecSolverWellOrder[nIndex];
|
|
|
oScene.wellType.push_back(static_cast<int>(oWellRef.m_eWellType));
|
|
|
oScene.wellName.push_back(vecWellInputs[nIndex].m_sWellName);
|
|
|
}
|
|
|
|
|
|
// 第二步:按每口井冻结的井别填充产量制度;求解模型只负责相容性校验。
|
|
|
const NM_SOLVER_MODEL_TYPE eSolverModelType =
|
|
|
static_cast<NM_SOLVER_MODEL_TYPE>(oScene.solverType);
|
|
|
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];
|
|
|
const nmPebiWellInputSnapshot& oWellInput =
|
|
|
vecWellInputs[nWellIndex];
|
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
|
|
|
!oWellInput.m_bRateControlled) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
|
|
|
!nmSolverModelSupportsWellCategory(
|
|
|
eSolverModelType, oWellInput.m_eWellCategory) ||
|
|
|
oWellInput.m_vecFlowPoints.isEmpty() ||
|
|
|
oWellInput.m_nFlowSectionIndex < 1 ||
|
|
|
oWellInput.m_nFlowSectionIndex >
|
|
|
oWellInput.m_vecFlowPoints.size()) {
|
|
|
qWarning() << "Rate-controlled well input is incompatible with solver model:"
|
|
|
<< oWellInput.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
std::vector<double> vecTime;
|
|
|
std::vector<double> vecRate;
|
|
|
vecTime.reserve(oWellInput.m_vecFlowPoints.size());
|
|
|
vecRate.reserve(oWellInput.m_vecFlowPoints.size());
|
|
|
for(int nPointIndex = 0;
|
|
|
nPointIndex < oWellInput.m_vecFlowPoints.size();
|
|
|
++nPointIndex) {
|
|
|
if((nPointIndex % 256) == 0 &&
|
|
|
isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
vecTime.push_back(oWellInput.m_vecFlowPoints[nPointIndex].x());
|
|
|
vecRate.push_back(oWellInput.m_vecFlowPoints[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(oWellInput.m_eWellCategory == NM_WellCategory_Gas) {
|
|
|
oScene.Rate.qg[nWellIndex] = vecRate;
|
|
|
} else if(oWellInput.m_eWellCategory == NM_WellCategory_Water) {
|
|
|
oScene.Rate.qw[nWellIndex] = vecRate;
|
|
|
} else {
|
|
|
oScene.Rate.qo[nWellIndex] = vecRate;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第三步:复制井筒储集、表皮和流量段索引。
|
|
|
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];
|
|
|
const nmPebiWellInputSnapshot& oWellInput =
|
|
|
vecWellInputs[nWellIndex];
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
oScene.CS.C[nWellIndex] = 0.0;
|
|
|
oScene.CS.S[nWellIndex] = 0.0;
|
|
|
oScene.wellFlowSectionIndex[nWellIndex] = 1;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
oScene.CS.C[nWellIndex] = oWellInput.m_dWellboreStorage;
|
|
|
oScene.CS.S[nWellIndex] = oWellInput.m_dSkin;
|
|
|
// 观察井没有流量段时该字段不参与物理计算,仍写入合法的一基占位值。
|
|
|
oScene.wellFlowSectionIndex[nWellIndex] =
|
|
|
oWellInput.m_nFlowSectionIndex >= 1
|
|
|
? oWellInput.m_nFlowSectionIndex : 1;
|
|
|
}
|
|
|
|
|
|
oSnapshot.m_bValid = !isCancellationRequested(pCancelRequested);
|
|
|
return oSnapshot.m_bValid;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationPebiGrid::calculateSnapshot(
|
|
|
const nmPebiGridInputSnapshot& oSnapshot,
|
|
|
nmPebiGridResult& oResult,
|
|
|
bool bCreateUnstructuredGrid,
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
{
|
|
|
nmInterruptibleMutexLocker oGridLocker;
|
|
|
if(!oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const nmPebiGridResult oEmptyResult;
|
|
|
oResult = oEmptyResult;
|
|
|
if(!oSnapshot.m_bValid || isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 建网、模型求解和 Kriging 共用 DLL 全局状态,加载至读取结果期间必须串行。
|
|
|
nmInterruptibleMutexLocker oDllLocker;
|
|
|
if(!oDllLocker.lock(nmCalculationUtils::getHxNwtmDllMutex(),
|
|
|
pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
HMODULE hGridModule = LoadLibrary(L"HX_NWTM.dll");
|
|
|
if(hGridModule == nullptr) {
|
|
|
qWarning() << "Failed to load HX_NWTM.dll. Error code:"
|
|
|
<< GetLastError();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
FreeLibrary(hGridModule);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。
|
|
|
if(!invokePebiGridDllGuarded(pfnGenerateGrid, oSnapshot, oResult)) {
|
|
|
// 本轮结果不再用于显示或求解,沿用现有失败通知保留旧成果。
|
|
|
nmCalculationUtils::cleanupPebiGridDebugFiles();
|
|
|
FreeLibrary(hGridModule);
|
|
|
hGridModule = nullptr;
|
|
|
return false;
|
|
|
}
|
|
|
nmCalculationUtils::cleanupPebiGridDebugFiles();
|
|
|
oResult.m_nPebiCount = pfnGetPebiCount();
|
|
|
|
|
|
// 后续 VTK 构造只读取本次局部输出,无需继续占用进程级 DLL 锁。
|
|
|
FreeLibrary(hGridModule);
|
|
|
hGridModule = nullptr;
|
|
|
oDllLocker.unlock();
|
|
|
|
|
|
// DLL 本身没有取消入口;若执行期间收到停止请求,返回后立即丢弃局部输出。
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 自动拟合只需要 DLL 数组,可跳过 VTK 构造;网格任务必须生成完整 VTK。
|
|
|
if(bCreateUnstructuredGrid) {
|
|
|
oResult.m_pUnstructuredGrid =
|
|
|
createPebiUnstructuredGrid(
|
|
|
oResult.m_oGridOutput1,
|
|
|
pCancelRequested);
|
|
|
if(oResult.m_pUnstructuredGrid == nullptr ||
|
|
|
oResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
oResult.m_bSucceeded = !isCancellationRequested(pCancelRequested);
|
|
|
} catch(const std::exception& e) {
|
|
|
nmCalculationUtils::cleanupPebiGridDebugFiles();
|
|
|
zxLogInstance::getInstance()->writeLogF(
|
|
|
QString("C++ Exception: %1").arg(e.what()));
|
|
|
logInputParameters(oSnapshot.m_oGridInput);
|
|
|
if(hGridModule != nullptr) {
|
|
|
FreeLibrary(hGridModule);
|
|
|
}
|
|
|
return false;
|
|
|
} catch(...) {
|
|
|
nmCalculationUtils::cleanupPebiGridDebugFiles();
|
|
|
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;
|
|
|
}
|