|
|
|
|
@ -8,6 +8,7 @@
|
|
|
|
|
#include <QThread>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
#include <QHash>
|
|
|
|
|
|
|
|
|
|
#include "nmCalculationUtils.h"
|
|
|
|
|
#include "zxLogInstance.h"
|
|
|
|
|
@ -41,6 +42,72 @@ const int CONST_PVT_POINT_COUNT = 200;
|
|
|
|
|
// 保留递归锁以兼容可能在持锁网格入口中调用缓存查询的旧代码路径。
|
|
|
|
|
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);
|
|
|
|
|
@ -242,41 +309,56 @@ void nmCalculationPebiGrid::clearGridData(
|
|
|
|
|
|
|
|
|
|
bool nmCalculationPebiGrid::copyCurrentGridFor(
|
|
|
|
|
const nmDataAnalyzeManager* pDataManager,
|
|
|
|
|
quint64 nGridInputRevision,
|
|
|
|
|
HX_NWTM_GRID_OUTPUT1& oGridOutput1,
|
|
|
|
|
HX_NWTM_GRID_OUTPUT2& oGridOutput2,
|
|
|
|
|
int& nPebiCount) const
|
|
|
|
|
int& nPebiCount,
|
|
|
|
|
const QAtomicInt* pCancelRequested) const
|
|
|
|
|
{
|
|
|
|
|
// 求解任务在主线程构造,不能为了复制缓存等待正在运行的网格 DLL。
|
|
|
|
|
// 锁忙时返回 false,任务会在后台基于自己的值快照生成局部网格。
|
|
|
|
|
if(!s_oPebiGridMutex.tryLock()) {
|
|
|
|
|
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 ||
|
|
|
|
|
!pDataManager->isPebiGridValid() ||
|
|
|
|
|
pDataManager->getNumericalAnalysisCase() == nullptr ||
|
|
|
|
|
m_nCachedGridInputRevision !=
|
|
|
|
|
pDataManager->getNumericalAnalysisCase()->getGridInputRevision() ||
|
|
|
|
|
m_nCachedGridInputRevision != nGridInputRevision ||
|
|
|
|
|
p1.PEBI_cell.p.empty()) {
|
|
|
|
|
s_oPebiGridMutex.unlock();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二步:一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
|
|
|
|
|
try {
|
|
|
|
|
oGridOutput1 = p1;
|
|
|
|
|
oGridOutput2 = p2;
|
|
|
|
|
nPebiCount = m_nPebiCount;
|
|
|
|
|
} catch(...) {
|
|
|
|
|
s_oPebiGridMutex.unlock();
|
|
|
|
|
throw;
|
|
|
|
|
// 一次性复制两份 DLL 输出,保证求解期间使用同一版本的网格快照。
|
|
|
|
|
oGridOutput1 = p1;
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
s_oPebiGridMutex.unlock();
|
|
|
|
|
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";
|
|
|
|
|
@ -415,7 +497,8 @@ bool nmCalculationPebiGrid::meshGenPebiBoundary(
|
|
|
|
|
bool nmCalculationPebiGrid::meshGenPebiWells(
|
|
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
|
|
HX_NWTM_GRID_INPUT& inputObj,
|
|
|
|
|
QVector<nmSolverWellRef>& vecSolverWellOrder)
|
|
|
|
|
QVector<nmSolverWellRef>& vecSolverWellOrder,
|
|
|
|
|
const QSet<QString>& setEffectiveWellCodes)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// 从数据中心获取井数据
|
|
|
|
|
@ -440,13 +523,6 @@ bool nmCalculationPebiGrid::meshGenPebiWells(
|
|
|
|
|
// 第一步:求解器顺序只写入局部快照,后台成功前不修改分析方案。
|
|
|
|
|
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();
|
|
|
|
|
// 获取垂直裂缝井数据
|
|
|
|
|
@ -816,7 +892,9 @@ int nmCalculationPebiGrid::getPebiCount() const
|
|
|
|
|
return m_nPebiCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructuredGrid(const HX_NWTM_GRID_OUTPUT1& P1)
|
|
|
|
|
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();
|
|
|
|
|
@ -827,6 +905,9 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
|
|
|
|
|
// 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;
|
|
|
|
|
@ -874,6 +955,9 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
@ -925,9 +1009,265 @@ vtkSmartPointer<vtkUnstructuredGrid> nmCalculationPebiGrid::createPebiUnstructur
|
|
|
|
|
return pUnstructuredGrid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationPebiGrid::captureInputSnapshot(
|
|
|
|
|
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();
|
|
|
|
|
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_vecFlowPoints = pWellData->getFlowPoints();
|
|
|
|
|
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(oWellInput.m_bRateControlled &&
|
|
|
|
|
oWellInput.m_vecFlowPoints.size() < 2) {
|
|
|
|
|
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;
|
|
|
|
|
@ -956,11 +1296,23 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
|
|
|
|
|
oSnapshot.m_oGridInput.GridControl =
|
|
|
|
|
pDataManager->getPebiGridControl();
|
|
|
|
|
|
|
|
|
|
// 第二步:把 Map 中的边界、有效井、断层和手工裂缝全部转成 DLL 值类型。
|
|
|
|
|
// 第二步:有效井集合只计算一次,后续几何、角色和结果井快照共同复用。
|
|
|
|
|
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) ||
|
|
|
|
|
oSnapshot.m_vecSolverWellOrder,
|
|
|
|
|
setEffectiveWellCodes) ||
|
|
|
|
|
!meshGenPebiFault(pDataManager, oSnapshot.m_oGridInput) ||
|
|
|
|
|
!meshGenPebiCrack(pDataManager,
|
|
|
|
|
oSnapshot.m_oGridInput,
|
|
|
|
|
@ -974,14 +1326,7 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
|
|
|
|
|
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;
|
|
|
|
|
@ -1009,32 +1354,159 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第四步:场景和授权路径同样在捕获阶段读取,后台不再接触任何井对象。
|
|
|
|
|
if(!buildPebiScene(pDataManager,
|
|
|
|
|
oSnapshot.m_oGridInput,
|
|
|
|
|
oSnapshot.m_vecSolverWellOrder,
|
|
|
|
|
oSnapshot.m_oScene)) {
|
|
|
|
|
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_vecFlowPoints = pWellData->getFlowPoints();
|
|
|
|
|
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(oWellInput.m_bRateControlled &&
|
|
|
|
|
oWellInput.m_vecFlowPoints.size() < 2) {
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
oSnapshot.m_bValid = true;
|
|
|
|
|
return true;
|
|
|
|
|
return bDeferPreparation
|
|
|
|
|
? true
|
|
|
|
|
: prepareInputSnapshot(oSnapshot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
nmDataAnalyzeManager* pDataManager,
|
|
|
|
|
const HX_NWTM_GRID_INPUT& oGridInput,
|
|
|
|
|
const QVector<nmSolverWellRef>& vecSolverWellOrder,
|
|
|
|
|
nmDataBinaryTools::NM_PEBI_SCENE& oScene)
|
|
|
|
|
bool nmCalculationPebiGrid::prepareInputSnapshot(
|
|
|
|
|
nmPebiGridInputSnapshot& oSnapshot,
|
|
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
|
|
{
|
|
|
|
|
if(pDataManager == nullptr) {
|
|
|
|
|
oSnapshot.m_bValid = false;
|
|
|
|
|
if(!oSnapshot.m_bCaptured ||
|
|
|
|
|
oSnapshot.m_vecWellInputs.size() !=
|
|
|
|
|
oSnapshot.m_vecSolverWellOrder.size() ||
|
|
|
|
|
isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第一步:复制网格基础数据和井显示信息。
|
|
|
|
|
const nmDataBinaryTools::NM_PEBI_SCENE oEmptyScene;
|
|
|
|
|
oScene = oEmptyScene;
|
|
|
|
|
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;
|
|
|
|
|
@ -1052,20 +1524,17 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
|
|
pDataManager->findWellByCode(oWellRef.m_sWellCode);
|
|
|
|
|
oScene.wellName.push_back(pWellData != nullptr
|
|
|
|
|
? pWellData->getWellName()
|
|
|
|
|
: QString());
|
|
|
|
|
oScene.wellName.push_back(vecWellInputs[nIndex].m_sWellName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二步:按求解器模型填充每个槽位的产量制度。
|
|
|
|
|
const NM_SOLVER_MODEL_TYPE eSolverModelType =
|
|
|
|
|
pDataManager->getSolverModelType();
|
|
|
|
|
oScene.solverType = static_cast<int>(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());
|
|
|
|
|
@ -1076,21 +1545,20 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
++nWellIndex) {
|
|
|
|
|
const nmSolverWellRef& oWellRef =
|
|
|
|
|
vecSolverWellOrder[nWellIndex];
|
|
|
|
|
const nmPebiWellInputSnapshot& oWellInput =
|
|
|
|
|
vecWellInputs[nWellIndex];
|
|
|
|
|
|
|
|
|
|
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
|
|
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
|
|
|
|
|
pDataManager->getCalculationWellMode(
|
|
|
|
|
oWellRef.m_sWellCode) == NM_CaseWell_Observation) {
|
|
|
|
|
continue;
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
|
|
pDataManager->findWellByCode(oWellRef.m_sWellCode);
|
|
|
|
|
if(pWellData == nullptr) {
|
|
|
|
|
// 手工裂缝和观察井保留外层槽位,但不提供源汇项。
|
|
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture ||
|
|
|
|
|
!oWellInput.m_bRateControlled) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
|
|
|
|
|
QVector<QPointF> vecTimeQ = oWellInput.m_vecFlowPoints;
|
|
|
|
|
if(!vecTimeQ.isEmpty() &&
|
|
|
|
|
vecTimeQ[0].x() == 0.0 &&
|
|
|
|
|
vecTimeQ[0].y() == 0.0) {
|
|
|
|
|
@ -1104,6 +1572,10 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
for(int nPointIndex = 0;
|
|
|
|
|
nPointIndex < vecTimeQ.size();
|
|
|
|
|
++nPointIndex) {
|
|
|
|
|
if((nPointIndex % 256) == 0 &&
|
|
|
|
|
isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
vecTime.push_back(vecTimeQ[nPointIndex].x());
|
|
|
|
|
vecRate.push_back(vecTimeQ[nPointIndex].y());
|
|
|
|
|
}
|
|
|
|
|
@ -1129,6 +1601,9 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
for(size_t nWellIndex = 0;
|
|
|
|
|
nWellIndex < oScene.Rate.t.size();
|
|
|
|
|
++nWellIndex) {
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
oScene.Rate.qg[nWellIndex].assign(
|
|
|
|
|
oScene.Rate.t[nWellIndex].size(), 0.0);
|
|
|
|
|
oScene.Rate.qw[nWellIndex].assign(
|
|
|
|
|
@ -1146,6 +1621,11 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
++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;
|
|
|
|
|
@ -1153,89 +1633,39 @@ bool nmCalculationPebiGrid::buildPebiScene(
|
|
|
|
|
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.CS.C[nWellIndex] = oWellInput.m_dWellboreStorage;
|
|
|
|
|
oScene.CS.S[nWellIndex] = oWellInput.m_dSkin;
|
|
|
|
|
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();
|
|
|
|
|
oWellInput.m_nFlowSectionIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
oSnapshot.m_bValid = !isCancellationRequested(pCancelRequested);
|
|
|
|
|
return oSnapshot.m_bValid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool nmCalculationPebiGrid::calculateSnapshot(
|
|
|
|
|
const nmPebiGridInputSnapshot& oSnapshot,
|
|
|
|
|
nmPebiGridResult& oResult,
|
|
|
|
|
bool bCreateUnstructuredGrid)
|
|
|
|
|
bool bCreateUnstructuredGrid,
|
|
|
|
|
const QAtomicInt* pCancelRequested)
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker oLocker(&s_oPebiGridMutex);
|
|
|
|
|
nmInterruptibleMutexLocker oGridLocker;
|
|
|
|
|
if(!oGridLocker.lock(&s_oPebiGridMutex, pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nmPebiGridResult oEmptyResult;
|
|
|
|
|
oResult = oEmptyResult;
|
|
|
|
|
if(!oSnapshot.m_bValid) {
|
|
|
|
|
if(!oSnapshot.m_bValid || isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 建网、模型求解和 Kriging 共用 DLL 全局状态,加载至读取结果期间必须串行。
|
|
|
|
|
QMutexLocker oDllLocker(
|
|
|
|
|
nmCalculationUtils::getHxNwtmDllMutex());
|
|
|
|
|
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:"
|
|
|
|
|
@ -1289,6 +1719,11 @@ bool nmCalculationPebiGrid::calculateSnapshot(
|
|
|
|
|
zxLogInstance::getInstance()->writeLogF(
|
|
|
|
|
"scene exported: " + sScenePath);
|
|
|
|
|
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
FreeLibrary(hGridModule);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 第二步:DLL 输出先落在局部结果,失败时不清空单例缓存和已有成果。
|
|
|
|
|
pfnGenerateGrid(oResult.m_oGridOutput1,
|
|
|
|
|
oResult.m_oGridOutput2,
|
|
|
|
|
@ -1301,18 +1736,24 @@ bool nmCalculationPebiGrid::calculateSnapshot(
|
|
|
|
|
hGridModule = nullptr;
|
|
|
|
|
oDllLocker.unlock();
|
|
|
|
|
|
|
|
|
|
// DLL 本身没有取消入口;若执行期间收到停止请求,返回后立即丢弃局部输出。
|
|
|
|
|
if(isCancellationRequested(pCancelRequested)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自动拟合只需要 DLL 数组,可跳过 VTK 构造;网格任务必须生成完整 VTK。
|
|
|
|
|
if(bCreateUnstructuredGrid) {
|
|
|
|
|
oResult.m_pUnstructuredGrid =
|
|
|
|
|
createPebiUnstructuredGrid(
|
|
|
|
|
oResult.m_oGridOutput1);
|
|
|
|
|
oResult.m_oGridOutput1,
|
|
|
|
|
pCancelRequested);
|
|
|
|
|
if(oResult.m_pUnstructuredGrid == nullptr ||
|
|
|
|
|
oResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
oResult.m_bSucceeded = true;
|
|
|
|
|
oResult.m_bSucceeded = !isCancellationRequested(pCancelRequested);
|
|
|
|
|
} catch(const std::exception& e) {
|
|
|
|
|
zxLogInstance::getInstance()->writeLogF(
|
|
|
|
|
QString("C++ Exception: %1").arg(e.what()));
|
|
|
|
|
|