|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
|
#include "nmAttrRegistry.h"
|
|
|
#include "nmPebiResultSnapshot.h"
|
|
|
#include "nmPebiResultSnapshotSerializer.h"
|
|
|
#include "nmNumericalSaveSession.h"
|
|
|
|
|
|
#include "nmDataAnalyzeContext.h"
|
|
|
#include "nmDataAnalyzeContextProvider.h"
|
|
|
#include "nmDataPlotContext.h"
|
|
|
#include "nmDataPlotContextProvider.h"
|
|
|
#include "nmDataWellBase.h"
|
|
|
#include "nmDataVerticalWell.h"
|
|
|
#include "nmDataVerticalFracturedWell.h"
|
|
|
#include "nmDataHorizontalFracturedWell.h"
|
|
|
|
|
|
#include "ZxDataWell.h"
|
|
|
#include "ZxBaseUtil.h"
|
|
|
#include <QSet>
|
|
|
#include <QThread>
|
|
|
#include "zxLogInstance.h"
|
|
|
|
|
|
#include "nmDataReservoir.h"
|
|
|
|
|
|
#include "nmDataRegionMark.h"
|
|
|
#include "nmDataOutline.h"
|
|
|
#include "nmDataRegion.h"
|
|
|
#include "nmDataFracture.h"
|
|
|
#include "nmDataFault.h"
|
|
|
#include "nmDataMeasuringScale.h"
|
|
|
#include "nmDataMeasure.h"
|
|
|
#include "nmDataAxis.h"
|
|
|
#include "nmDataGeoRef.h"
|
|
|
#include "nmDataDiagnostic.h"
|
|
|
#include "nmDataForecast.h"
|
|
|
#include "nmDataAutomaticFitting.h"
|
|
|
#include "nmDataSensitive.h"
|
|
|
|
|
|
|
|
|
#include "nmDataPvtParaForPebi.h"
|
|
|
#include "ZxDataWell.h"
|
|
|
#include "ZxDataGaugeP.h"
|
|
|
#include "ZxDataGaugeF.h"
|
|
|
#include "zxSysUtils.h"
|
|
|
#include "ZxDataProject.h"
|
|
|
|
|
|
#include "nmDataMixedResults.h"
|
|
|
#include "nmDataLayer.h"
|
|
|
#include "nmDataUtils.h"
|
|
|
|
|
|
#include "mAlgDefines.h"
|
|
|
|
|
|
#include "nmDataJsonTools.h" // JSON工具类
|
|
|
#include "nmTranslationManager.h"
|
|
|
|
|
|
#include "iAnalRun.h"
|
|
|
#include "ZxDataAnalRun.h"
|
|
|
|
|
|
#include <Windows.h>
|
|
|
#include <iostream>
|
|
|
#include <new>
|
|
|
#include <vector>
|
|
|
#include "singlePhaseSolver.h"
|
|
|
|
|
|
#include "nmDataTimeStepSetting.h"
|
|
|
|
|
|
#include <QByteArray>
|
|
|
#include <QCoreApplication>
|
|
|
#include <QDebug>
|
|
|
#include <QDir>
|
|
|
#include <QFile>
|
|
|
#include <QFileInfo>
|
|
|
#include <QMessageBox>
|
|
|
#include <QUuid>
|
|
|
|
|
|
// 井基参数名 → 数据成员访问器的映射描述
|
|
|
struct WellParaDesc {
|
|
|
const char* sName; // 参数基名,如 "W_X"
|
|
|
NM_WELL_MODEL eWellType; // Unknow_Well 表示公用参数
|
|
|
nmDataAttribute* (*getAttr)(nmDataWellBase&); // 访问器函数指针
|
|
|
};
|
|
|
|
|
|
static nmDataAttribute* getParaX(nmDataWellBase& w) { return &w.getX(); }
|
|
|
static nmDataAttribute* getParaY(nmDataWellBase& w) { return &w.getY(); }
|
|
|
static nmDataAttribute* getParaRw(nmDataWellBase& w) { return &w.getRadius(); }
|
|
|
static nmDataAttribute* getParaC(nmDataWellBase& w) { return &w.getWellboreStorage(); }
|
|
|
static nmDataAttribute* getParaSkin(nmDataWellBase& w)
|
|
|
{
|
|
|
return w.getPerforationCount() > 0 ? &w.getPerforation(0)->getSkin() : NULL;
|
|
|
}
|
|
|
static nmDataAttribute* getParaWellLength(nmDataWellBase& w) { return &w.getWellLength(); }
|
|
|
static nmDataAttribute* getParaHfDrainAngle(nmDataWellBase& w) { return &static_cast<nmDataHorizontalFracturedWell&>(w).getDrainAngle(); }
|
|
|
static nmDataAttribute* getParaHfNumberOfFractures(nmDataWellBase& w) { return &static_cast<nmDataHorizontalFracturedWell&>(w).getNumberOfFractures(); }
|
|
|
static nmDataAttribute* getParaVfFractureHalfLength(nmDataWellBase& w) { return &static_cast<nmDataVerticalFracturedWell&>(w).getFractureHalfLength(); }
|
|
|
static nmDataAttribute* getParaVfFractureAngle(nmDataWellBase& w) { return &static_cast<nmDataVerticalFracturedWell&>(w).getFractureAngle(); }
|
|
|
static nmDataAttribute* getParaVfDfc(nmDataWellBase& w) { return &static_cast<nmDataVerticalFracturedWell&>(w).getDfc(); }
|
|
|
static nmDataAttribute* getParaHfFractureHalfLength(nmDataWellBase& w) { return &static_cast<nmDataHorizontalFracturedWell&>(w).getFractureHalfLength(); }
|
|
|
static nmDataAttribute* getParaHfFractureAngle(nmDataWellBase& w) { return &static_cast<nmDataHorizontalFracturedWell&>(w).getFractureAngle(); }
|
|
|
static nmDataAttribute* getParaHfDfc(nmDataWellBase& w) { return &static_cast<nmDataHorizontalFracturedWell&>(w).getDfc(); }
|
|
|
|
|
|
static const WellParaDesc WELL_PARA_DESCS[] = {
|
|
|
{"W_X", NM_WELL_MODEL::Unknow_Well, &getParaX},
|
|
|
{"W_Y", NM_WELL_MODEL::Unknow_Well, &getParaY},
|
|
|
{"W_Rw", NM_WELL_MODEL::Unknow_Well, &getParaRw},
|
|
|
{"W_C", NM_WELL_MODEL::Unknow_Well, &getParaC},
|
|
|
{"W_Skin", NM_WELL_MODEL::Unknow_Well, &getParaSkin},
|
|
|
{"W_FractureHalfLength", NM_WELL_MODEL::Vertical_Fractured_Well, &getParaVfFractureHalfLength},
|
|
|
{"W_FractureAngle", NM_WELL_MODEL::Vertical_Fractured_Well, &getParaVfFractureAngle},
|
|
|
{"W_Dfc", NM_WELL_MODEL::Vertical_Fractured_Well, &getParaVfDfc},
|
|
|
{"W_WellLength", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaWellLength},
|
|
|
{"W_DrainAngle", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaHfDrainAngle},
|
|
|
{"W_NumberOfFractures", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaHfNumberOfFractures},
|
|
|
{"W_FractureHalfLength", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaHfFractureHalfLength},
|
|
|
{"W_FractureAngle", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaHfFractureAngle},
|
|
|
{"W_Dfc", NM_WELL_MODEL::Horizontal_Fractured_Well, &getParaHfDfc},
|
|
|
};
|
|
|
static const int WELL_PARA_DESC_COUNT = sizeof(WELL_PARA_DESCS) / sizeof(WELL_PARA_DESCS[0]);
|
|
|
|
|
|
static bool isWellParaOf(const WellParaDesc& desc, NM_WELL_MODEL eWellType)
|
|
|
{
|
|
|
return desc.eWellType == NM_WELL_MODEL::Unknow_Well || desc.eWellType == eWellType;
|
|
|
}
|
|
|
|
|
|
// 当前 PEBI 网格只支持这三类数值井。Map 中其他类型的图元不能进入求解井集合。
|
|
|
static bool isSupportedNumericalWell(const nmDataWellBase* pWellData)
|
|
|
{
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const NM_WELL_MODEL eWellType = pWellData->getWellType();
|
|
|
return eWellType == NM_WELL_MODEL::Vertical_Well ||
|
|
|
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
|
|
|
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
|
|
|
}
|
|
|
|
|
|
static bool isWellParaAttrName(const QString& name)
|
|
|
{
|
|
|
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) {
|
|
|
if (name.endsWith(QString("_") + WELL_PARA_DESCS[i].sName)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 参数名必须与 XML 定义和参数面板拼接的基础编号完全一致。
|
|
|
struct GeometryParaDesc {
|
|
|
const char* sName;
|
|
|
nmDataAttribute* (*getFractureAttr)(nmDataFracture&);
|
|
|
nmDataAttribute* (*getFaultAttr)(nmDataFault&);
|
|
|
nmDataAttribute* (*getRegionAttr)(nmDataRegion&);
|
|
|
nmDataAttribute* (*getRegionMarkAttr)(nmDataRegionMark&);
|
|
|
};
|
|
|
|
|
|
static nmDataAttribute* getFractureStartX(nmDataFracture& data) { return &data.getStartX(); }
|
|
|
static nmDataAttribute* getFractureStartY(nmDataFracture& data) { return &data.getStartY(); }
|
|
|
static nmDataAttribute* getFractureEndX(nmDataFracture& data) { return &data.getEndX(); }
|
|
|
static nmDataAttribute* getFractureEndY(nmDataFracture& data) { return &data.getEndY(); }
|
|
|
static nmDataAttribute* getFractureFc(nmDataFracture& data) { return &data.getFractureDfc(); }
|
|
|
static nmDataAttribute* getFaultStartX(nmDataFault& data) { return &data.getStartX(); }
|
|
|
static nmDataAttribute* getFaultStartY(nmDataFault& data) { return &data.getStartY(); }
|
|
|
static nmDataAttribute* getFaultEndX(nmDataFault& data) { return &data.getEndX(); }
|
|
|
static nmDataAttribute* getFaultEndY(nmDataFault& data) { return &data.getEndY(); }
|
|
|
static nmDataAttribute* getRegionLeakage(nmDataRegion& data) { return &data.getRegionLeakage(); }
|
|
|
static nmDataAttribute* getRegionMarkComW(nmDataRegionMark& data) { return &data.getComW(); }
|
|
|
static nmDataAttribute* getRegionMarkComKr(nmDataRegionMark& data) { return &data.getComKr(); }
|
|
|
static nmDataAttribute* getRegionMarkNetToGross(nmDataRegionMark& data) { return &data.getNetToGross(); }
|
|
|
|
|
|
static const GeometryParaDesc GEOMETRY_PARA_DESCS[] = {
|
|
|
{"F_X0", &getFractureStartX, NULL, NULL, NULL},
|
|
|
{"F_Y0", &getFractureStartY, NULL, NULL, NULL},
|
|
|
{"F_X1", &getFractureEndX, NULL, NULL, NULL},
|
|
|
{"F_Y1", &getFractureEndY, NULL, NULL, NULL},
|
|
|
{"F_FC", &getFractureFc, NULL, NULL, NULL},
|
|
|
{"FT_X0", NULL, &getFaultStartX, NULL, NULL},
|
|
|
{"FT_Y0", NULL, &getFaultStartY, NULL, NULL},
|
|
|
{"FT_X1", NULL, &getFaultEndX, NULL, NULL},
|
|
|
{"FT_Y1", NULL, &getFaultEndY, NULL, NULL},
|
|
|
{"R_Leakage", NULL, NULL, &getRegionLeakage, NULL},
|
|
|
{"RM_ComW", NULL, NULL, NULL, &getRegionMarkComW},
|
|
|
{"RM_ComKr", NULL, NULL, NULL, &getRegionMarkComKr},
|
|
|
{"RM_NetToGross", NULL, NULL, NULL, &getRegionMarkNetToGross},
|
|
|
};
|
|
|
static const int GEOMETRY_PARA_DESC_COUNT = sizeof(GEOMETRY_PARA_DESCS) / sizeof(GEOMETRY_PARA_DESCS[0]);
|
|
|
|
|
|
static const char* OUTLINE_PARA_NAMES[] = {
|
|
|
"BR_XMin", "BR_YMin", "BR_XMax", "BR_YMax",
|
|
|
"BC_CenterX", "BC_CenterY", "BC_Radius",
|
|
|
"BP_X", "BP_Y"
|
|
|
};
|
|
|
static const int OUTLINE_PARA_NAME_COUNT = sizeof(OUTLINE_PARA_NAMES) / sizeof(OUTLINE_PARA_NAMES[0]);
|
|
|
|
|
|
static bool isGeometryParaAttrName(const QString& name)
|
|
|
{
|
|
|
for (int i = 0; i < GEOMETRY_PARA_DESC_COUNT; ++i) {
|
|
|
if (name.endsWith(QString("_") + GEOMETRY_PARA_DESCS[i].sName)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
for (int i = 0; i < OUTLINE_PARA_NAME_COUNT; ++i) {
|
|
|
if (name.endsWith(QString("_") + OUTLINE_PARA_NAMES[i])) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
// 清空指定目录下的所有旧文件和子目录,但保留目录本身
|
|
|
bool clearDirectoryContents(const QString& dirPath)
|
|
|
{
|
|
|
if(dirPath.trimmed().isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDir dir(dirPath);
|
|
|
if(dir.isRoot()) {
|
|
|
qDebug() << QString("Refuse to clear root directory: %1").arg(dirPath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!dir.exists()) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden | QDir::System);
|
|
|
for(int i = 0; i < entries.size(); ++i) {
|
|
|
const QFileInfo& entry = entries.at(i);
|
|
|
bool bRemoved = false;
|
|
|
|
|
|
if(entry.isDir() && !entry.isSymLink()) {
|
|
|
bRemoved = clearDirectoryContents(entry.absoluteFilePath()) && dir.rmdir(entry.fileName());
|
|
|
} else {
|
|
|
bRemoved = QFile::remove(entry.absoluteFilePath());
|
|
|
}
|
|
|
|
|
|
if(!bRemoved) {
|
|
|
qDebug() << QString("Failed to remove old result item: %1").arg(entry.absoluteFilePath());
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 多条PVT曲线都可能带压力横坐标,只保存第一次读到的有效横坐标
|
|
|
void setPebiPressureIfEmpty(nmDataPvtParaForPebi* pPvtPara, const QVector<double>& vecX)
|
|
|
{
|
|
|
if(pPvtPara != nullptr && pPvtPara->getPressure().isEmpty() && !vecX.isEmpty()) {
|
|
|
pPvtPara->setPressure(vecX);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 从Diffusion右侧结果表中提取指定列数据
|
|
|
bool extractDiffusionColumn(const VVecDouble& vvec, int nColumn, QVector<double>& vecValues)
|
|
|
{
|
|
|
vecValues.clear();
|
|
|
if(vvec.isEmpty() || nColumn < 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(vvec.size() > nColumn && vvec[nColumn].size() > 4 && vvec[nColumn].size() > vvec.size()) {
|
|
|
vecValues = vvec[nColumn];
|
|
|
return !vecValues.isEmpty();
|
|
|
}
|
|
|
|
|
|
// Diffusion结果表通常按行保存:[自变量, 结果1, 结果2]
|
|
|
for(int i = 0; i < vvec.size(); ++i) {
|
|
|
if(vvec[i].size() > nColumn) {
|
|
|
vecValues.append(vvec[i][nColumn]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!vecValues.isEmpty()) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 兼容少量按列缓存的数据
|
|
|
if(vvec.size() > nColumn) {
|
|
|
vecValues = vvec[nColumn];
|
|
|
}
|
|
|
|
|
|
return !vecValues.isEmpty();
|
|
|
}
|
|
|
|
|
|
// 根据饱和度坐标计算互补饱和度,例如油水相渗中由Sw得到So
|
|
|
QVector<double> complementSaturation(const QVector<double>& vecSaturation)
|
|
|
{
|
|
|
QVector<double> vecResult;
|
|
|
vecResult.reserve(vecSaturation.size());
|
|
|
|
|
|
for(int i = 0; i < vecSaturation.size(); ++i) {
|
|
|
vecResult.append(1.0 - vecSaturation[i]);
|
|
|
}
|
|
|
|
|
|
return vecResult;
|
|
|
}
|
|
|
|
|
|
// 将数组顺序反转,用于把Diffusion中按Sw递增的数据整理为按So递增的数据
|
|
|
QVector<double> reversedVector(const QVector<double>& vecValues)
|
|
|
{
|
|
|
QVector<double> vecResult;
|
|
|
vecResult.reserve(vecValues.size());
|
|
|
|
|
|
for(int i = vecValues.size(); i > 0; --i) {
|
|
|
vecResult.append(vecValues[i - 1]);
|
|
|
}
|
|
|
|
|
|
return vecResult;
|
|
|
}
|
|
|
|
|
|
// 将油水两相Diffusion相渗结果映射到PEBI求解器需要的饱和度和相对渗透率字段
|
|
|
void applyDiffusionKkToPebiPvt(nmDataPvtParaForPebi* pPvtPara, const VVecDouble& vvecKK)
|
|
|
{
|
|
|
if(vvecKK.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QVector<double> vecS;
|
|
|
QVector<double> vecKr1;
|
|
|
QVector<double> vecKr2;
|
|
|
|
|
|
if(!extractDiffusionColumn(vvecKK, 0, vecS)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
extractDiffusionColumn(vvecKK, 1, vecKr1);
|
|
|
extractDiffusionColumn(vvecKK, 2, vecKr2);
|
|
|
|
|
|
// 油水:第一列为Sw,So按1-Sw生成,后续列为Kro/Krw
|
|
|
// PEBI默认相渗以So为横坐标,Diffusion表按Sw递增保存,这里整体转为So递增方向
|
|
|
pPvtPara->setSo(reversedVector(complementSaturation(vecS)));
|
|
|
if(!vecKr1.isEmpty()) {
|
|
|
pPvtPara->setKro(reversedVector(vecKr1));
|
|
|
}
|
|
|
if(!vecKr2.isEmpty()) {
|
|
|
pPvtPara->setKrw(reversedVector(vecKr2));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// @brief 尝试获取某相态的3条PVT曲线(B/C/Miu),全部成功则写入pebiPvtPara
|
|
|
/// @param pCtx 上下文提供者,用于读取PVT结果页
|
|
|
/// @param pSubWnd 当前流动段分析窗口
|
|
|
/// @param pftContext getPvtRstOf需要的相态参数
|
|
|
/// @param sBName 体积系数参数名("Bo"/"Bg"/"Bw")
|
|
|
/// @param sCName 压缩系数参数名("Co"/"Cg"/"Cw")
|
|
|
/// @param sMiuName 粘度参数名("Miuo"/"Miug"/"Miuw")
|
|
|
/// @param pPvt 输出的PEBI PVT参数对象
|
|
|
/// @param vecPressure 压力横坐标(首次成功时写入,后续复用)
|
|
|
/// @return 三条曲线全部获取成功返回true,否则返回false且不写入pPvt
|
|
|
bool tryFetchPhasePvtCurves(
|
|
|
nmDataAnalyzeContextProvider* pCtx,
|
|
|
iSubWndFitting* pSubWnd,
|
|
|
PvtFluidType pftContext,
|
|
|
const QString& sBName,
|
|
|
const QString& sCName,
|
|
|
const QString& sMiuName,
|
|
|
nmDataPvtParaForPebi* pPvt,
|
|
|
QVector<double>& vecPressure)
|
|
|
{
|
|
|
// 分别获取体积系数、压缩系数、粘度三条曲线
|
|
|
QVector<double> vecB, vecC, vecMiu, vecX;
|
|
|
bool bB = pCtx->getPvtRstOf(pSubWnd, pftContext, sBName, vecX, vecB);
|
|
|
bool bC = pCtx->getPvtRstOf(pSubWnd, pftContext, sCName, vecX, vecC);
|
|
|
bool bMiu = pCtx->getPvtRstOf(pSubWnd, pftContext, sMiuName, vecX, vecMiu);
|
|
|
|
|
|
// 任一曲线缺失则视为该相态PVT数据不完整,不写入
|
|
|
if(!(bB && bC && bMiu)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 首次成功时保存压力横坐标
|
|
|
if(vecPressure.isEmpty() && !vecX.isEmpty()) {
|
|
|
vecPressure = vecX;
|
|
|
}
|
|
|
|
|
|
// 按相态名称写入对应的setter(油气水各自独立字段)
|
|
|
if(sBName == "Bo") pPvt->setBo(vecB);
|
|
|
else if(sBName == "Bg") pPvt->setBg(vecB);
|
|
|
else if(sBName == "Bw") pPvt->setBw(vecB);
|
|
|
|
|
|
if(sCName == "Co") pPvt->setCo(vecC);
|
|
|
else if(sCName == "Cg") pPvt->setCg(vecC);
|
|
|
else if(sCName == "Cw") pPvt->setCw(vecC);
|
|
|
|
|
|
if(sMiuName == "Miuo") pPvt->setMiuo(vecMiu);
|
|
|
else if(sMiuName == "Miug") pPvt->setMiug(vecMiu);
|
|
|
else if(sMiuName == "Miuw") pPvt->setMiuw(vecMiu);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/// @brief 设置油藏属性的简化辅助,消除 tempAttr=getX(); tempAttr.setValue(); setX(tempAttr) 三行重复模式
|
|
|
/// @param attr 油藏属性引用
|
|
|
/// @param dValue 属性值
|
|
|
void setReservoirAttr(nmDataAttribute& attr, double dValue)
|
|
|
{
|
|
|
attr.setValue(dValue);
|
|
|
}
|
|
|
|
|
|
/// @brief 设置油藏属性的简化辅助(QString版本)
|
|
|
void setReservoirAttr(nmDataAttribute& attr, const QString& sValue)
|
|
|
{
|
|
|
attr.setValue(sValue);
|
|
|
}
|
|
|
|
|
|
/// @brief 从界面读取PVT单值参数
|
|
|
/// @param pCtx 上下文提供者
|
|
|
/// @param pSubWnd 当前流动段分析窗口
|
|
|
/// @param eType 相态类型
|
|
|
/// @return 参数名到值的映射
|
|
|
QMap<QString, double> readPvtSingleValues(
|
|
|
nmDataAnalyzeContextProvider* pCtx,
|
|
|
iSubWndFitting* pSubWnd,
|
|
|
PvtFluidType eType)
|
|
|
{
|
|
|
// 按相态构建需要读取的参数列表
|
|
|
QStringList listParas;
|
|
|
switch(eType) {
|
|
|
case WFT_Oil: listParas << "Bo" << "Miuo"; break;
|
|
|
case WFT_Gas: listParas << "Bg" << "Miug"; break;
|
|
|
case WFT_Water: listParas << "Bw" << "Miuw"; break;
|
|
|
case WFT_Oil_Water: listParas << "Bo" << "Miuo" << "Bw" << "Miuw"; break;
|
|
|
default: break;
|
|
|
}
|
|
|
|
|
|
// 综合压缩系数Ct,不区分模式统一读取
|
|
|
listParas << "Ct";
|
|
|
|
|
|
// 调用接口读取参数值
|
|
|
QMap<QString, double> mapPvtValues;
|
|
|
if(!listParas.isEmpty()) {
|
|
|
pCtx->getPvtParaValues(pSubWnd, listParas, mapPvtValues);
|
|
|
}
|
|
|
return mapPvtValues;
|
|
|
}
|
|
|
|
|
|
/// @brief 按相态填充油藏PVT字段和压缩系数
|
|
|
/// @param pRes 油藏数据对象
|
|
|
/// @param eType 相态类型
|
|
|
/// @param mapPvtValues 从界面读取的PVT单值参数
|
|
|
/// @param dCf 岩石压缩系数(来自分层数据)
|
|
|
void populateReservoirByPhase(
|
|
|
nmDataReservoir* pRes,
|
|
|
PvtFluidType eType,
|
|
|
const QMap<QString, double>& mapPvtValues,
|
|
|
double dCf)
|
|
|
{
|
|
|
// 按相态设置多相流类型和对应的B/Miu字段
|
|
|
switch(eType) {
|
|
|
case WFT_Oil:
|
|
|
pRes->setPhaseType(PHASE_Oil);
|
|
|
setReservoirAttr(pRes->getBo(), mapPvtValues.value("Bo", 1.5));
|
|
|
setReservoirAttr(pRes->getMiuo(), mapPvtValues.value("Miuo", 1.0));
|
|
|
break;
|
|
|
case WFT_Gas:
|
|
|
pRes->setPhaseType(PHASE_Gas);
|
|
|
setReservoirAttr(pRes->getBg(), mapPvtValues.value("Bg", 1.0));
|
|
|
setReservoirAttr(pRes->getMiug(), mapPvtValues.value("Miug", 1.0));
|
|
|
break;
|
|
|
case WFT_Water:
|
|
|
pRes->setPhaseType(PHASE_Water);
|
|
|
setReservoirAttr(pRes->getBw(), mapPvtValues.value("Bw", 1.0));
|
|
|
setReservoirAttr(pRes->getMiuw(), mapPvtValues.value("Miuw", 1.0));
|
|
|
break;
|
|
|
case WFT_Oil_Water:
|
|
|
pRes->setPhaseType(PHASE_Oil_Water);
|
|
|
setReservoirAttr(pRes->getBo(), mapPvtValues.value("Bo", 1.5));
|
|
|
setReservoirAttr(pRes->getMiuo(), mapPvtValues.value("Miuo", 1.0));
|
|
|
setReservoirAttr(pRes->getBw(), mapPvtValues.value("Bw", 1.0));
|
|
|
setReservoirAttr(pRes->getMiuw(), mapPvtValues.value("Miuw", 1.0));
|
|
|
break;
|
|
|
default:
|
|
|
pRes->setPhaseType(PHASE_UNKNOWN);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 压缩系数:Ct来自PVT参数界面,Cf来自分层数据,不区分模式统一设置
|
|
|
setReservoirAttr(pRes->getCt(), mapPvtValues.value("Ct", 0.1));
|
|
|
setReservoirAttr(pRes->getCf(), dCf);
|
|
|
}
|
|
|
|
|
|
/// @brief 创建默认分层
|
|
|
/// @param dThickness 储层厚度
|
|
|
/// @param vecLayers 分层数据列表(输出)
|
|
|
void createDefaultLayer(double dThickness, QVector<nmDataLayer*>& vecLayers)
|
|
|
{
|
|
|
// 清空现有分层数据
|
|
|
qDeleteAll(vecLayers);
|
|
|
vecLayers.clear();
|
|
|
|
|
|
// 创建一个默认分层
|
|
|
nmDataLayer* pDefaultLayer = new nmDataLayer();
|
|
|
pDefaultLayer->setTop(6000.0); // 默认顶深
|
|
|
pDefaultLayer->setThickness(dThickness); // 使用从界面获取的厚度值
|
|
|
pDefaultLayer->setBottom(6000.0 + dThickness); // 计算底深
|
|
|
pDefaultLayer->setIsChecked(false); // 默认未选中
|
|
|
pDefaultLayer->setColor(QColor(0, 255, 0)); // 设置默认颜色(绿色)
|
|
|
|
|
|
// 将默认分层添加到分层列表
|
|
|
vecLayers.append(pDefaultLayer);
|
|
|
}
|
|
|
|
|
|
rapidjson::Value numericalJsonString(
|
|
|
const QString& sValue,
|
|
|
rapidjson::Document::AllocatorType& oAllocator)
|
|
|
{
|
|
|
const QByteArray baValue = sValue.toUtf8();
|
|
|
return rapidjson::Value(baValue.constData(),
|
|
|
static_cast<rapidjson::SizeType>(baValue.size()), oAllocator);
|
|
|
}
|
|
|
|
|
|
void addNumericalFileReference(
|
|
|
rapidjson::Value& oParent,
|
|
|
const char* pName,
|
|
|
const nmNumericalFileReference& oReference,
|
|
|
rapidjson::Document::AllocatorType& oAllocator)
|
|
|
{
|
|
|
rapidjson::Value oJson(rapidjson::kObjectType);
|
|
|
oJson.AddMember("Path", numericalJsonString(
|
|
|
oReference.m_sRelativePath, oAllocator), oAllocator);
|
|
|
oJson.AddMember("Length", oReference.m_nLength, oAllocator);
|
|
|
oJson.AddMember("Sha1", numericalJsonString(
|
|
|
oReference.m_sSha1, oAllocator), oAllocator);
|
|
|
oParent.AddMember(rapidjson::Value(pName, oAllocator).Move(),
|
|
|
oJson, oAllocator);
|
|
|
}
|
|
|
|
|
|
bool readNumericalFileReference(
|
|
|
const rapidjson::Value& oParent,
|
|
|
const char* pName,
|
|
|
nmNumericalFileReference& oReference)
|
|
|
{
|
|
|
if(!oParent.IsObject() || !oParent.HasMember(pName) ||
|
|
|
!oParent[pName].IsObject())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
const rapidjson::Value& oJson = oParent[pName];
|
|
|
if(!oJson.HasMember("Path") || !oJson["Path"].IsString() ||
|
|
|
!oJson.HasMember("Length") || !oJson["Length"].IsUint64() ||
|
|
|
!oJson.HasMember("Sha1") || !oJson["Sha1"].IsString())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
oReference.m_sRelativePath = QString::fromUtf8(oJson["Path"].GetString());
|
|
|
oReference.m_nLength = oJson["Length"].GetUint64();
|
|
|
oReference.m_sSha1 = QString::fromLatin1(oJson["Sha1"].GetString());
|
|
|
return oReference.isValid();
|
|
|
}
|
|
|
|
|
|
bool readV3WindowHeader(
|
|
|
const QString& sMainJsonPath,
|
|
|
nmNumericalWindowPayloadReferences& oReferences,
|
|
|
QString& sCurrentResultWellInstanceId,
|
|
|
quint64& nGridInputRevision,
|
|
|
quint64& nBuiltGridRevision,
|
|
|
quint64& nResultInputRevision,
|
|
|
QString* pError)
|
|
|
{
|
|
|
const QFileInfo oInfo(sMainJsonPath);
|
|
|
if(!oInfo.isFile() || oInfo.size() <= 0 ||
|
|
|
oInfo.size() > 64ll * 1024ll * 1024ll)
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical v3 main JSON is missing or too large.";
|
|
|
return false;
|
|
|
}
|
|
|
rapidjson::Document oDocument;
|
|
|
if(!nmDataJsonTools::ReadDomFromFile(sMainJsonPath, oDocument) ||
|
|
|
!oDocument.IsObject() ||
|
|
|
!oDocument.HasMember("NumericalProjectVersion") ||
|
|
|
!oDocument["NumericalProjectVersion"].IsInt())
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical project version is missing.";
|
|
|
return false;
|
|
|
}
|
|
|
const int nVersion = oDocument["NumericalProjectVersion"].GetInt();
|
|
|
if(nVersion != nmNumericalResultPersistence::projectVersion())
|
|
|
{
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
*pError = nVersion == 2
|
|
|
? "Numerical project version 2 is not supported."
|
|
|
: "Numerical project version is not supported.";
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
if(!oDocument.HasMember("InputRevisions") ||
|
|
|
!oDocument["InputRevisions"].IsObject() ||
|
|
|
!oDocument.HasMember("CurrentResultWellInstanceId") ||
|
|
|
!oDocument["CurrentResultWellInstanceId"].IsString() ||
|
|
|
!oDocument.HasMember("PayloadFiles") ||
|
|
|
!oDocument["PayloadFiles"].IsObject())
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical v3 main JSON header is incomplete.";
|
|
|
return false;
|
|
|
}
|
|
|
const rapidjson::Value& oRevisions = oDocument["InputRevisions"];
|
|
|
if(!oRevisions.HasMember("GridInputRevision") ||
|
|
|
!oRevisions["GridInputRevision"].IsUint64() ||
|
|
|
!oRevisions.HasMember("BuiltGridRevision") ||
|
|
|
!oRevisions["BuiltGridRevision"].IsUint64() ||
|
|
|
!oRevisions.HasMember("ResultInputRevision") ||
|
|
|
!oRevisions["ResultInputRevision"].IsUint64())
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical input revisions are invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
nGridInputRevision = oRevisions["GridInputRevision"].GetUint64();
|
|
|
nBuiltGridRevision = oRevisions["BuiltGridRevision"].GetUint64();
|
|
|
nResultInputRevision = oRevisions["ResultInputRevision"].GetUint64();
|
|
|
if(nGridInputRevision == 0 || nResultInputRevision == 0 ||
|
|
|
(nBuiltGridRevision != 0 && nBuiltGridRevision != nGridInputRevision))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical input revision values are invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
sCurrentResultWellInstanceId = QString::fromUtf8(
|
|
|
oDocument["CurrentResultWellInstanceId"].GetString());
|
|
|
|
|
|
const rapidjson::Value& oPayload = oDocument["PayloadFiles"];
|
|
|
if(!oPayload.HasMember("CurrentGrid") ||
|
|
|
!oPayload.HasMember("WellHistories") ||
|
|
|
!oPayload["WellHistories"].IsArray() ||
|
|
|
oPayload["WellHistories"].Size() > 10000u ||
|
|
|
!oPayload.HasMember("Snapshot"))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Numerical payload reference list is invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
QSet<QString> setPaths;
|
|
|
oReferences = nmNumericalWindowPayloadReferences();
|
|
|
if(!oPayload["CurrentGrid"].IsNull())
|
|
|
{
|
|
|
if(!readNumericalFileReference(oPayload, "CurrentGrid",
|
|
|
oReferences.m_oCurrentGrid))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Current grid reference is invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_bHasCurrentGrid = true;
|
|
|
setPaths.insert(oReferences.m_oCurrentGrid.m_sRelativePath);
|
|
|
}
|
|
|
const rapidjson::Value& oHistories = oPayload["WellHistories"];
|
|
|
for(rapidjson::SizeType nIndex = 0; nIndex < oHistories.Size(); ++nIndex)
|
|
|
{
|
|
|
const rapidjson::Value& oJson = oHistories[nIndex];
|
|
|
nmNumericalFileReference oReference;
|
|
|
if(!oJson.IsObject() ||
|
|
|
!oJson.HasMember("WellInstanceId") ||
|
|
|
!oJson["WellInstanceId"].IsString() ||
|
|
|
!readNumericalFileReference(oJson, "File", oReference))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Well history reference is invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
const QString sWellInstanceId = QString::fromUtf8(
|
|
|
oJson["WellInstanceId"].GetString());
|
|
|
if(sWellInstanceId.isEmpty() ||
|
|
|
oReferences.m_mapWellHistories.contains(sWellInstanceId) ||
|
|
|
setPaths.contains(oReference.m_sRelativePath))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Well history references are duplicated.";
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_mapWellHistories.insert(sWellInstanceId, oReference);
|
|
|
setPaths.insert(oReference.m_sRelativePath);
|
|
|
}
|
|
|
if(!oPayload["Snapshot"].IsNull())
|
|
|
{
|
|
|
if(!readNumericalFileReference(oPayload, "Snapshot",
|
|
|
oReferences.m_oSnapshot) ||
|
|
|
setPaths.contains(oReferences.m_oSnapshot.m_sRelativePath))
|
|
|
{
|
|
|
if(pError != NULL) *pError = "Snapshot reference is invalid or duplicated.";
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_bHasSnapshot = true;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager)
|
|
|
|
|
|
nmDataAnalyzeManager::nmDataAnalyzeManager(): ZxDataObjectBin(0)
|
|
|
{
|
|
|
m_pOwnerFitting = nullptr;
|
|
|
m_nBackgroundUseCount = 0;
|
|
|
m_bPebiGridGenerationActive = false;
|
|
|
m_nPebiGridGenerationRevision = 0;
|
|
|
m_reservoirData = nullptr;
|
|
|
m_outlineData = nullptr;
|
|
|
m_pMeasuringScaleData = nullptr;
|
|
|
m_pebiPvtPara = nullptr;
|
|
|
m_pMixedResults = nullptr;
|
|
|
m_pLayerData = nullptr;
|
|
|
m_pCurDataWell = nullptr;
|
|
|
m_pMeasureData = nullptr;
|
|
|
m_axisData = nullptr;
|
|
|
m_pNmGuiPlot = nullptr;
|
|
|
m_pGeoRefData = nullptr;
|
|
|
m_pTimeStep = nullptr;
|
|
|
//m_pPerCloData = nullptr;
|
|
|
m_pAutomaticFittingData = nullptr;
|
|
|
m_pDiagnosticData = nullptr;
|
|
|
//m_pSkinVsRateData = nullptr;
|
|
|
//m_pFlowSegmentData = nullptr;
|
|
|
m_pForecastData = nullptr;
|
|
|
m_pSensitiveData = nullptr;
|
|
|
|
|
|
// 属性注册表
|
|
|
m_pAttrRegistry = new nmAttrRegistry();
|
|
|
|
|
|
m_backgroundImageInfo.bIsVisible = false;
|
|
|
// 初始化混合参数数据(Temp)
|
|
|
//m_pMixedResults = new nmDataMixedResults;
|
|
|
// 初始化储层数据
|
|
|
m_pLayerData = new nmDataLayer;
|
|
|
m_eGridType = NM_Grid_Type::NM_Grid_PEBI; //默认网格类型为PEBI
|
|
|
m_eSolverModelType = NM_SOLVER_MODEL_TYPE::SMT_Oil_ConstPvt;
|
|
|
m_nPebiSolverType = PebiSolverCpuAccelerated;
|
|
|
m_nPebiOmpThreads = 4;
|
|
|
m_nPebiIluReuseSteps = 5;
|
|
|
this->initDefaultDisplaySettings();
|
|
|
// 初始化中英文翻译映射
|
|
|
nmTranslationManager::initTranslations();
|
|
|
m_bIsLoadData = false;
|
|
|
|
|
|
// 获取完整路径
|
|
|
//QString appPath = QCoreApplication::applicationFilePath();
|
|
|
//qDebug() << "完整应用路径:" << appPath;
|
|
|
// 许可证路径
|
|
|
//m_licensePath = appPath + "/../../3rd/Pebi/license/HXNWTM_license.dat";
|
|
|
|
|
|
// 获取完整路径
|
|
|
QString appPath = QCoreApplication::applicationFilePath();
|
|
|
|
|
|
// 获取应用程序所在的目录
|
|
|
QString appDir = QFileInfo(appPath).absolutePath();
|
|
|
|
|
|
// 定义从应用程序目录到许可证目录的相对路径
|
|
|
QString relativeJump = "/../Res/license/HXNWTM_license.dat";
|
|
|
|
|
|
// 将目录和相对跳转路径拼接
|
|
|
m_licensePath = appDir + relativeJump;
|
|
|
}
|
|
|
|
|
|
// 初始化静态成员
|
|
|
nmDataAnalyzeManager::~nmDataAnalyzeManager()
|
|
|
{
|
|
|
// 第一步:任何窗口销毁顺序下,都必须先等后台网格/求解任务停止访问。
|
|
|
// wait() 会暂时释放互斥量,因此任务仍能调用 endBackgroundUse() 正常退出。
|
|
|
{
|
|
|
QMutexLocker oLocker(&m_oBackgroundUseMutex);
|
|
|
while(m_nBackgroundUseCount > 0) {
|
|
|
m_oNoBackgroundUseCondition.wait(&m_oBackgroundUseMutex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 图元只引用数据,数据统一由DataManager释放。
|
|
|
// 关闭成果或流动段分析后,清理该分析窗口对应的全部数据。
|
|
|
// 这两个成员是借用引用或容器内对象的别名,不单独delete。
|
|
|
m_pCurDataWell = nullptr;
|
|
|
m_pNmGuiPlot = nullptr;
|
|
|
|
|
|
// 先释放容器中的数据对象,再释放其余独占的单对象数据。
|
|
|
qDeleteAll(m_vWellData);
|
|
|
m_vWellData.clear();
|
|
|
qDeleteAll(m_vFaultData);
|
|
|
m_vFaultData.clear();
|
|
|
qDeleteAll(m_vFractureData);
|
|
|
m_vFractureData.clear();
|
|
|
qDeleteAll(m_vRegionData);
|
|
|
m_vRegionData.clear();
|
|
|
qDeleteAll(m_vRegionMarkData);
|
|
|
m_vRegionMarkData.clear();
|
|
|
qDeleteAll(m_vecLayers);
|
|
|
m_vecLayers.clear();
|
|
|
|
|
|
delete m_outlineData;
|
|
|
m_outlineData = nullptr;
|
|
|
delete m_axisData;
|
|
|
m_axisData = nullptr;
|
|
|
delete m_pAutomaticFittingData;
|
|
|
m_pAutomaticFittingData = nullptr;
|
|
|
delete m_reservoirData;
|
|
|
m_reservoirData = nullptr;
|
|
|
delete m_pMeasuringScaleData;
|
|
|
m_pMeasuringScaleData = nullptr;
|
|
|
delete m_pMeasureData;
|
|
|
m_pMeasureData = nullptr;
|
|
|
delete m_pGeoRefData;
|
|
|
m_pGeoRefData = nullptr;
|
|
|
delete m_pForecastData;
|
|
|
m_pForecastData = nullptr;
|
|
|
delete m_pSensitiveData;
|
|
|
m_pSensitiveData = nullptr;
|
|
|
delete m_pDiagnosticData;
|
|
|
m_pDiagnosticData = nullptr;
|
|
|
delete m_pebiPvtPara;
|
|
|
m_pebiPvtPara = nullptr;
|
|
|
delete m_pMixedResults;
|
|
|
m_pMixedResults = nullptr;
|
|
|
delete m_pLayerData;
|
|
|
m_pLayerData = nullptr;
|
|
|
delete m_pTimeStep;
|
|
|
m_pTimeStep = nullptr;
|
|
|
|
|
|
// 最后 delete 属性注册表(确保数据对象先销毁,nmAttrRegistry 能接收 destroyed 信号清理映射)
|
|
|
delete m_pAttrRegistry;
|
|
|
m_pAttrRegistry = nullptr;
|
|
|
}
|
|
|
|
|
|
QMap<iSubWndFitting*, nmDataAnalyzeManager*> nmDataAnalyzeManager::s_mapDataAnalManager;
|
|
|
iSubWndFitting* nmDataAnalyzeManager::s_pCurSubWndFitting = nullptr;
|
|
|
|
|
|
nmDataAnalyzeManager* nmDataAnalyzeManager::getInstanceByFitting(iSubWndFitting* pSubWndF)
|
|
|
{
|
|
|
if(s_mapDataAnalManager.contains(pSubWndF)) {
|
|
|
return s_mapDataAnalManager[pSubWndF];
|
|
|
}
|
|
|
|
|
|
nmDataAnalyzeManager* pInstance = new nmDataAnalyzeManager();
|
|
|
// DataManager 与创建它的成果窗口一一绑定。后续后台任务读取 PVT、拟压力等
|
|
|
// 上下文时必须使用该窗口,不能随界面当前页签切换到另一份成果。
|
|
|
pInstance->m_pOwnerFitting = pSubWndF;
|
|
|
s_mapDataAnalManager[pSubWndF] = pInstance;
|
|
|
return pInstance;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::removeInstanceByFitting(iSubWndFitting* pSubWndF)
|
|
|
{
|
|
|
if(pSubWndF == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// take()先解除窗口和manager的映射,避免析构期间再次找到待释放对象。
|
|
|
nmDataAnalyzeManager* pInstance = s_mapDataAnalManager.take(pSubWndF);
|
|
|
if(s_pCurSubWndFitting == pSubWndF) {
|
|
|
s_pCurSubWndFitting = nullptr;
|
|
|
}
|
|
|
|
|
|
delete pInstance;
|
|
|
}
|
|
|
|
|
|
nmDataAnalyzeManager* nmDataAnalyzeManager::getCurrentInstance()
|
|
|
{
|
|
|
if(s_mapDataAnalManager.contains(s_pCurSubWndFitting)) {
|
|
|
return s_mapDataAnalManager[s_pCurSubWndFitting];
|
|
|
} else {
|
|
|
return nullptr;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::beginBackgroundUse()
|
|
|
{
|
|
|
QMutexLocker oLocker(&m_oBackgroundUseMutex);
|
|
|
++m_nBackgroundUseCount;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::endBackgroundUse()
|
|
|
{
|
|
|
QMutexLocker oLocker(&m_oBackgroundUseMutex);
|
|
|
if(m_nBackgroundUseCount <= 0) {
|
|
|
Q_ASSERT(false);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
--m_nBackgroundUseCount;
|
|
|
if(m_nBackgroundUseCount == 0) {
|
|
|
m_oNoBackgroundUseCondition.wakeAll();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType)
|
|
|
{
|
|
|
// 根据当前Fitting窗口来获取对应的井相关数据
|
|
|
iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
|
|
|
nmDataAnalyzeContextProvider* pContextProvider = nmDataAnalyzeContext::provider();
|
|
|
Q_ASSERT(nullptr != pSubWndFitting);
|
|
|
Q_ASSERT(nullptr != pContextProvider);
|
|
|
|
|
|
nmDataWellBase* pWellData = nullptr;
|
|
|
|
|
|
// 新建一口井,默认直井
|
|
|
if(eWellType == NM_WELL_MODEL::Vertical_Well) {
|
|
|
// 直接使用子类指针创建对象
|
|
|
nmDataVerticalWell* verticalWell = new nmDataVerticalWell;
|
|
|
pWellData = static_cast<nmDataWellBase*>(verticalWell);
|
|
|
} else if(eWellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
|
|
|
// 直接使用子类指针创建对象
|
|
|
nmDataVerticalFracturedWell* vFracturedWell = new nmDataVerticalFracturedWell;
|
|
|
pWellData = static_cast<nmDataWellBase*>(vFracturedWell);
|
|
|
} else if(eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
|
|
|
// 直接使用子类指针创建对象
|
|
|
nmDataHorizontalFracturedWell* hFracturedWell = new nmDataHorizontalFracturedWell;
|
|
|
pWellData = static_cast<nmDataWellBase*>(hFracturedWell);
|
|
|
}
|
|
|
|
|
|
// 默认井才可以选择当前流动段,其余井默认选择最后一段
|
|
|
if(pWellData == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_vWellData.count() == 0) {
|
|
|
int nIndexF = -1;
|
|
|
// 第一口井就选择界面上的流动段索引
|
|
|
// 流动段索引由窗口层上下文提供,数据层不直接访问窗口对象
|
|
|
if(pSubWndFitting != nullptr && pContextProvider != nullptr) {
|
|
|
if(pContextProvider->getCurrentSegmentIndex(pSubWndFitting, nIndexF)) {
|
|
|
// 设置当前流动段索引
|
|
|
pWellData->setIndexF(nIndexF);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Map 中增加真实井会改变井约束点和求解器井槽位,旧网格必须立即失效。
|
|
|
m_vWellData.append(pWellData);
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
emit dataChanged();
|
|
|
return pWellData;
|
|
|
}
|
|
|
|
|
|
// 实现 changeWellType 函数
|
|
|
nmDataWellBase* nmDataAnalyzeManager::changeWellType(nmDataWellBase* pOldWellData, NM_WELL_MODEL eTargetWellType)
|
|
|
{
|
|
|
if(pOldWellData == nullptr || !m_vWellData.contains(pOldWellData)) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
if(pOldWellData->getWellType() == eTargetWellType) {
|
|
|
return pOldWellData;
|
|
|
}
|
|
|
|
|
|
// 第一步:先在管理器之外构造目标井,失败时旧井及其分析方案关联保持不变。
|
|
|
nmDataWellBase* pNewWellData = nullptr;
|
|
|
if(eTargetWellType == NM_WELL_MODEL::Vertical_Well) {
|
|
|
pNewWellData = new nmDataVerticalWell;
|
|
|
} else if(eTargetWellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
|
|
|
pNewWellData = new nmDataVerticalFracturedWell;
|
|
|
} else if(eTargetWellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
|
|
|
pNewWellData = new nmDataHorizontalFracturedWell;
|
|
|
}
|
|
|
|
|
|
if(pNewWellData == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
// 第二步:复制井编码、名称、坐标、产量、压力及公共井参数,再恢复目标井型。
|
|
|
// 基类赋值会复制旧井型,因此必须在赋值后显式写回目标井型。
|
|
|
*pNewWellData = *pOldWellData;
|
|
|
pNewWellData->setWellType(eTargetWellType);
|
|
|
|
|
|
// 第三步:裂缝井依据复制后的井位和目标井默认裂缝参数重建几何。
|
|
|
nmDataVerticalFracturedWell* pVerticalFracturedWell =
|
|
|
dynamic_cast<nmDataVerticalFracturedWell*>(pNewWellData);
|
|
|
if(pVerticalFracturedWell != nullptr) {
|
|
|
pVerticalFracturedWell->setFracs();
|
|
|
}
|
|
|
nmDataHorizontalFracturedWell* pHorizontalFracturedWell =
|
|
|
dynamic_cast<nmDataHorizontalFracturedWell*>(pNewWellData);
|
|
|
if(pHorizontalFracturedWell != nullptr) {
|
|
|
pHorizontalFracturedWell->setFracs();
|
|
|
}
|
|
|
|
|
|
// 第四步:原位提交替换,WellCode 不变,因此主井、包含井和结果井关联自然保留。
|
|
|
if(!replaceWellData(pOldWellData, pNewWellData)) {
|
|
|
delete pNewWellData;
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
return pNewWellData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isPrimaryWell(
|
|
|
const nmDataWellBase* pWellData) const
|
|
|
{
|
|
|
if(pWellData == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sPrimaryWellCode =
|
|
|
m_oNumericalAnalysisCase.getPrimaryWellCode();
|
|
|
return !sPrimaryWellCode.isEmpty() &&
|
|
|
pWellData->getWellCode() == sPrimaryWellCode;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::canRemoveWell(
|
|
|
const nmDataWellBase* pWellData) const
|
|
|
{
|
|
|
if(pWellData == nullptr || isPrimaryWell(pWellData)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 不通过 WellCode 代替所有权判断,避免同编码外部对象误删管理器内的井。
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
if(m_vWellData[nIndex] == pWellData) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
|
|
|
{
|
|
|
// 固定主井及非法指针必须在任何版本递增、图元解绑和对象删除前拒绝。
|
|
|
// 该检查是数据层最终兜底,不能只依赖 Map 界面的提前拦截。
|
|
|
if(!canRemoveWell(pWellData)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString removedWellName = pWellData->getWellName();
|
|
|
const QString removedWellCode = pWellData->getWellCode();
|
|
|
const bool isCurrentWell = (m_pCurDataWell == pWellData);
|
|
|
|
|
|
// 第二步:Map 中删除任意真实井都会改变网格井集合。即使它是自动参与的
|
|
|
// 无产量观察井,也必须让旧网格、旧求解器顺序和旧结果一起失效。
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
|
|
|
// 第三步:删除井对象前,先从“包含其他井”中移除对应 WellCode。
|
|
|
QVector<nmCalculationWellRef> vecIncludedWells =
|
|
|
m_oNumericalAnalysisCase.getIncludedWells();
|
|
|
for(int nIndex = vecIncludedWells.size() - 1; nIndex >= 0; --nIndex) {
|
|
|
if(vecIncludedWells[nIndex].m_sWellCode == removedWellCode) {
|
|
|
vecIncludedWells.remove(nIndex);
|
|
|
}
|
|
|
}
|
|
|
m_oNumericalAnalysisCase.setIncludedWells(vecIncludedWells);
|
|
|
|
|
|
// 如果删的是当前井,切换到其他有效井;如果没有,则清空当前井指针。
|
|
|
if(isCurrentWell) {
|
|
|
m_pCurDataWell = nullptr;
|
|
|
for(int i = 0; i < m_vWellData.size(); ++i) {
|
|
|
nmDataWellBase* pCandidate = m_vWellData[i];
|
|
|
if(pCandidate != nullptr && pCandidate != pWellData) {
|
|
|
m_pCurDataWell = pCandidate;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString removedCode = pWellData->getWellCode();
|
|
|
QString removedName = pWellData->getWellName();
|
|
|
|
|
|
// 先从 registry 清理该井的所有属性,避免野指针
|
|
|
if (m_pAttrRegistry) {
|
|
|
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) {
|
|
|
if (!isWellParaOf(WELL_PARA_DESCS[i], pWellData->getWellType())) {
|
|
|
continue;
|
|
|
}
|
|
|
QString name = removedCode + "_" + WELL_PARA_DESCS[i].sName;
|
|
|
m_pAttrRegistry->unregAttr(name);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 倒序移除全部匹配项,兼容旧逻辑可能遗留的重复指针。
|
|
|
for (int i = m_vWellData.size() - 1; i >= 0; --i) {
|
|
|
if (m_vWellData[i] == pWellData) {
|
|
|
m_vWellData.remove(i);
|
|
|
}
|
|
|
}
|
|
|
delete pWellData;
|
|
|
pWellData = nullptr;
|
|
|
|
|
|
emit sigWellRemoved(removedCode, removedName);
|
|
|
emit dataChanged();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::removeWellDataAndPlot(nmDataWellBase* pWellData)
|
|
|
{
|
|
|
// 删除图元之前先执行同一业务预检,避免主井数据被拒绝后图元已经消失。
|
|
|
if(!canRemoveWell(pWellData)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider();
|
|
|
if (m_pNmGuiPlot != nullptr && pPlotContextProvider != nullptr)
|
|
|
{
|
|
|
// 移除所有井图元
|
|
|
pPlotContextProvider->removeWellPlotByData(m_pNmGuiPlot, pWellData);
|
|
|
removeWell(pWellData);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearAllWellData()
|
|
|
{
|
|
|
typedef QPair<QString, QString> WellIdentity;
|
|
|
QList<WellIdentity> removedWells;
|
|
|
|
|
|
// 全量清空表示当前分析上下文被丢弃;先发出快照清除通知,
|
|
|
// 此时实时井对象仍然有效,直接连接的 UI 刷新不会遇到悬空井指针。
|
|
|
clearPebiResultSnapshot();
|
|
|
|
|
|
// 遍历并删除所有井对象,兼容旧逻辑可能遗留的重复指针。
|
|
|
QSet<nmDataWellBase*> deletedWells;
|
|
|
foreach(nmDataWellBase* pWell, m_vWellData) {
|
|
|
if(pWell != nullptr && !deletedWells.contains(pWell)) {
|
|
|
deletedWells.insert(pWell);
|
|
|
removedWells.append(qMakePair(pWell->getWellCode(), pWell->getWellName()));
|
|
|
delete pWell;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 清空数组
|
|
|
m_vWellData.clear();
|
|
|
m_pCurDataWell = nullptr;
|
|
|
m_oNumericalAnalysisCase.clear();
|
|
|
|
|
|
// 清理 registry 中所有井相关属性
|
|
|
if (m_pAttrRegistry) {
|
|
|
foreach (const QString& name, m_pAttrRegistry->registeredNames()) {
|
|
|
if (isWellParaAttrName(name)) {
|
|
|
m_pAttrRegistry->unregAttr(name);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach (const WellIdentity& well, removedWells) {
|
|
|
emit sigWellRemoved(well.first, well.second);
|
|
|
}
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
|
|
|
QVector<nmDataWellBase*> nmDataAnalyzeManager::getWellDataList() const
|
|
|
{
|
|
|
return m_vWellData;
|
|
|
}
|
|
|
|
|
|
//QVector<nmDataWellBase> nmDataAnalyzeManager::getWellDataListCopy() const
|
|
|
//{
|
|
|
// QVector<nmDataWellBase> result;
|
|
|
// result.reserve(m_vWellData.size());
|
|
|
//
|
|
|
// foreach(const nmDataWellBase* well, m_vWellData) {
|
|
|
// if(well) {
|
|
|
// result.append(*well); // 调用拷贝构造函数
|
|
|
// }
|
|
|
// }
|
|
|
//
|
|
|
// return result;
|
|
|
//}
|
|
|
|
|
|
//void nmDataAnalyzeManager::updateWellData(const QVector<nmDataWellBase>& newData)
|
|
|
//{
|
|
|
//
|
|
|
// // 确保数量一致
|
|
|
// if (newData.size() != m_vWellData.size()) {
|
|
|
// return;
|
|
|
// }
|
|
|
//
|
|
|
// for (int i = 0; i < newData.size(); ++i) {
|
|
|
// nmDataWellBase* existingWell = m_vWellData[i];
|
|
|
// const nmDataWellBase& newWell = newData[i];
|
|
|
//
|
|
|
// if (existingWell && existingWell->getWellName() == newWell.getWellName()) {
|
|
|
// // 根据类型更新数据
|
|
|
// if (auto existingVFractured = dynamic_cast<nmDataVerticalFracturedWell*>(existingWell)) {
|
|
|
// if (auto newVFractured = dynamic_cast<const nmDataVerticalFracturedWell*>(&newWell)) {
|
|
|
// *existingVFractured = *newVFractured;
|
|
|
// }
|
|
|
// }
|
|
|
// else if (auto existingHFractured = dynamic_cast<nmDataHorizontalFracturedWell*>(existingWell)) {
|
|
|
// if (auto newHFractured = dynamic_cast<const nmDataHorizontalFracturedWell*>(&newWell)) {
|
|
|
// *existingHFractured = *newHFractured;
|
|
|
// }
|
|
|
// } else if (auto existingVertical = dynamic_cast<nmDataVerticalWell*>(existingWell)) {
|
|
|
// if (auto newVertical = dynamic_cast<const nmDataVerticalWell*>(&newWell)) {
|
|
|
// *existingVertical = *newVertical; // 调用赋值运算符
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
//
|
|
|
//}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateVerticalWells(const QVector<nmDataVerticalWell>& wells)
|
|
|
{
|
|
|
bool bWellUpdated = false;
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
const QString sWellInstanceId = newWell.getWellInstanceId();
|
|
|
if(sWellInstanceId.isEmpty()) {
|
|
|
qWarning() << "Cannot update vertical well without UUID:"
|
|
|
<< newWell.getWellName();
|
|
|
continue;
|
|
|
}
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataVerticalWell * >(existingWell)) {
|
|
|
// UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。
|
|
|
if(vWell->getWellInstanceId() == sWellInstanceId) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
bWellUpdated = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。
|
|
|
if(bWellUpdated) {
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateVerticalFracturedWells(const QVector<nmDataVerticalFracturedWell>& wells)
|
|
|
{
|
|
|
bool bWellUpdated = false;
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
const QString sWellInstanceId = newWell.getWellInstanceId();
|
|
|
if(sWellInstanceId.isEmpty()) {
|
|
|
qWarning() << "Cannot update vertical fractured well without UUID:"
|
|
|
<< newWell.getWellName();
|
|
|
continue;
|
|
|
}
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataVerticalFracturedWell * >(existingWell)) {
|
|
|
// UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。
|
|
|
if(vWell->getWellInstanceId() == sWellInstanceId) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
bWellUpdated = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。
|
|
|
if(bWellUpdated) {
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateHorizontalFracturedWells(const QVector<nmDataHorizontalFracturedWell>& wells)
|
|
|
{
|
|
|
bool bWellUpdated = false;
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
const QString sWellInstanceId = newWell.getWellInstanceId();
|
|
|
if(sWellInstanceId.isEmpty()) {
|
|
|
qWarning() << "Cannot update horizontal fractured well without UUID:"
|
|
|
<< newWell.getWellName();
|
|
|
continue;
|
|
|
}
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataHorizontalFracturedWell * >(existingWell)) {
|
|
|
// UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。
|
|
|
if(vWell->getWellInstanceId() == sWellInstanceId) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
bWellUpdated = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。
|
|
|
if(bWellUpdated) {
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取所有直井数据
|
|
|
QVector<nmDataVerticalWell*> nmDataAnalyzeManager::getVerticalWellData() const
|
|
|
{
|
|
|
QVector<nmDataVerticalWell*> verticalWells;
|
|
|
|
|
|
foreach(nmDataWellBase* well, m_vWellData) {
|
|
|
nmDataVerticalWell* vWell = dynamic_cast<nmDataVerticalWell*>(well);
|
|
|
|
|
|
if(vWell && !dynamic_cast<nmDataVerticalFracturedWell * >(well)) {
|
|
|
// 确保不是垂直裂缝井
|
|
|
verticalWells.append(vWell);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return verticalWells;
|
|
|
}
|
|
|
|
|
|
// 获取所有垂直裂缝井数据
|
|
|
QVector<nmDataVerticalFracturedWell*> nmDataAnalyzeManager::getVerticalFracturedWellData() const
|
|
|
{
|
|
|
QVector<nmDataVerticalFracturedWell*> vFracturedWells;
|
|
|
|
|
|
foreach(nmDataWellBase* well, m_vWellData) {
|
|
|
nmDataVerticalFracturedWell* vFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(well);
|
|
|
|
|
|
if(vFracturedWell) {
|
|
|
vFracturedWells.append(vFracturedWell);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vFracturedWells;
|
|
|
}
|
|
|
|
|
|
// 获取所有多段压裂水平井数据
|
|
|
QVector<nmDataHorizontalFracturedWell*> nmDataAnalyzeManager::getHorizontalFracturedWellData() const
|
|
|
{
|
|
|
QVector<nmDataHorizontalFracturedWell*> hFracturedWells;
|
|
|
|
|
|
foreach(nmDataWellBase* well, m_vWellData) {
|
|
|
nmDataHorizontalFracturedWell* hFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(well);
|
|
|
|
|
|
if(hFracturedWell) {
|
|
|
hFracturedWells.append(hFracturedWell);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return hFracturedWells;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* nmDataAnalyzeManager::findWellByName(QString wellName) const
|
|
|
{
|
|
|
foreach(nmDataWellBase* pWell, m_vWellData) {
|
|
|
if(pWell && pWell->getWellName() == wellName) {
|
|
|
return pWell; // 找到匹配的井,返回指针
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return nullptr; // 未找到匹配的井,返回 nullptr
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* nmDataAnalyzeManager::findWellByCode(
|
|
|
const QString& sWellCode) const
|
|
|
{
|
|
|
if(sWellCode.isEmpty()) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
foreach(nmDataWellBase* pWell, m_vWellData) {
|
|
|
if(pWell != nullptr && pWell->getWellCode() == sWellCode) {
|
|
|
return pWell;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* nmDataAnalyzeManager::findWellByInstanceId(
|
|
|
const QString& sWellInstanceId) const
|
|
|
{
|
|
|
if(sWellInstanceId.isEmpty()) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
foreach(nmDataWellBase* pWell, m_vWellData) {
|
|
|
if(pWell != nullptr &&
|
|
|
pWell->getWellInstanceId() == sWellInstanceId) {
|
|
|
return pWell;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::initCurWellData()
|
|
|
{
|
|
|
// 获取当前默认井数据
|
|
|
ZxDataWell* pWellData = zxCurWell;
|
|
|
if(pWellData == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 判断是哪一种井类型,初始化对应的参数
|
|
|
QString wellClass = pWellData->getWellClassEn();
|
|
|
|
|
|
// 获取当前井的压力、流量数据
|
|
|
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
|
|
|
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
|
|
|
|
|
|
ZxDataGaugeP* pGaugeP = nullptr;
|
|
|
ZxDataGaugeF* pGaugeF = nullptr;
|
|
|
|
|
|
// 遍历压力数据列表
|
|
|
for(int i = 0; i < m_listGaugeP.size(); ++i) {
|
|
|
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历流量数据列表
|
|
|
for(int i = 0; i < m_listGaugeF.size(); ++i) {
|
|
|
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取的压力、流量数据
|
|
|
QVector<QPointF> vecPtsP, vecPtsF;
|
|
|
// 临时存储x,y坐标
|
|
|
VecDouble vecX, vecY;
|
|
|
|
|
|
if(pGaugeP != nullptr) {
|
|
|
// 获取压力数据
|
|
|
if(pGaugeP->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsP.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(pGaugeF != nullptr) {
|
|
|
vecX.clear();
|
|
|
vecY.clear();
|
|
|
|
|
|
// 获取流量数据
|
|
|
if(pGaugeF->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsF.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) {
|
|
|
// 初始化直井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Vertical_Well);
|
|
|
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pWell);
|
|
|
if(pVerticalWell == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
pVerticalWell->setWellName(pWellData->getName());
|
|
|
pVerticalWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pVerticalWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pVerticalWell->setX(tempAttr);
|
|
|
tempAttr = pVerticalWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pVerticalWell->setY(tempAttr);
|
|
|
tempAttr = pVerticalWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pVerticalWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pVerticalWell->setPressurePoints(vecPtsP);
|
|
|
pVerticalWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
QVector<QVector<double>> vvecHistoryPressureData; //压力历史数据
|
|
|
QVector<QVector<double>> vvecHistoryLogData; // 历史双对数曲线数据
|
|
|
QVector<QVector<double>> vvecHistorySemiLogData; // 历史半对数曲线数据
|
|
|
|
|
|
this->calculationLogData(pVerticalWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData);
|
|
|
|
|
|
// 使用setter方法存储历史数据到井对象中
|
|
|
pVerticalWell->setHistoryPressure(vvecHistoryPressureData);
|
|
|
pVerticalWell->setHistoryLogLog(vvecHistoryLogData);
|
|
|
pVerticalWell->setHistorySemiLog(vvecHistorySemiLogData);
|
|
|
|
|
|
// 设置为当前查看的井
|
|
|
this->setCurWellData(pVerticalWell);
|
|
|
} else if(ZxBaseUtil::isSameStr(wellClass, "VerticalFracturedWell")) {
|
|
|
|
|
|
// 初始化垂直裂缝井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Vertical_Fractured_Well);
|
|
|
nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
|
|
|
if(pVFracturedWell == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
pVFracturedWell->setWellName(pWellData->getName());
|
|
|
pVFracturedWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pVFracturedWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pVFracturedWell->setX(tempAttr);
|
|
|
tempAttr = pVFracturedWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pVFracturedWell->setY(tempAttr);
|
|
|
tempAttr = pVFracturedWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pVFracturedWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pVFracturedWell->setPressurePoints(vecPtsP);
|
|
|
pVFracturedWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
QVector<QVector<double>> vvecHistoryPressureData; //压力历史数据
|
|
|
QVector<QVector<double>> vvecHistoryLogData; // 历史双对数曲线数据
|
|
|
QVector<QVector<double>> vvecHistorySemiLogData; // 历史半对数曲线数据
|
|
|
|
|
|
this->calculationLogData(pVFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData);
|
|
|
|
|
|
// 使用setter方法存储历史数据到井对象中
|
|
|
pVFracturedWell->setHistoryPressure(vvecHistoryPressureData);
|
|
|
pVFracturedWell->setHistoryLogLog(vvecHistoryLogData);
|
|
|
pVFracturedWell->setHistorySemiLog(vvecHistorySemiLogData);
|
|
|
|
|
|
// 更新裂缝位置信息
|
|
|
pVFracturedWell->setFracs();
|
|
|
|
|
|
// 设置为当前查看的井
|
|
|
this->setCurWellData(pVFracturedWell);
|
|
|
} else if(ZxBaseUtil::isSameStr(wellClass, "HorizontalMultiFracturedWell")) {
|
|
|
|
|
|
// 初始化多段压裂水平井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Horizontal_Fractured_Well);
|
|
|
nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
|
|
|
if(pHFracturedWell == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
pHFracturedWell->setWellName(pWellData->getName());
|
|
|
pHFracturedWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pHFracturedWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pHFracturedWell->setX(tempAttr);
|
|
|
tempAttr = pHFracturedWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pHFracturedWell->setY(tempAttr);
|
|
|
tempAttr = pHFracturedWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pHFracturedWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pHFracturedWell->setPressurePoints(vecPtsP);
|
|
|
pHFracturedWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
QVector<QVector<double>> vvecHistoryPressureData; //压力历史数据
|
|
|
QVector<QVector<double>> vvecHistoryLogData; // 历史双对数曲线数据
|
|
|
QVector<QVector<double>> vvecHistorySemiLogData; // 历史半对数曲线数据
|
|
|
|
|
|
this->calculationLogData(pHFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData);
|
|
|
|
|
|
//使用setter方法存储历史数据到井对象中
|
|
|
pHFracturedWell->setHistoryPressure(vvecHistoryPressureData);
|
|
|
pHFracturedWell->setHistoryLogLog(vvecHistoryLogData);
|
|
|
pHFracturedWell->setHistorySemiLog(vvecHistorySemiLogData);
|
|
|
|
|
|
// 计算裂缝数据
|
|
|
pHFracturedWell->setFracs();
|
|
|
|
|
|
// 设置为当前查看的井
|
|
|
this->setCurWellData(pHFracturedWell);
|
|
|
}
|
|
|
|
|
|
// 第一次导入的当前流动段井固定为数值分析入口井。后续切换查看井或
|
|
|
// 重复初始化不得改变主井身份及其求解角色。
|
|
|
if(m_oNumericalAnalysisCase.getPrimaryWellCode().isEmpty() &&
|
|
|
m_pCurDataWell != nullptr &&
|
|
|
!m_pCurDataWell->getWellCode().isEmpty()) {
|
|
|
const QString sPrimaryWellCode = m_pCurDataWell->getWellCode();
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellCode(sPrimaryWellCode);
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellMode(
|
|
|
m_pCurDataWell->getFlowPoints().size() >= 2
|
|
|
? NM_CaseWell_RateControlled
|
|
|
: NM_CaseWell_Observation);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::calculationLogData(
|
|
|
nmDataWellBase* pWellData,
|
|
|
QVector<QVector<double>>& vvecHistoryData,
|
|
|
QVector<QVector<double>>& vvecLogPreData,
|
|
|
QVector<QVector<double>>& vvecSemiLogPreData)
|
|
|
{
|
|
|
// 清空输出参数
|
|
|
vvecHistoryData.clear();
|
|
|
vvecLogPreData.clear();
|
|
|
vvecSemiLogPreData.clear();
|
|
|
|
|
|
if(pWellData == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 初始化二维数组结构
|
|
|
vvecHistoryData.resize(2); // [0]=x, [1]=y
|
|
|
vvecLogPreData.resize(3); // [0]=x, [1]=y, [2]=z
|
|
|
vvecSemiLogPreData.resize(2); // [0]=x, [1]=pointData[0]
|
|
|
|
|
|
// 准备压力数据
|
|
|
QVector<QPointF> vecPressure = pWellData->getPressurePoints();
|
|
|
std::vector<Point> wellPressureData;
|
|
|
|
|
|
// 填充 wellPressureData 和 vvecHistoryData
|
|
|
foreach(const QPointF& qpoint, vecPressure) {
|
|
|
// wellPressureData
|
|
|
Point pt;
|
|
|
pt.x = qpoint.x();
|
|
|
pt.y = qpoint.y();
|
|
|
pt.z = 0.0;
|
|
|
wellPressureData.push_back(pt);
|
|
|
|
|
|
// vvecHistoryData
|
|
|
vvecHistoryData[0].append(qpoint.x()); // x
|
|
|
vvecHistoryData[1].append(qpoint.y()); // y
|
|
|
}
|
|
|
|
|
|
// Prepare flow-rate segment data.
|
|
|
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
|
|
|
if(vecPressure.isEmpty() || vecTimeQ.size() < 2) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
int nTimeNumQ = vecTimeQ.size() - 1;
|
|
|
if (nTimeNumQ <= 0) {
|
|
|
// 没有流量段数据,无法计算双对数/半对数曲线
|
|
|
return;
|
|
|
}
|
|
|
std::vector<double> timeQ(nTimeNumQ);
|
|
|
std::vector<double> q(nTimeNumQ);
|
|
|
|
|
|
for(int i = 0; i < nTimeNumQ; ++i) {
|
|
|
timeQ[i] = vecTimeQ[i + 1].x();
|
|
|
q[i] = vecTimeQ[i + 1].y();
|
|
|
}
|
|
|
|
|
|
// 调用 DLL 计算双对数曲线
|
|
|
std::vector<Point> logPre;
|
|
|
int iSectionFlowIndex = pWellData->getIndexF();
|
|
|
|
|
|
HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll");
|
|
|
|
|
|
if(hMod_solver) {
|
|
|
typedef bool (*PreLog)(const std::vector<Point>&, const int&, double*, double*, int, std::vector<Point>&);
|
|
|
PreLog preLogFun = (PreLog)GetProcAddress(hMod_solver, "logLogPre");
|
|
|
|
|
|
if(nullptr == preLogFun) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
std::cout << "preLogFun failed!\n";
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
bool bCalculated = preLogFun(wellPressureData, iSectionFlowIndex,
|
|
|
timeQ.data(), q.data(), nTimeNumQ, logPre);
|
|
|
if(!bCalculated || logPre.empty()) {
|
|
|
FreeLibrary(hMod_solver);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// The solver's final point is not part of the plotted result.
|
|
|
for(std::vector<Point>::size_type i = 0; i + 1 < logPre.size(); ++i) {
|
|
|
//logFile << logPre[i].x << "\t" << logPre[i].y << "\t" << logPre[i].z << "\t" << std::endl;
|
|
|
vvecLogPreData[0].append(logPre[i].x); // x
|
|
|
vvecLogPreData[1].append(logPre[i].y); // y
|
|
|
vvecLogPreData[2].append(logPre[i].z); // z
|
|
|
}
|
|
|
|
|
|
// 填充半对数曲线数据 (x, pointData[0])
|
|
|
foreach(const auto& point, logPre) {
|
|
|
vvecSemiLogPreData[0].append(point.x); // x
|
|
|
double y_value = point.pointData.empty() ? 0.0 : point.pointData[0];
|
|
|
vvecSemiLogPreData[1].append(y_value); // pointData[0] 或默认值
|
|
|
}
|
|
|
|
|
|
FreeLibrary(hMod_solver);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
|
|
|
{
|
|
|
if(pWellData == nullptr || pWellData->getCode().isEmpty()) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
// 第一步:同一 WellCode 只创建一个数值井;重复选择时直接返回已有对象。
|
|
|
nmDataWellBase* pExistingWell = findWellByCode(pWellData->getCode());
|
|
|
if(pExistingWell != nullptr) {
|
|
|
return pExistingWell;
|
|
|
}
|
|
|
|
|
|
// 判断是哪一种井类型,初始化对应的参数
|
|
|
QString sWellClass = pWellData->getWellClassEn();
|
|
|
|
|
|
// 获取当前井的压力、流量数据
|
|
|
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
|
|
|
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
|
|
|
|
|
|
ZxDataGaugeP* pGaugeP = nullptr;
|
|
|
ZxDataGaugeF* pGaugeF = nullptr;
|
|
|
|
|
|
// 遍历压力数据列表
|
|
|
for(int i = 0; i < m_listGaugeP.size(); ++i) {
|
|
|
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历流量数据列表
|
|
|
for(int i = 0; i < m_listGaugeF.size(); ++i) {
|
|
|
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取的压力、流量数据
|
|
|
QVector<QPointF> vecPtsP, vecPtsF;
|
|
|
// 临时存储x,y坐标
|
|
|
VecDouble vecX, vecY;
|
|
|
|
|
|
if(pGaugeP != nullptr) {
|
|
|
// 获取压力数据
|
|
|
if(pGaugeP->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsP.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(pGaugeF != nullptr) {
|
|
|
vecX.clear();
|
|
|
vecY.clear();
|
|
|
|
|
|
// 获取流量数据
|
|
|
if(pGaugeF->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsF.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int nIndexF; //当前井的流动段索引
|
|
|
|
|
|
// 更新当前井的流动段的索引,默认为最后一段
|
|
|
nIndexF = vecPtsF.count() - 1;
|
|
|
|
|
|
nmDataWellBase* pAddedWell = nullptr;
|
|
|
if(ZxBaseUtil::isSameStr(sWellClass, "VerticalWell")) {
|
|
|
// 初始化直井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Vertical_Well);
|
|
|
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pWell);
|
|
|
if(pVerticalWell == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
pAddedWell = pVerticalWell;
|
|
|
|
|
|
pVerticalWell->setWellName(pWellData->getName());
|
|
|
pVerticalWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pVerticalWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pVerticalWell->setX(tempAttr);
|
|
|
tempAttr = pVerticalWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pVerticalWell->setY(tempAttr);
|
|
|
tempAttr = pVerticalWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pVerticalWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pVerticalWell->setPressurePoints(vecPtsP);
|
|
|
pVerticalWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
// 设置流量段索引
|
|
|
pVerticalWell->setIndexF(nIndexF);
|
|
|
} else if(ZxBaseUtil::isSameStr(sWellClass, "VerticalFracturedWell")) {
|
|
|
|
|
|
// 初始化垂直裂缝井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Vertical_Fractured_Well);
|
|
|
nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
|
|
|
if(pVFracturedWell == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
pAddedWell = pVFracturedWell;
|
|
|
|
|
|
pVFracturedWell->setWellName(pWellData->getName());
|
|
|
pVFracturedWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pVFracturedWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pVFracturedWell->setX(tempAttr);
|
|
|
tempAttr = pVFracturedWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pVFracturedWell->setY(tempAttr);
|
|
|
tempAttr = pVFracturedWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pVFracturedWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pVFracturedWell->setPressurePoints(vecPtsP);
|
|
|
pVFracturedWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
// 更新裂缝位置信息
|
|
|
pVFracturedWell->setFracs();
|
|
|
// 设置流量段索引
|
|
|
pVFracturedWell->setIndexF(nIndexF);
|
|
|
} else if(ZxBaseUtil::isSameStr(sWellClass, "HorizontalMultiFracturedWell")) {
|
|
|
|
|
|
// 初始化多段压裂水平井默认参数
|
|
|
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Horizontal_Fractured_Well);
|
|
|
nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
|
|
|
if(pHFracturedWell == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
pAddedWell = pHFracturedWell;
|
|
|
|
|
|
pHFracturedWell->setWellName(pWellData->getName());
|
|
|
pHFracturedWell->setWellCode(pWellData->getCode());
|
|
|
nmDataAttribute tempAttr = pHFracturedWell->getX();
|
|
|
tempAttr.setValue(pWellData->getLocationX());
|
|
|
pHFracturedWell->setX(tempAttr);
|
|
|
tempAttr = pHFracturedWell->getY();
|
|
|
tempAttr.setValue(pWellData->getLocationY());
|
|
|
pHFracturedWell->setY(tempAttr);
|
|
|
tempAttr = pHFracturedWell->getRadius();
|
|
|
tempAttr.setValue(pWellData->getWellRadius());
|
|
|
pHFracturedWell->setRadius(tempAttr);
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pHFracturedWell->setPressurePoints(vecPtsP);
|
|
|
pHFracturedWell->setFlowPoints(vecPtsF);
|
|
|
|
|
|
// 计算裂缝数据
|
|
|
pHFracturedWell->setFracs();
|
|
|
|
|
|
// 设置流量段索引
|
|
|
pHFracturedWell->setIndexF(nIndexF);
|
|
|
}
|
|
|
|
|
|
// 第二步:不支持的项目井型不进入 Map,也不发送伪造的新增井通知。
|
|
|
if(pAddedWell == nullptr) {
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
// 第三步:井数据完整建立后,再通知 Map 和参数面板新增井分组。
|
|
|
{
|
|
|
syncWellAttrs();
|
|
|
QString code = pWellData->getCode();
|
|
|
QString name = pWellData->getName();
|
|
|
QStringList paras;
|
|
|
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) {
|
|
|
if (isWellParaOf(WELL_PARA_DESCS[i], pAddedWell->getWellType())) {
|
|
|
paras << WELL_PARA_DESCS[i].sName;
|
|
|
}
|
|
|
}
|
|
|
emit sigWellAdded(code, name, paras);
|
|
|
}
|
|
|
|
|
|
return pAddedWell;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData)
|
|
|
{
|
|
|
if (pWellData == nullptr)
|
|
|
return;
|
|
|
bool bWellAdded = false;
|
|
|
if (!m_vWellData.contains(pWellData))
|
|
|
{
|
|
|
m_vWellData.append(pWellData);
|
|
|
bWellAdded = true;
|
|
|
}
|
|
|
|
|
|
// 外部先构造、再加入 Map 的井没有经过 createWell(),需要在这里统一
|
|
|
// 使旧网格失效。已由 createWell() 登记的井不重复增加输入版本号。
|
|
|
if(bWellAdded)
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
}
|
|
|
|
|
|
// 通知面板新增井分组
|
|
|
{
|
|
|
syncWellAttrs();
|
|
|
QString code = pWellData->getWellCode();
|
|
|
QString name = pWellData->getWellName();
|
|
|
QStringList paras;
|
|
|
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) {
|
|
|
if (isWellParaOf(WELL_PARA_DESCS[i], pWellData->getWellType())) {
|
|
|
paras << WELL_PARA_DESCS[i].sName;
|
|
|
}
|
|
|
}
|
|
|
emit sigWellAdded(code, name, paras);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell,
|
|
|
nmDataWellBase* pNewWell)
|
|
|
{
|
|
|
// 先完成所有无副作用的前置校验,失败时保持双方所有权不变。
|
|
|
if (pOldWell == nullptr || pNewWell == nullptr ||
|
|
|
pOldWell == pNewWell || m_vWellData.contains(pNewWell) ||
|
|
|
pOldWell->getWellCode().isEmpty() ||
|
|
|
pOldWell->getWellCode() != pNewWell->getWellCode() ||
|
|
|
pOldWell->getWellInstanceId().isEmpty() ||
|
|
|
pOldWell->getWellInstanceId() !=
|
|
|
pNewWell->getWellInstanceId())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int nOldIndex = m_vWellData.indexOf(pOldWell);
|
|
|
if (nOldIndex < 0)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 删除旧对象前保存观察者通知所需信息,后续不得再解引用旧指针。
|
|
|
QString sOldCode = pOldWell->getWellCode();
|
|
|
QString sOldName = pOldWell->getWellName();
|
|
|
bool bWasCurrentWell = m_pCurDataWell == pOldWell;
|
|
|
|
|
|
// 第一步:在原位置替换全部旧指针;同一 WellCode 的分析方案关联无需迁移。
|
|
|
for (int nIndex = m_vWellData.size() - 1; nIndex >= 0; --nIndex)
|
|
|
{
|
|
|
if (m_vWellData[nIndex] == pOldWell)
|
|
|
{
|
|
|
m_vWellData.remove(nIndex);
|
|
|
}
|
|
|
}
|
|
|
m_vWellData.insert(qMin(nOldIndex, m_vWellData.size()), pNewWell);
|
|
|
// 井型改变会改变 PEBI 几何输入,旧求解器顺序和网格必须整体失效。
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
if (bWasCurrentWell)
|
|
|
{
|
|
|
m_pCurDataWell = pNewWell;
|
|
|
}
|
|
|
|
|
|
// 第二步:先让注册表指向新对象,再销毁旧对象并统一通知观察者。
|
|
|
syncWellAttrs();
|
|
|
delete pOldWell;
|
|
|
// 同 UUID 井型替换后,新对象必须重新获得最后结果的弱引用。
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
|
|
|
QStringList listParameters;
|
|
|
for (int nIndex = 0; nIndex < WELL_PARA_DESC_COUNT; ++nIndex)
|
|
|
{
|
|
|
if (isWellParaOf(WELL_PARA_DESCS[nIndex],
|
|
|
pNewWell->getWellType()))
|
|
|
{
|
|
|
listParameters << WELL_PARA_DESCS[nIndex].sName;
|
|
|
}
|
|
|
}
|
|
|
// 沿用既有移除和添加信号,保证工程树及属性页无需感知替换细节。
|
|
|
emit sigWellRemoved(sOldCode, sOldName);
|
|
|
emit sigWellAdded(pNewWell->getWellCode(),
|
|
|
pNewWell->getWellName(),
|
|
|
listParameters);
|
|
|
emit dataChanged();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/// @brief 创建油藏数据对象,从界面读取PVT单值和分层数据,构建reservoir对象
|
|
|
void nmDataAnalyzeManager::createReservoir()
|
|
|
{
|
|
|
// 1. 获取上下文
|
|
|
iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
|
|
|
nmDataAnalyzeContextProvider* pCtx = nmDataAnalyzeContext::provider();
|
|
|
Q_ASSERT(nullptr != pSubWndFitting);
|
|
|
Q_ASSERT(nullptr != pCtx);
|
|
|
|
|
|
PvtFluidType eType = WFT_Null;
|
|
|
pCtx->getBasicPft(pSubWndFitting, eType);
|
|
|
|
|
|
// 2. 读取PVT单值参数
|
|
|
QMap<QString, double> mapPvtValues = readPvtSingleValues(pCtx, pSubWndFitting, eType);
|
|
|
|
|
|
// 3. 读取分层数据,提取储层基础参数
|
|
|
VVecVariant vvecLayerData;
|
|
|
pCtx->getBasicDataLayers(pSubWndFitting, vvecLayerData);
|
|
|
|
|
|
double dThickness = 10;
|
|
|
double dPorosity = 0.5;
|
|
|
double dCf = 1.0;
|
|
|
double dInitialPre = 30.0;
|
|
|
if(!vvecLayerData.isEmpty() && vvecLayerData[0].size() >= 5) {
|
|
|
dThickness = vvecLayerData[0][1].toDouble();
|
|
|
dPorosity = vvecLayerData[0][2].toDouble();
|
|
|
dCf = vvecLayerData[0][3].toDouble();
|
|
|
dInitialPre = vvecLayerData[0][4].toDouble();
|
|
|
}
|
|
|
|
|
|
// 4. 构建油藏对象
|
|
|
if(m_reservoirData != nullptr) {
|
|
|
delete m_reservoirData;
|
|
|
m_reservoirData = nullptr;
|
|
|
}
|
|
|
m_reservoirData = new nmDataReservoir;
|
|
|
|
|
|
// 注册储层属性到 nmAttrRegistry(name 对齐 XML ParaItem Name)
|
|
|
m_pAttrRegistry->clear();
|
|
|
m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness());
|
|
|
m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure());
|
|
|
m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability());
|
|
|
m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity());
|
|
|
m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt());
|
|
|
m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf());
|
|
|
m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi());
|
|
|
m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi());
|
|
|
|
|
|
// 按相态填充PVT字段和压缩系数
|
|
|
populateReservoirByPhase(m_reservoirData, eType, mapPvtValues, dCf);
|
|
|
|
|
|
// 设置基础属性
|
|
|
setReservoirAttr(m_reservoirData->getInitialPressure(), dInitialPre);
|
|
|
setReservoirAttr(m_reservoirData->getReservoirType(), QString("Homogeneous"));
|
|
|
setReservoirAttr(m_reservoirData->getThickness(), dThickness);
|
|
|
setReservoirAttr(m_reservoirData->getPorosity(), dPorosity);
|
|
|
|
|
|
// 5. 创建默认分层
|
|
|
createDefaultLayer(dThickness, m_vecLayers);
|
|
|
}
|
|
|
|
|
|
nmDataAxis* nmDataAnalyzeManager::getAxisData() const
|
|
|
{
|
|
|
return m_axisData;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setAxisData(nmDataAxis* pAxisData)
|
|
|
{
|
|
|
if(pAxisData != nullptr) {
|
|
|
m_axisData = pAxisData;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataReservoir* nmDataAnalyzeManager::getReservoirData() const
|
|
|
{
|
|
|
return m_reservoirData;
|
|
|
}
|
|
|
|
|
|
nmAttrRegistry* nmDataAnalyzeManager::getAttrRegistry() const
|
|
|
{
|
|
|
return m_pAttrRegistry;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::syncWellAttrs()
|
|
|
{
|
|
|
if (m_pAttrRegistry == NULL) return;
|
|
|
|
|
|
QSet<QString> activeAttrNames;
|
|
|
QSet<QString> activeWellCodes;
|
|
|
foreach (nmDataWellBase* pWell, m_vWellData)
|
|
|
{
|
|
|
if (pWell == nullptr) continue;
|
|
|
|
|
|
QString code = pWell->getWellCode();
|
|
|
if (code.isEmpty() || activeWellCodes.contains(code)) continue;
|
|
|
activeWellCodes.insert(code);
|
|
|
|
|
|
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i)
|
|
|
{
|
|
|
if (!isWellParaOf(WELL_PARA_DESCS[i], pWell->getWellType())) {
|
|
|
continue;
|
|
|
}
|
|
|
nmDataAttribute* pAttr = WELL_PARA_DESCS[i].getAttr(*pWell);
|
|
|
if (pAttr != NULL)
|
|
|
{
|
|
|
QString name = code + "_" + WELL_PARA_DESCS[i].sName;
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pAttr);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 精确清理已经不存在的井参数注册,兼容 wellCode 自身包含下划线。
|
|
|
foreach (const QString& name, m_pAttrRegistry->registeredNames())
|
|
|
{
|
|
|
if (isWellParaAttrName(name) && !activeAttrNames.contains(name))
|
|
|
{
|
|
|
m_pAttrRegistry->unregAttr(name);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::syncGeometryAttrs()
|
|
|
{
|
|
|
if (m_pAttrRegistry == NULL) return;
|
|
|
|
|
|
// 沿用井参数注册方式,以“对象编码 + XML 参数名”建立双向绑定。
|
|
|
QSet<QString> activeAttrNames;
|
|
|
int fractureIndex = 0;
|
|
|
foreach (nmDataFracture* pFracture, m_vFractureData)
|
|
|
{
|
|
|
// DFN 裂缝由专用功能批量管理,不作为独立裂缝显示在参数面板中。
|
|
|
if (pFracture == nullptr || pFracture->getFractureType().getValue().toString() == "DFN") {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
QString code = QString("FRAC%1").arg(++fractureIndex, 4, 10, QChar('0'));
|
|
|
for (int i = 0; i < GEOMETRY_PARA_DESC_COUNT; ++i)
|
|
|
{
|
|
|
if (GEOMETRY_PARA_DESCS[i].getFractureAttr == NULL) continue;
|
|
|
nmDataAttribute* pAttr = GEOMETRY_PARA_DESCS[i].getFractureAttr(*pFracture);
|
|
|
if (pAttr != NULL)
|
|
|
{
|
|
|
QString name = code + "_" + GEOMETRY_PARA_DESCS[i].sName;
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pAttr);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (int faultIndex = 0; faultIndex < m_vFaultData.size(); ++faultIndex)
|
|
|
{
|
|
|
nmDataFault* pFault = m_vFaultData[faultIndex];
|
|
|
if (pFault == nullptr) continue;
|
|
|
|
|
|
QString code = QString("FAULT%1").arg(faultIndex + 1, 4, 10, QChar('0'));
|
|
|
for (int i = 0; i < GEOMETRY_PARA_DESC_COUNT; ++i)
|
|
|
{
|
|
|
if (GEOMETRY_PARA_DESCS[i].getFaultAttr == NULL) continue;
|
|
|
nmDataAttribute* pAttr = GEOMETRY_PARA_DESCS[i].getFaultAttr(*pFault);
|
|
|
if (pAttr != NULL)
|
|
|
{
|
|
|
QString name = code + "_" + GEOMETRY_PARA_DESCS[i].sName;
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pAttr);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (int regionIndex = 0; regionIndex < m_vRegionData.size(); ++regionIndex)
|
|
|
{
|
|
|
nmDataRegion* pRegion = m_vRegionData[regionIndex];
|
|
|
if (pRegion == nullptr) continue;
|
|
|
|
|
|
QString code = QString("REGION%1").arg(regionIndex + 1, 4, 10, QChar('0'));
|
|
|
for (int i = 0; i < GEOMETRY_PARA_DESC_COUNT; ++i)
|
|
|
{
|
|
|
if (GEOMETRY_PARA_DESCS[i].getRegionAttr == NULL) continue;
|
|
|
nmDataAttribute* pAttr = GEOMETRY_PARA_DESCS[i].getRegionAttr(*pRegion);
|
|
|
if (pAttr != NULL)
|
|
|
{
|
|
|
QString name = code + "_" + GEOMETRY_PARA_DESCS[i].sName;
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pAttr);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (int markIndex = 0; markIndex < m_vRegionMarkData.size(); ++markIndex)
|
|
|
{
|
|
|
nmDataRegionMark* pMark = m_vRegionMarkData[markIndex];
|
|
|
if (pMark == nullptr) continue;
|
|
|
|
|
|
QString code = QString("RMARK%1").arg(markIndex + 1, 4, 10, QChar('0'));
|
|
|
for (int i = 0; i < GEOMETRY_PARA_DESC_COUNT; ++i)
|
|
|
{
|
|
|
if (GEOMETRY_PARA_DESCS[i].getRegionMarkAttr == NULL) continue;
|
|
|
nmDataAttribute* pAttr = GEOMETRY_PARA_DESCS[i].getRegionMarkAttr(*pMark);
|
|
|
if (pAttr != NULL)
|
|
|
{
|
|
|
QString name = code + "_" + GEOMETRY_PARA_DESCS[i].sName;
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pAttr);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (m_outlineData != nullptr)
|
|
|
{
|
|
|
if (m_outlineData->getOutlineType() == NM_Rect_Outline_Type)
|
|
|
{
|
|
|
const QString code = "BOUNDARY";
|
|
|
const QStringList names = QStringList() << "BR_XMin" << "BR_YMin" << "BR_XMax" << "BR_YMax";
|
|
|
nmDataAttribute* attrs[] = {
|
|
|
&m_outlineData->getLeftAttribute(),
|
|
|
&m_outlineData->getBottomAttribute(),
|
|
|
&m_outlineData->getRightAttribute(),
|
|
|
&m_outlineData->getTopAttribute()
|
|
|
};
|
|
|
for (int i = 0; i < names.count(); ++i) {
|
|
|
const QString name = code + "_" + names[i];
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, attrs[i]);
|
|
|
}
|
|
|
}
|
|
|
else if (m_outlineData->getOutlineType() == NM_Round_Outline_Type)
|
|
|
{
|
|
|
const QString code = "BOUNDARY";
|
|
|
const QStringList names = QStringList() << "BC_CenterX" << "BC_CenterY" << "BC_Radius";
|
|
|
nmDataAttribute* attrs[] = {
|
|
|
&m_outlineData->getCenterXAttribute(),
|
|
|
&m_outlineData->getCenterYAttribute(),
|
|
|
&m_outlineData->getRadiusAttribute()
|
|
|
};
|
|
|
for (int i = 0; i < names.count(); ++i) {
|
|
|
const QString name = code + "_" + names[i];
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, attrs[i]);
|
|
|
}
|
|
|
}
|
|
|
else if (m_outlineData->getOutlineType() == NM_Polygon_Outline_Type)
|
|
|
{
|
|
|
// 多边形顶点数量动态变化,每个 Point 使用独立对象编码注册 X/Y。
|
|
|
for (int pointIndex = 0; pointIndex < m_outlineData->getOutlinePointCount(); ++pointIndex)
|
|
|
{
|
|
|
const QString code = QString("BVERT%1").arg(pointIndex + 1, 4, 10, QChar('0'));
|
|
|
nmDataAttribute* pX = m_outlineData->getPointXAttribute(pointIndex);
|
|
|
nmDataAttribute* pY = m_outlineData->getPointYAttribute(pointIndex);
|
|
|
if (pX != nullptr)
|
|
|
{
|
|
|
const QString name = code + "_BP_X";
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pX);
|
|
|
}
|
|
|
if (pY != nullptr)
|
|
|
{
|
|
|
const QString name = code + "_BP_Y";
|
|
|
activeAttrNames.insert(name);
|
|
|
m_pAttrRegistry->regAttr(name, pY);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 精确清理已删除对象、已删除顶点和旧边界类型留下的注册项。
|
|
|
foreach (const QString& name, m_pAttrRegistry->registeredNames())
|
|
|
{
|
|
|
if (isGeometryParaAttrName(name) && !activeAttrNames.contains(name))
|
|
|
{
|
|
|
m_pAttrRegistry->unregAttr(name);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataReservoir nmDataAnalyzeManager::getReservoirDataCopy() const
|
|
|
{
|
|
|
if(m_reservoirData) {
|
|
|
return *m_reservoirData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
return nmDataReservoir(); // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateReservoirData(const nmDataReservoir& newData)
|
|
|
{
|
|
|
if(m_reservoirData) {
|
|
|
*m_reservoirData = newData; // 调用赋值运算符
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->refreshAll();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataGeoRef* nmDataAnalyzeManager::createGeoRefData()
|
|
|
{
|
|
|
if(m_pGeoRefData != nullptr) {
|
|
|
delete m_pGeoRefData;
|
|
|
m_pGeoRefData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pGeoRefData = new nmDataGeoRef;
|
|
|
return m_pGeoRefData;
|
|
|
}
|
|
|
|
|
|
nmDataGeoRef* nmDataAnalyzeManager::getGeoRefData() const
|
|
|
{
|
|
|
return m_pGeoRefData;
|
|
|
}
|
|
|
|
|
|
nmDataGeoRef nmDataAnalyzeManager::getGeoRefDataCopy() const
|
|
|
{
|
|
|
if(m_pGeoRefData) {
|
|
|
return *m_pGeoRefData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
return nmDataGeoRef(); // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateGeoRefData(const nmDataGeoRef& newData)
|
|
|
{
|
|
|
if(m_pGeoRefData == nullptr) {
|
|
|
m_pGeoRefData = new nmDataGeoRef;
|
|
|
}
|
|
|
|
|
|
*m_pGeoRefData = newData; // 调用赋值运算符
|
|
|
}
|
|
|
|
|
|
nmDataForecast* nmDataAnalyzeManager::createForecastData()
|
|
|
{
|
|
|
if(m_pForecastData != nullptr) {
|
|
|
delete m_pForecastData;
|
|
|
m_pForecastData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pForecastData = new nmDataForecast;
|
|
|
return m_pForecastData;
|
|
|
}
|
|
|
|
|
|
nmDataForecast* nmDataAnalyzeManager::getForecastData() const
|
|
|
{
|
|
|
return m_pForecastData;
|
|
|
}
|
|
|
|
|
|
nmDataForecast nmDataAnalyzeManager::getForecastDataCopy() const
|
|
|
{
|
|
|
if(m_pForecastData) {
|
|
|
return *m_pForecastData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
return nmDataForecast(); // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateForecastData(const nmDataForecast& newData)
|
|
|
{
|
|
|
if(m_pForecastData == nullptr) {
|
|
|
m_pForecastData = new nmDataForecast;
|
|
|
}
|
|
|
|
|
|
*m_pForecastData = newData; // 调用赋值运算符
|
|
|
}
|
|
|
|
|
|
nmDataSensitive* nmDataAnalyzeManager::createSensitiveData()
|
|
|
{
|
|
|
// 如果已有数据,先删除再创建
|
|
|
if (m_pSensitiveData != nullptr) {
|
|
|
delete m_pSensitiveData;
|
|
|
m_pSensitiveData = nullptr;
|
|
|
}
|
|
|
|
|
|
// 创建一个新的 nmDataSensitive 对象
|
|
|
m_pSensitiveData = new nmDataSensitive();
|
|
|
|
|
|
// 初始化 Calculation Type
|
|
|
m_pSensitiveData->setCalculationType(nmDataSensitive::CALC_DETERMINISTIC);
|
|
|
|
|
|
// 设置总模型数为 0,后面可以通过 setTotalModelCount 修改
|
|
|
m_pSensitiveData->setTotalModelCount(0);
|
|
|
|
|
|
// 创建变量列表
|
|
|
QList<nmDataSensitive::VariableSampling> vars;
|
|
|
|
|
|
// 定义一个通用的添加变量的辅助函数
|
|
|
auto addVar = [&](const QString& group,
|
|
|
const QString& name,
|
|
|
double model, double min, double max,
|
|
|
const QString& unit)
|
|
|
{
|
|
|
nmDataSensitive::VariableSampling vs;
|
|
|
vs.setVarGroup(group); // 设置变量组
|
|
|
vs.setVarName(name); // 设置变量名
|
|
|
vs.setEnabled(false); // 默认不勾选
|
|
|
vs.setMode(nmDataSensitive::VariableSampling::MODE_AUTOMATIC); // 自动模式
|
|
|
vs.setLog(false); // 默认不选 Log
|
|
|
vs.setNumber(5); // 默认 Number 为 5
|
|
|
|
|
|
// 设置 modelValue、minValue、maxValue 和单位
|
|
|
nmDataAttribute& modelValue = vs.getModelValue();
|
|
|
modelValue.setName(name + " Model");
|
|
|
modelValue.setUnit(unit);
|
|
|
modelValue.setValue(model);
|
|
|
|
|
|
nmDataAttribute& minValue = vs.getMinValue();
|
|
|
minValue.setName(name + " Min");
|
|
|
minValue.setUnit(unit);
|
|
|
minValue.setValue(min);
|
|
|
|
|
|
nmDataAttribute& maxValue = vs.getMaxValue();
|
|
|
maxValue.setName(name + " Max");
|
|
|
maxValue.setUnit(unit);
|
|
|
maxValue.setValue(max);
|
|
|
|
|
|
vars.append(vs); // 将变量添加到变量列表中
|
|
|
};
|
|
|
|
|
|
// 初始化变量组和每个变量
|
|
|
// === Tested Well ===
|
|
|
addVar("Tested Well", "Zw", 15.0, 7.5, 30.0, "ft");
|
|
|
addVar("Tested Well", "Hw", 10.0, 5.0, 20.0, "ft");
|
|
|
addVar("Tested Well", "Lw", 12.0, 6.0, 18.0, "ft");
|
|
|
addVar("Tested Well", "Skin", 1.0, 0.5, 1.5, "dimensionless");
|
|
|
addVar("Tested Well", "C", 0.3, 0.1, 1.0, "dimensionless");
|
|
|
|
|
|
// === Reservoir ===
|
|
|
addVar("Reservoir", "Pi", 1000.0, 800.0, 1200.0, "psi");
|
|
|
addVar("Reservoir", "k", 150.0, 100.0, 200.0, "mD");
|
|
|
addVar("Reservoir", "h", 50.0, 30.0, 70.0, "ft");
|
|
|
addVar("Reservoir", "φ", 0.2, 0.1, 0.4, "dimensionless");
|
|
|
addVar("Reservoir", "ntg", 0.9, 0.7, 1.0, "dimensionless");
|
|
|
addVar("Reservoir", "kz/kr", 1.0, 0.5, 1.5, "dimensionless");
|
|
|
|
|
|
// === Pvt ===
|
|
|
addVar("Pvt", "Total compressibility", 5e-6, 2e-6, 8e-6, "1/psi");
|
|
|
|
|
|
// 将变量列表设置到敏感性数据对象中
|
|
|
m_pSensitiveData->setVariables(vars);
|
|
|
|
|
|
// 返回已初始化的敏感性数据对象
|
|
|
return m_pSensitiveData;
|
|
|
}
|
|
|
|
|
|
nmDataSensitive* nmDataAnalyzeManager::getSensitiveData() const
|
|
|
{
|
|
|
return m_pSensitiveData;
|
|
|
}
|
|
|
|
|
|
nmDataSensitive nmDataAnalyzeManager::getSensitiveDataCopy() const
|
|
|
{
|
|
|
if(m_pSensitiveData) {
|
|
|
return *m_pSensitiveData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
return nmDataSensitive(); // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateSensitiveData(const nmDataSensitive& newData)
|
|
|
{
|
|
|
if(m_pSensitiveData == nullptr) {
|
|
|
m_pSensitiveData = new nmDataSensitive();
|
|
|
}
|
|
|
|
|
|
*m_pSensitiveData = newData; // 调用赋值运算符
|
|
|
}
|
|
|
|
|
|
nmDataDiagnostic* nmDataAnalyzeManager::getDiagnosticData() const {
|
|
|
return m_pDiagnosticData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::resetFromDiagnostic()
|
|
|
{
|
|
|
// 获取当前井和储层数据
|
|
|
nmDataWellBase* pCurrentWell = getCurWellData();
|
|
|
nmDataReservoir* pReservoirData = getReservoirData();
|
|
|
|
|
|
if(!pCurrentWell || !pReservoirData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 获取"双对数"线性数据
|
|
|
QVector<QVector<double>> rawData = pCurrentWell->getHistoryLogLog();
|
|
|
if(rawData.isEmpty() || rawData.size() != 3) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 创建诊断对象并使用修正后的数据
|
|
|
if(!m_pDiagnosticData) {
|
|
|
m_pDiagnosticData = new nmDataDiagnostic();
|
|
|
}
|
|
|
|
|
|
m_pDiagnosticData->resetFromDiagnostic(rawData, pCurrentWell, pReservoirData);
|
|
|
|
|
|
// 应用诊断结果到相应参数
|
|
|
applyDiagnosticResults(m_pDiagnosticData, pCurrentWell, pReservoirData);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
catch(...) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::resetFromAnalytical()
|
|
|
{
|
|
|
// 获取当前井和储层数据
|
|
|
nmDataWellBase* pCurrentWell = getCurWellData();
|
|
|
nmDataReservoir* pReservoirData = getReservoirData();
|
|
|
|
|
|
if(!pCurrentWell || !pReservoirData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 重置储层参数为默认值
|
|
|
pReservoirData->resetToDefaults();
|
|
|
|
|
|
// 重置井参数为默认值
|
|
|
pCurrentWell->resetToDefaults();
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
catch(...) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 应用诊断结果
|
|
|
void nmDataAnalyzeManager::applyDiagnosticResults(nmDataDiagnostic* diagnostic, nmDataWellBase* wellData, nmDataReservoir* reservoirData)
|
|
|
{
|
|
|
if(!diagnostic || !wellData || !reservoirData) return;
|
|
|
|
|
|
// 获取诊断结果
|
|
|
double diagnosticPerm = diagnostic->getDiagnosticPermeability().getValue().toDouble();
|
|
|
double diagnosticStorage = diagnostic->getDiagnosticWellboreStorage().getValue().toDouble();
|
|
|
double diagnosticTrans = diagnostic->getDiagnosticTransmissibility().getValue().toDouble();
|
|
|
double diagnosticSkin = diagnostic->getDiagnosticSkin().getValue().toDouble();
|
|
|
|
|
|
// 应用渗透率到储层数据
|
|
|
nmDataAttribute& reservoirPerm = reservoirData->getPermeability();
|
|
|
reservoirPerm.setValue(diagnosticPerm);
|
|
|
|
|
|
// 应用井筒储存到井数据
|
|
|
nmDataAttribute& wellStorage = wellData->getWellboreStorage();
|
|
|
wellStorage.setValue(diagnosticStorage);
|
|
|
|
|
|
// 应用导流能力到储层数据
|
|
|
nmDataAttribute& reservoirTrans = reservoirData->getTransmissibility();
|
|
|
reservoirTrans.setValue(diagnosticTrans);
|
|
|
|
|
|
// 应用皮损系数到井的第一段射孔
|
|
|
if(wellData->getPerforationCount() > 0) {
|
|
|
nmDataPerforation* firstPerforation = wellData->getPerforation(0);
|
|
|
if(firstPerforation) {
|
|
|
firstPerforation->getSkin().setValue(diagnosticSkin);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataAutomaticFitting* nmDataAnalyzeManager::createAutomaticFittingData()
|
|
|
{
|
|
|
if(m_pAutomaticFittingData != nullptr) {
|
|
|
delete m_pAutomaticFittingData;
|
|
|
m_pAutomaticFittingData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pAutomaticFittingData = new nmDataAutomaticFitting;
|
|
|
return m_pAutomaticFittingData;
|
|
|
}
|
|
|
|
|
|
nmDataAutomaticFitting* nmDataAnalyzeManager::getAutomaticFittingData() const
|
|
|
{
|
|
|
return m_pAutomaticFittingData;
|
|
|
}
|
|
|
|
|
|
nmDataAutomaticFitting nmDataAnalyzeManager::getAutomaticFittingDataCopy() const
|
|
|
{
|
|
|
if(m_pAutomaticFittingData) {
|
|
|
return *m_pAutomaticFittingData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
return nmDataAutomaticFitting(); // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateAutomaticFittingData(const nmDataAutomaticFitting& newData)
|
|
|
{
|
|
|
if(m_pAutomaticFittingData == nullptr) {
|
|
|
m_pAutomaticFittingData = new nmDataAutomaticFitting;
|
|
|
}
|
|
|
|
|
|
*m_pAutomaticFittingData = newData; // 调用赋值运算符
|
|
|
}
|
|
|
|
|
|
/// @brief 从PVT结果页读取曲线数组,确定求解器模型类型
|
|
|
/// 按相态获取PVT数组参数,获取不到则回退到常数PVT模型(Gas除外弹警告)
|
|
|
void nmDataAnalyzeManager::initPvtParaFromSubFit()
|
|
|
{
|
|
|
// 1. 获取上下文
|
|
|
iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
|
|
|
nmDataAnalyzeContextProvider* pCtx = nmDataAnalyzeContext::provider();
|
|
|
Q_ASSERT(nullptr != pSubWndFitting);
|
|
|
Q_ASSERT(nullptr != pCtx);
|
|
|
|
|
|
if(nullptr == pSubWndFitting || nullptr == pCtx) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
PvtFluidType eType = WFT_Null;
|
|
|
pCtx->getBasicPft(pSubWndFitting, eType);
|
|
|
|
|
|
// 2. 清理旧的PVT参数对象,新建空对象
|
|
|
if(m_pebiPvtPara != nullptr) {
|
|
|
delete m_pebiPvtPara;
|
|
|
m_pebiPvtPara = nullptr;
|
|
|
}
|
|
|
m_pebiPvtPara = new nmDataPvtParaForPebi;
|
|
|
|
|
|
// 压力横坐标,首次成功读取时赋值
|
|
|
QVector<double> vecPressure;
|
|
|
|
|
|
// 3. 按相态分支获取PVT曲线并确定求解器类型
|
|
|
switch(eType) {
|
|
|
case WFT_Oil:
|
|
|
// 油相:获取到变量PVT曲线则升级模型,否则保持常数PVT
|
|
|
if(tryFetchPhasePvtCurves(pCtx, pSubWndFitting, WFT_Oil,
|
|
|
"Bo", "Co", "Miuo", m_pebiPvtPara, vecPressure)) {
|
|
|
setSolverModelType(SMT_Oil_VariablePvt);
|
|
|
} else {
|
|
|
setSolverModelType(SMT_Oil_ConstPvt);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case WFT_Water:
|
|
|
// 水相:获取到变量PVT曲线则升级模型,否则保持常数PVT
|
|
|
if(tryFetchPhasePvtCurves(pCtx, pSubWndFitting, WFT_Water,
|
|
|
"Bw", "Cw", "Miuw", m_pebiPvtPara, vecPressure)) {
|
|
|
setSolverModelType(SMT_Water_VariablePvt);
|
|
|
} else {
|
|
|
setSolverModelType(SMT_Water_ConstPvt);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case WFT_Gas:
|
|
|
// 气相:必须获取到全部PVT曲线,否则弹警告并返回
|
|
|
setSolverModelType(SMT_Gas_VariablePvt);
|
|
|
if(!tryFetchPhasePvtCurves(pCtx, pSubWndFitting, WFT_Gas,
|
|
|
"Bg", "Cg", "Miug", m_pebiPvtPara, vecPressure)) {
|
|
|
QMessageBox::warning(nullptr, tr("Warning"), tr("Please select all gas PVT parameters."));
|
|
|
return;
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case WFT_Oil_Water: {
|
|
|
// 油水两相:油、水PVT曲线必须全部获取,否则返回
|
|
|
setSolverModelType(SMT_Oil_Water_TwoPhase);
|
|
|
bool bOilOk = tryFetchPhasePvtCurves(pCtx, pSubWndFitting, WFT_Oil_Water,
|
|
|
"Bo", "Co", "Miuo", m_pebiPvtPara, vecPressure);
|
|
|
bool bWaterOk = tryFetchPhasePvtCurves(pCtx, pSubWndFitting, WFT_Oil_Water,
|
|
|
"Bw", "Cw", "Miuw", m_pebiPvtPara, vecPressure);
|
|
|
if(!(bOilOk && bWaterOk)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Diffusion相渗数据来自Diffusion页面
|
|
|
VVecDouble vvecDiffusionKK;
|
|
|
if(pCtx->getDiffusionRstOf(pSubWndFitting, DSO_KK, vvecDiffusionKK)) {
|
|
|
applyDiffusionKkToPebiPvt(m_pebiPvtPara, vvecDiffusionKK);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 4. 统一设置压力横坐标
|
|
|
setPebiPressureIfEmpty(m_pebiPvtPara, vecPressure);
|
|
|
}
|
|
|
|
|
|
nmDataRegionMark *nmDataAnalyzeManager::createRegionMark()
|
|
|
{
|
|
|
nmDataRegionMark* regionMarkData = new nmDataRegionMark;
|
|
|
m_vRegionMarkData.append(regionMarkData);
|
|
|
return regionMarkData;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataRegionMark*> nmDataAnalyzeManager::getRegionMarkDataList() const
|
|
|
{
|
|
|
return m_vRegionMarkData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeRegionMarkData(nmDataRegionMark * pData)
|
|
|
{
|
|
|
if(pData) {
|
|
|
// 遍历 m_vRegionMarkData 数组,找到并移除对应的对象
|
|
|
for(int i = 0; i < m_vRegionMarkData.size(); ++i) {
|
|
|
if(m_vRegionMarkData[i] == pData) {
|
|
|
delete m_vRegionMarkData[i]; // 释放内存
|
|
|
m_vRegionMarkData.remove(i);
|
|
|
notifyGeometryListChanged();
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
QVector<nmDataRegionMark> nmDataAnalyzeManager::getRegionMarkDataListCopy() const
|
|
|
{
|
|
|
QVector<nmDataRegionMark> result;
|
|
|
result.reserve(m_vRegionMarkData.size());
|
|
|
|
|
|
foreach(const nmDataRegionMark* regionMark, m_vRegionMarkData) {
|
|
|
if(regionMark) {
|
|
|
result.append(*regionMark); // 调用拷贝构造函数
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateRegionMarkData(const QVector<nmDataRegionMark>& newData)
|
|
|
{
|
|
|
// 确保数量一致
|
|
|
if(newData.size() != m_vRegionMarkData.size()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < newData.size(); ++i) {
|
|
|
if(m_vRegionMarkData[i]) {
|
|
|
// 只更新内容,不改变指针
|
|
|
*m_vRegionMarkData[i] = newData[i]; // 调用赋值运算符
|
|
|
m_vRegionMarkData[i]->notifyDataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 批量赋值不触发属性信号,完成后统一刷新参数面板。
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->refreshAll();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataOutline *nmDataAnalyzeManager::createOutline()
|
|
|
{
|
|
|
if(m_outlineData != nullptr) {
|
|
|
delete m_outlineData;
|
|
|
m_outlineData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_outlineData = new nmDataOutline;
|
|
|
return m_outlineData;
|
|
|
}
|
|
|
|
|
|
nmDataOutline* nmDataAnalyzeManager::getOutlineData()
|
|
|
{
|
|
|
return m_outlineData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeOutlineData()
|
|
|
{
|
|
|
if(m_outlineData) {
|
|
|
delete m_outlineData; // 删除边界数据对象
|
|
|
m_outlineData = nullptr;
|
|
|
notifyGeometryListChanged();
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
nmDataOutline nmDataAnalyzeManager::getOutlineDataCopy() const
|
|
|
{
|
|
|
if(m_outlineData) {
|
|
|
return *m_outlineData; // 调用拷贝构造函数
|
|
|
}
|
|
|
|
|
|
nmDataOutline obj;
|
|
|
return obj; // 返回默认构造对象
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateOutlineData(const nmDataOutline & newData)
|
|
|
{
|
|
|
if(m_outlineData) {
|
|
|
*m_outlineData = newData; // 调用赋值运算符
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataRegion *nmDataAnalyzeManager::createRegion()
|
|
|
{
|
|
|
nmDataRegion* regionData = new nmDataRegion;
|
|
|
m_vRegionData.append(regionData);
|
|
|
return regionData;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataRegion*> nmDataAnalyzeManager::getRegionDataList() const
|
|
|
{
|
|
|
return m_vRegionData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeRegionData(nmDataRegion * pData)
|
|
|
{
|
|
|
if(pData) {
|
|
|
// 遍历 m_vRegionData 数组,找到并移除对应的对象
|
|
|
for(int i = 0; i < m_vRegionData.size(); ++i) {
|
|
|
if(m_vRegionData[i] == pData) {
|
|
|
delete m_vRegionData[i]; // 释放内存
|
|
|
m_vRegionData.remove(i);
|
|
|
notifyGeometryListChanged();
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
QVector<nmDataRegion> nmDataAnalyzeManager::getRegionDataListCopy() const
|
|
|
{
|
|
|
QVector<nmDataRegion> result;
|
|
|
result.reserve(m_vRegionData.size());
|
|
|
|
|
|
foreach(const nmDataRegion* region, m_vRegionData) {
|
|
|
if(region) {
|
|
|
result.append(*region); // 调用拷贝构造函数
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateRegionData(const QVector<nmDataRegion>& newData)
|
|
|
{
|
|
|
// 确保数量一致
|
|
|
if(newData.size() != m_vRegionData.size()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < newData.size(); ++i) {
|
|
|
if(m_vRegionData[i]) {
|
|
|
// 只更新内容,不改变指针
|
|
|
*m_vRegionData[i] = newData[i]; // 调用赋值运算符
|
|
|
m_vRegionData[i]->notifyDataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 批量赋值不触发属性信号,完成后统一刷新参数面板。
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->refreshAll();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataFracture *nmDataAnalyzeManager::createFracture()
|
|
|
{
|
|
|
nmDataFracture* fractureData = new nmDataFracture;
|
|
|
m_vFractureData.append(fractureData);
|
|
|
return fractureData;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataFracture*> nmDataAnalyzeManager::getFractureDataList() const
|
|
|
{
|
|
|
return m_vFractureData;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataFracture*> nmDataAnalyzeManager::getDFNFractureDataList() const
|
|
|
{
|
|
|
QVector<nmDataFracture*> vecDFNs;
|
|
|
|
|
|
// 遍历 m_vFractureData 数组,找到并移除对应的对象
|
|
|
for(int i = 0; i < m_vFractureData.size(); ++i) {
|
|
|
if(m_vFractureData[i]->getFractureType().getValue() == tr("DFN")) {
|
|
|
vecDFNs.append(m_vFractureData[i]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vecDFNs;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeFractureData(nmDataFracture * pData)
|
|
|
{
|
|
|
if(pData) {
|
|
|
// 遍历 m_vFractureData 数组,找到并移除对应的对象
|
|
|
for(int i = 0; i < m_vFractureData.size(); ++i) {
|
|
|
if(m_vFractureData[i] == pData) {
|
|
|
const bool bNotifyGeometryList =
|
|
|
m_vFractureData[i]->getFractureType().getValue().toString() != "DFN";
|
|
|
delete m_vFractureData[i]; // 释放内存
|
|
|
m_vFractureData.remove(i);
|
|
|
if (bNotifyGeometryList) {
|
|
|
notifyGeometryListChanged();
|
|
|
} else {
|
|
|
notifyDataChanged();
|
|
|
}
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
QVector<nmDataFracture> nmDataAnalyzeManager::getFractureDataListCopy() const
|
|
|
{
|
|
|
QVector<nmDataFracture> result;
|
|
|
result.reserve(m_vFractureData.size());
|
|
|
|
|
|
foreach(const nmDataFracture* fracture, m_vFractureData) {
|
|
|
if(fracture) {
|
|
|
result.append(*fracture); // 调用拷贝构造函数
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateFractureData(const QVector<nmDataFracture>& newData)
|
|
|
{
|
|
|
// 确保数量一致
|
|
|
if(newData.size() != m_vFractureData.size()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < newData.size(); ++i) {
|
|
|
if(m_vFractureData[i]) {
|
|
|
// 只更新内容,不改变指针
|
|
|
*m_vFractureData[i] = newData[i]; // 调用赋值运算符
|
|
|
m_vFractureData[i]->notifyDataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 批量赋值不触发属性信号,完成后统一刷新参数面板。
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->refreshAll();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
nmDataFault *nmDataAnalyzeManager::createFault()
|
|
|
{
|
|
|
nmDataFault* faultData = new nmDataFault;
|
|
|
m_vFaultData.append(faultData);
|
|
|
return faultData;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataFault*> nmDataAnalyzeManager::getFaultDataList() const
|
|
|
{
|
|
|
return m_vFaultData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeFaultData(nmDataFault * pData)
|
|
|
{
|
|
|
if(pData) {
|
|
|
// 遍历 m_vFaultData 数组,找到并移除对应的对象
|
|
|
for(int i = 0; i < m_vFaultData.size(); ++i) {
|
|
|
if(m_vFaultData[i] == pData) {
|
|
|
delete m_vFaultData[i]; // 释放内存
|
|
|
m_vFaultData.remove(i);
|
|
|
notifyGeometryListChanged();
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
QVector<nmDataFault> nmDataAnalyzeManager::getFaultDataListCopy() const
|
|
|
{
|
|
|
QVector<nmDataFault> result;
|
|
|
result.reserve(m_vFaultData.size());
|
|
|
|
|
|
foreach(const nmDataFault* fault, m_vFaultData) {
|
|
|
if(fault) {
|
|
|
result.append(*fault); // 调用拷贝构造函数
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateFaultData(const QVector<nmDataFault>& newData)
|
|
|
{
|
|
|
// 确保数量一致
|
|
|
if(newData.size() != m_vFaultData.size()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < newData.size(); ++i) {
|
|
|
if(m_vFaultData[i]) {
|
|
|
// 只更新内容,不改变指针
|
|
|
*m_vFaultData[i] = newData[i]; // 调用赋值运算符
|
|
|
m_vFaultData[i]->notifyDataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 批量赋值不触发属性信号,完成后统一刷新参数面板。
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->refreshAll();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmGuiPlot* nmDataAnalyzeManager::getPlot() const
|
|
|
{
|
|
|
return m_pNmGuiPlot;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPlot(nmGuiPlot * plot)
|
|
|
{
|
|
|
m_pNmGuiPlot = plot;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateWellPlotByDataManager()
|
|
|
{
|
|
|
nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider();
|
|
|
if (m_pNmGuiPlot != nullptr && pPlotContextProvider != nullptr)
|
|
|
{
|
|
|
pPlotContextProvider->updateWellPlots(m_pNmGuiPlot, this);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> nmDataAnalyzeManager::getValueAllConnectablePoints()
|
|
|
{
|
|
|
QVector<QPointF> vecAllPoints;
|
|
|
|
|
|
// 遍历所有断层数据
|
|
|
foreach(nmDataFault* fault, m_vFaultData) {
|
|
|
if(fault) {
|
|
|
QVector<QPointF> faultPoints = fault->getFaultPoints();
|
|
|
|
|
|
foreach(const QPointF& point, faultPoints) {
|
|
|
vecAllPoints.append(point);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历所有裂缝数据
|
|
|
foreach(nmDataFracture* fracture, m_vFractureData) {
|
|
|
if(fracture) {
|
|
|
QVector<QPointF> fracturePoints = fracture->getFracturePoints();
|
|
|
|
|
|
foreach(const QPointF& point, fracturePoints) {
|
|
|
vecAllPoints.append(point);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历所有复合区数据
|
|
|
foreach(nmDataRegion* region, m_vRegionData) {
|
|
|
if(region) {
|
|
|
QVector<QPointF> regionPoints = region->getVecPts();
|
|
|
|
|
|
foreach(const QPointF& point, regionPoints) {
|
|
|
vecAllPoints.append(point);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 处理边界数据
|
|
|
if(m_outlineData) {
|
|
|
QVector<QPointF> outlinePoints = m_outlineData->getOutlinePoints();
|
|
|
|
|
|
// 圆形边界,特殊处理
|
|
|
if(m_outlineData->getOutlineType() == NM_Round_Outline_Type) {
|
|
|
outlinePoints.remove(0); // 圆心
|
|
|
outlinePoints.remove(0); // 半径
|
|
|
}
|
|
|
|
|
|
foreach(const QPointF& point, outlinePoints) {
|
|
|
vecAllPoints.append(point);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 垂直裂缝井的裂缝两个端点
|
|
|
// 遍历所有井数据
|
|
|
foreach(nmDataWellBase* well, m_vWellData) {
|
|
|
if(well == nullptr) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(well->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
|
|
|
nmDataVerticalFracturedWell* pVFwell = dynamic_cast<nmDataVerticalFracturedWell*>(well);
|
|
|
if(pVFwell == nullptr) {
|
|
|
continue;
|
|
|
}
|
|
|
QVector<QPointF> fracsPoints = pVFwell->getFracs();
|
|
|
|
|
|
foreach(const QPointF& point, fracsPoints) {
|
|
|
vecAllPoints.append(point);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vecAllPoints;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> nmDataAnalyzeManager::getPosAllConnectablePoints()
|
|
|
{
|
|
|
// 将绘图坐标系转为qt坐标系
|
|
|
QVector<QPointF> vecValues = this->getValueAllConnectablePoints();
|
|
|
QVector<QPointF> vecPos;
|
|
|
|
|
|
Q_ASSERT(m_pNmGuiPlot != nullptr);
|
|
|
nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider();
|
|
|
if(m_pNmGuiPlot == nullptr || pPlotContextProvider == nullptr) {
|
|
|
return vecPos;
|
|
|
}
|
|
|
|
|
|
pPlotContextProvider->getPosForValue(m_pNmGuiPlot, vecValues, vecPos);
|
|
|
|
|
|
// TODO:没有m_pNmGuiPlot的情况下怎么办,也就是加载的时候
|
|
|
|
|
|
return vecPos;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> nmDataAnalyzeManager::removePosByObject(QObject * obj)
|
|
|
{
|
|
|
// 获取所有可连接的点(屏幕坐标)
|
|
|
QVector<QPointF> vecAllPoints = this->getPosAllConnectablePoints();
|
|
|
|
|
|
// 创建一个临时容器用于存储要移除的点
|
|
|
QVector<QPointF> pointsToRemove;
|
|
|
|
|
|
// 检查传入的对象类型并收集相关点
|
|
|
if(nmDataFault * fault = dynamic_cast<nmDataFault * >(obj)) {
|
|
|
QVector<QPointF> faultPoints = fault->getFaultPoints();
|
|
|
|
|
|
foreach(const QPointF& point, faultPoints) {
|
|
|
pointsToRemove.append(point);
|
|
|
}
|
|
|
} else if(nmDataFracture * fracture = dynamic_cast<nmDataFracture * >(obj)) {
|
|
|
QVector<QPointF> fracturePoints = fracture->getFracturePoints();
|
|
|
|
|
|
foreach(const QPointF& point, fracturePoints) {
|
|
|
pointsToRemove.append(point);
|
|
|
}
|
|
|
} else if(nmDataRegion * region = dynamic_cast<nmDataRegion * >(obj)) {
|
|
|
QVector<QPointF> regionPoints = region->getVecPts();
|
|
|
|
|
|
foreach(const QPointF& point, regionPoints) {
|
|
|
pointsToRemove.append(point);
|
|
|
}
|
|
|
} else if(nmDataVerticalFracturedWell * well = dynamic_cast<nmDataVerticalFracturedWell * >(obj)) {
|
|
|
QVector<QPointF> fracsPoints = well->getFracs();
|
|
|
|
|
|
foreach(const QPointF& point, fracsPoints) {
|
|
|
pointsToRemove.append(point);
|
|
|
}
|
|
|
} else if(m_outlineData == obj) {
|
|
|
QVector<QPointF> outlinePoints = m_outlineData->getOutlinePoints();
|
|
|
|
|
|
if(m_outlineData->getOutlineType() == NM_Round_Outline_Type) {
|
|
|
outlinePoints.remove(0); // 圆心
|
|
|
outlinePoints.remove(0); // 半径
|
|
|
}
|
|
|
|
|
|
foreach(const QPointF& point, outlinePoints) {
|
|
|
pointsToRemove.append(point);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// change to Pos
|
|
|
QVector<QPointF> vecPosRemove;
|
|
|
|
|
|
nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider();
|
|
|
if(m_pNmGuiPlot && pPlotContextProvider) {
|
|
|
pPlotContextProvider->getPosForValue(m_pNmGuiPlot, pointsToRemove, vecPosRemove);
|
|
|
}
|
|
|
|
|
|
// 移除与特定对象相关的点
|
|
|
QVector<QPointF>::iterator itRemove;
|
|
|
|
|
|
for(itRemove = vecPosRemove.begin(); itRemove != vecPosRemove.end(); ++itRemove) {
|
|
|
QPointF pointToRemove = *itRemove;
|
|
|
|
|
|
for(int i = 0; i < vecAllPoints.size(); ++i) {
|
|
|
if(vecAllPoints[i] == pointToRemove) {
|
|
|
vecAllPoints.remove(i);
|
|
|
break; // 只移除第一个匹配的点
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vecAllPoints;
|
|
|
}
|
|
|
|
|
|
nmDataMeasuringScale *nmDataAnalyzeManager::getMeasuringScaleData()
|
|
|
{
|
|
|
if(m_pMeasuringScaleData == nullptr) {
|
|
|
m_pMeasuringScaleData = new nmDataMeasuringScale;
|
|
|
}
|
|
|
|
|
|
return m_pMeasuringScaleData;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setMeasuringScaleData(nmDataMeasuringScale * pMeasuringScaleData)
|
|
|
{
|
|
|
if(m_pMeasuringScaleData != nullptr) {
|
|
|
delete m_pMeasuringScaleData;
|
|
|
m_pMeasuringScaleData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pMeasuringScaleData = pMeasuringScaleData;
|
|
|
}
|
|
|
|
|
|
nmDataMeasure *nmDataAnalyzeManager::getMeasureData()
|
|
|
{
|
|
|
if(m_pMeasureData == nullptr) {
|
|
|
m_pMeasureData = new nmDataMeasure;
|
|
|
}
|
|
|
|
|
|
return m_pMeasureData;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setMeasureData(nmDataMeasure * pMeasureData)
|
|
|
{
|
|
|
if(m_pMeasureData != nullptr) {
|
|
|
delete m_pMeasureData;
|
|
|
m_pMeasureData = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pMeasureData = pMeasureData;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeMeasureData()
|
|
|
{
|
|
|
if(m_pMeasureData) {
|
|
|
delete m_pMeasureData;
|
|
|
m_pMeasureData = nullptr;
|
|
|
return true; // 移除成功,返回 true
|
|
|
}
|
|
|
|
|
|
return false; // 未找到或移除失败,返回 false
|
|
|
}
|
|
|
|
|
|
NM_Grid_Type nmDataAnalyzeManager::getGridType()
|
|
|
{
|
|
|
return m_eGridType;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setGridType(NM_Grid_Type newGridType)
|
|
|
{
|
|
|
if(m_eGridType == newGridType) {
|
|
|
return;
|
|
|
}
|
|
|
m_eGridType = newGridType;
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
}
|
|
|
|
|
|
NM_SOLVER_MODEL_TYPE nmDataAnalyzeManager::getSolverModelType() const
|
|
|
{
|
|
|
return m_eSolverModelType;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setSolverModelType(NM_SOLVER_MODEL_TYPE newSolverModelType)
|
|
|
{
|
|
|
if(m_eSolverModelType == newSolverModelType) {
|
|
|
return;
|
|
|
}
|
|
|
m_eSolverModelType = newSolverModelType;
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
|
|
|
int nmDataAnalyzeManager::getPebiSolverType() const
|
|
|
{
|
|
|
return m_nPebiSolverType;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPebiSolverType(int nSolverType)
|
|
|
{
|
|
|
if((nSolverType == PebiSolverCpuAccelerated ||
|
|
|
nSolverType == PebiSolverOriginal) &&
|
|
|
m_nPebiSolverType != nSolverType) {
|
|
|
m_nPebiSolverType = nSolverType;
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int nmDataAnalyzeManager::getPebiOmpThreads() const
|
|
|
{
|
|
|
return m_nPebiOmpThreads;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPebiOmpThreads(int nOmpThreads)
|
|
|
{
|
|
|
if(nOmpThreads == 1 || nOmpThreads == 2 || nOmpThreads == 4
|
|
|
|| nOmpThreads == 8 || nOmpThreads == 16) {
|
|
|
if(m_nPebiOmpThreads != nOmpThreads) {
|
|
|
m_nPebiOmpThreads = nOmpThreads;
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int nmDataAnalyzeManager::getPebiIluReuseSteps() const
|
|
|
{
|
|
|
return m_nPebiIluReuseSteps;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPebiIluReuseSteps(int nIluReuseSteps)
|
|
|
{
|
|
|
if(nIluReuseSteps >= 1 && nIluReuseSteps <= 100 &&
|
|
|
m_nPebiIluReuseSteps != nIluReuseSteps) {
|
|
|
m_nPebiIluReuseSteps = nIluReuseSteps;
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取Pebi网格求解数据接口
|
|
|
// 获取压力历史数据
|
|
|
QVector<QVector<double>> nmDataAnalyzeManager::getPebiSolverHistoryDataByName(const QString & wellName)
|
|
|
{
|
|
|
QVector<QVector<double>> vvecHisotryData;
|
|
|
QVector<double> vX;
|
|
|
QVector<double> vY;
|
|
|
|
|
|
// 验证井名有效性
|
|
|
if(wellName.isEmpty()) {
|
|
|
return vvecHisotryData;
|
|
|
}
|
|
|
|
|
|
// 从结果文件中读取数据
|
|
|
QString sHistoryDataFilePath = ZxBaseUtil::getCurWellDirOf("Nm/Solver") + "output/Pebi/" + wellName + "/Pressure.txt";
|
|
|
|
|
|
if(!QFile::exists(sHistoryDataFilePath)) {
|
|
|
return vvecHisotryData;
|
|
|
}
|
|
|
|
|
|
QStringList sContent = nmDataUtils::readNmDataFile(sHistoryDataFilePath);
|
|
|
QRegExp regex("\\s+");
|
|
|
|
|
|
for(int i = 0; i < sContent.size(); i++) {
|
|
|
QString sData = sContent[i].trimmed();
|
|
|
|
|
|
if(sData.isEmpty()) continue;
|
|
|
|
|
|
QStringList sXY = sData.split(regex, QString::SkipEmptyParts);
|
|
|
|
|
|
if(sXY.size() == 2) {
|
|
|
bool okX, okY;
|
|
|
double x = sXY[0].toDouble(&okX);
|
|
|
double y = sXY[1].toDouble(&okY);
|
|
|
|
|
|
if(okX && okY) {
|
|
|
vX.append(x);
|
|
|
vY.append(y);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vvecHisotryData.append(vX);
|
|
|
vvecHisotryData.append(vY);
|
|
|
|
|
|
return vvecHisotryData;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double>> nmDataAnalyzeManager::getPebiSolverLogPreDataByName(const QString & wellName)
|
|
|
{
|
|
|
QVector<QVector<double>> vvecLogPreData;
|
|
|
QVector<double> vX;
|
|
|
QVector<double> vY;
|
|
|
QVector<double> vZ;
|
|
|
|
|
|
// 验证井名有效性
|
|
|
if(wellName.isEmpty()) {
|
|
|
return vvecLogPreData;
|
|
|
}
|
|
|
|
|
|
// 从结果文件中读取数据
|
|
|
QString sLogPreDataFilePath = ZxBaseUtil::getCurWellDirOf("Nm/Solver") + "output/Pebi/" + wellName + "/Loglog.txt";
|
|
|
|
|
|
if(!QFile::exists(sLogPreDataFilePath)) {
|
|
|
return vvecLogPreData;
|
|
|
}
|
|
|
|
|
|
QStringList sContent = nmDataUtils::readNmDataFile(sLogPreDataFilePath);
|
|
|
QRegExp regex("\\s+");
|
|
|
|
|
|
for(int i = 0; i < sContent.size() - 1; i++) {
|
|
|
QString sData = sContent[i].trimmed();
|
|
|
|
|
|
if(sData.isEmpty()) continue;
|
|
|
|
|
|
QStringList sXYZ = sData.split(regex, QString::SkipEmptyParts);
|
|
|
|
|
|
if(sXYZ.size() == 3) {
|
|
|
bool okX, okY, okZ;
|
|
|
double x = sXYZ[0].toDouble(&okX);
|
|
|
double y = sXYZ[1].toDouble(&okY);
|
|
|
double z = sXYZ[2].toDouble(&okZ);
|
|
|
|
|
|
if(okX && okY && okZ) {
|
|
|
vX.append(x);
|
|
|
vY.append(y);
|
|
|
vZ.append(z);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vvecLogPreData.append(vX);
|
|
|
vvecLogPreData.append(vY);
|
|
|
vvecLogPreData.append(vZ);
|
|
|
|
|
|
return vvecLogPreData;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double>> nmDataAnalyzeManager::getPebiSolverSemiLogPreDataByName(const QString & wellName)
|
|
|
{
|
|
|
QVector<QVector<double>> vvecSemiLogPreData;
|
|
|
QVector<double> vX;
|
|
|
QVector<double> vY;
|
|
|
|
|
|
// 验证井名有效性
|
|
|
if(wellName.isEmpty()) {
|
|
|
return vvecSemiLogPreData;
|
|
|
}
|
|
|
|
|
|
// 从结果文件中读取数据
|
|
|
QString sSemiLogPreDataFilePath = ZxBaseUtil::getCurWellDirOf("Nm/Solver") + "output/Pebi/" + wellName + "/SemiLog.txt";
|
|
|
|
|
|
if(!QFile::exists(sSemiLogPreDataFilePath)) {
|
|
|
return vvecSemiLogPreData;
|
|
|
}
|
|
|
|
|
|
QStringList sContent = nmDataUtils::readNmDataFile(sSemiLogPreDataFilePath);
|
|
|
QRegExp regex("\\s+");
|
|
|
|
|
|
for(int i = 0; i < sContent.size(); i++) {
|
|
|
QString sData = sContent[i].trimmed();
|
|
|
|
|
|
if(sData.isEmpty()) continue;
|
|
|
|
|
|
QStringList sXY = sData.split(regex, QString::SkipEmptyParts);
|
|
|
|
|
|
if(sXY.size() == 2) {
|
|
|
bool okX, okY;
|
|
|
double x = sXY[0].toDouble(&okX);
|
|
|
double y = sXY[1].toDouble(&okY);
|
|
|
|
|
|
if(okX && okY) {
|
|
|
vX.append(x);
|
|
|
vY.append(y);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vvecSemiLogPreData.append(vX);
|
|
|
vvecSemiLogPreData.append(vY);
|
|
|
|
|
|
return vvecSemiLogPreData;
|
|
|
}
|
|
|
|
|
|
nmDataPvtParaForPebi* nmDataAnalyzeManager::getPebiPvtPara()
|
|
|
{
|
|
|
return m_pebiPvtPara;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::getPebiPseudoPressureTable(std::vector<double>& pressure,
|
|
|
std::vector<double>& pseudoPressure)
|
|
|
{
|
|
|
pressure.clear();
|
|
|
pseudoPressure.clear();
|
|
|
|
|
|
nmDataAnalyzeContextProvider* context = nmDataAnalyzeContext::provider();
|
|
|
// 使用本 DataManager 创建时绑定的成果窗口;切换当前页签不能改变数据来源。
|
|
|
iSubWndFitting* fitting = m_pOwnerFitting;
|
|
|
VVecDouble pseudoResult;
|
|
|
if(context == nullptr
|
|
|
|| fitting == nullptr
|
|
|
|| !context->getPseuRstOf(fitting, pseudoResult)
|
|
|
|| pseudoResult.size() < 2
|
|
|
|| pseudoResult[0].size() < 2
|
|
|
|| pseudoResult[0].size() != pseudoResult[1].size()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
pressure = pseudoResult[0].toStdVector();
|
|
|
pseudoPressure = pseudoResult[1].toStdVector();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
nmDataMixedResults* nmDataAnalyzeManager::getMixedResults()
|
|
|
{
|
|
|
return m_pMixedResults;
|
|
|
}
|
|
|
|
|
|
nmDataMixedResults* nmDataAnalyzeManager::createMixedResult()
|
|
|
{
|
|
|
if(m_pMixedResults != nullptr) {
|
|
|
delete m_pMixedResults;
|
|
|
m_pMixedResults = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pMixedResults = new nmDataMixedResults;
|
|
|
return m_pMixedResults;
|
|
|
}
|
|
|
|
|
|
nmDataLayer* nmDataAnalyzeManager::getLayerData()
|
|
|
{
|
|
|
// 1. 删除 m_vecLayers 中当前存储的所有 nmDataLayer 对象,释放内存
|
|
|
qDeleteAll(m_vecLayers);
|
|
|
// 2. 清空 m_vecLayers 自身,移除所有指针
|
|
|
m_vecLayers.clear();
|
|
|
|
|
|
return m_pLayerData;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setLayers(QVector<nmDataLayer*> vecLayers)
|
|
|
{
|
|
|
m_vecLayers = vecLayers;
|
|
|
// 几何分层会改变储层离散输入,旧网格和旧结果不再有效。
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
|
|
|
QVector<nmDataLayer*> nmDataAnalyzeManager::getLayers()
|
|
|
{
|
|
|
return m_vecLayers;
|
|
|
}
|
|
|
|
|
|
double nmDataAnalyzeManager::getMinLayerTop()
|
|
|
{
|
|
|
// 在计算前初始化边界值
|
|
|
double m_dMinLayerTop = DBL_MAX;
|
|
|
|
|
|
// 复制新数据并计算边界
|
|
|
foreach(const nmDataLayer* layer , m_vecLayers) {
|
|
|
if(layer) {
|
|
|
// 更新缓存的最小顶深度
|
|
|
m_dMinLayerTop = qMin(m_dMinLayerTop, layer->getTop());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果 m_vecLayers 为空,可以设置默认值
|
|
|
if(m_vecLayers.isEmpty()) {
|
|
|
m_dMinLayerTop = 0.0;
|
|
|
}
|
|
|
return m_dMinLayerTop;
|
|
|
}
|
|
|
|
|
|
// 新增的实现:返回存储的最大底深度
|
|
|
double nmDataAnalyzeManager::getMaxLayerBottom()
|
|
|
{
|
|
|
// 在计算前初始化边界值
|
|
|
double m_dMaxLayerBottom = DBL_MIN;
|
|
|
|
|
|
// 复制新数据并计算边界
|
|
|
foreach(const nmDataLayer* layer , m_vecLayers) {
|
|
|
if(layer) {
|
|
|
// 更新缓存的最大顶深度
|
|
|
m_dMaxLayerBottom = qMax(m_dMaxLayerBottom, layer->getBottom());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果 m_vecLayers 为空,可以设置默认值
|
|
|
if(m_vecLayers.isEmpty()) {
|
|
|
m_dMaxLayerBottom = 0.0;
|
|
|
}
|
|
|
return m_dMaxLayerBottom;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::appendCalculationWell(const QPair<NM_WELL_MODEL, QString>& well)
|
|
|
{
|
|
|
// 旧接口只作为过渡适配层:输入的显示井名在这里立即转换成 WellCode。
|
|
|
if(well.first == NM_WELL_MODEL::Unknow_Well) {
|
|
|
appendSolverWell(nmSolverWellRef(-1,
|
|
|
well.first,
|
|
|
QString(),
|
|
|
NM_SolverEntry_ManualFracture));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData = findWellByName(well.second);
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
appendSolverWell(nmSolverWellRef(-1,
|
|
|
well.first,
|
|
|
pWellData->getWellCode(),
|
|
|
NM_SolverEntry_Well));
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::insertCalculationWell(int index, const QPair<NM_WELL_MODEL, QString>& well)
|
|
|
{
|
|
|
if(well.first == NM_WELL_MODEL::Unknow_Well) {
|
|
|
insertSolverWell(index,
|
|
|
nmSolverWellRef(index,
|
|
|
well.first,
|
|
|
QString(),
|
|
|
NM_SolverEntry_ManualFracture));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData = findWellByName(well.second);
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
insertSolverWell(index,
|
|
|
nmSolverWellRef(index,
|
|
|
well.first,
|
|
|
pWellData->getWellCode(),
|
|
|
NM_SolverEntry_Well));
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeCalculationWell(int index)
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.removeSolverWell(index);
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearCalculationWells()
|
|
|
{
|
|
|
clearSolverWellOrder();
|
|
|
}
|
|
|
|
|
|
QVector<QPair<NM_WELL_MODEL, QString>> nmDataAnalyzeManager::getCalculationWells() const
|
|
|
{
|
|
|
QVector<QPair<NM_WELL_MODEL, QString> > vecLegacyOrder;
|
|
|
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
|
|
|
|
|
|
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
vecLegacyOrder.append(qMakePair(NM_WELL_MODEL::Unknow_Well,
|
|
|
QString()));
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode);
|
|
|
if(pWellData != nullptr) {
|
|
|
vecLegacyOrder.append(qMakePair(oWellRef.m_eWellType,
|
|
|
pWellData->getWellName()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vecLegacyOrder;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isContainsWellName(const QString & wellName) const
|
|
|
{
|
|
|
nmDataWellBase* pWellData = findWellByName(wellName);
|
|
|
return pWellData != nullptr &&
|
|
|
isWellSelectedForCalculation(pWellData->getWellCode());
|
|
|
}
|
|
|
|
|
|
nmDataNumericalAnalysisCase* nmDataAnalyzeManager::getNumericalAnalysisCase()
|
|
|
{
|
|
|
return &m_oNumericalAnalysisCase;
|
|
|
}
|
|
|
|
|
|
const nmDataNumericalAnalysisCase*
|
|
|
nmDataAnalyzeManager::getNumericalAnalysisCase() const
|
|
|
{
|
|
|
return &m_oNumericalAnalysisCase;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPrimaryWellCode(const QString& sWellCode)
|
|
|
{
|
|
|
if(!sWellCode.isEmpty() && findWellByCode(sWellCode) == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const quint64 nOldRevision =
|
|
|
m_oNumericalAnalysisCase.getGridInputRevision();
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellCode(sWellCode);
|
|
|
if(m_oNumericalAnalysisCase.getGridInputRevision() != nOldRevision) {
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString nmDataAnalyzeManager::getPrimaryWellCode() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getPrimaryWellCode();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setIncludeOtherWells(bool bInclude)
|
|
|
{
|
|
|
const bool bOldInclude = m_oNumericalAnalysisCase.getIncludeOtherWells();
|
|
|
m_oNumericalAnalysisCase.setIncludeOtherWells(bInclude);
|
|
|
if(bOldInclude != m_oNumericalAnalysisCase.getIncludeOtherWells()) {
|
|
|
// 开关只改变当前求解输入。最后结果下拉框继续使用快照冻结的 UUID 列表。
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::getIncludeOtherWells() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getIncludeOtherWells();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPebiGridControl(double dGridControl)
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.setPebiGridControl(dGridControl);
|
|
|
}
|
|
|
|
|
|
double nmDataAnalyzeManager::getPebiGridControl() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getPebiGridControl();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setIncludedCalculationWells(
|
|
|
const QVector<nmCalculationWellRef>& vecWells)
|
|
|
{
|
|
|
const QVector<nmCalculationWellRef> vecOldWells =
|
|
|
m_oNumericalAnalysisCase.getIncludedWells();
|
|
|
QVector<nmCalculationWellRef> vecValidWells;
|
|
|
|
|
|
// 第一步:Include Other Wells 只保存其他有产量的生产/注入井。
|
|
|
// 无产量井由 Map 自动作为观察井加入,不能混入手工包含列表。
|
|
|
for(int nIndex = 0; nIndex < vecWells.size(); ++nIndex) {
|
|
|
const nmCalculationWellRef& oWellRef = vecWells[nIndex];
|
|
|
nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode);
|
|
|
if(isSupportedNumericalWell(pWellData) &&
|
|
|
oWellRef.m_sWellCode != getPrimaryWellCode() &&
|
|
|
pWellData->getFlowPoints().size() >= 2) {
|
|
|
vecValidWells.append(nmCalculationWellRef(
|
|
|
oWellRef.m_sWellCode,
|
|
|
NM_CaseWell_RateControlled));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第二步:分析方案内部负责按 WellCode 去重,并根据井集合变化使网格失效。
|
|
|
m_oNumericalAnalysisCase.setIncludedWells(vecValidWells);
|
|
|
const QVector<nmCalculationWellRef> vecNewWells =
|
|
|
m_oNumericalAnalysisCase.getIncludedWells();
|
|
|
bool bChanged = vecOldWells.size() != vecNewWells.size();
|
|
|
for(int nIndex = 0;
|
|
|
!bChanged && nIndex < vecNewWells.size();
|
|
|
++nIndex) {
|
|
|
bChanged = vecOldWells[nIndex].m_sWellCode !=
|
|
|
vecNewWells[nIndex].m_sWellCode ||
|
|
|
vecOldWells[nIndex].m_eMode !=
|
|
|
vecNewWells[nIndex].m_eMode;
|
|
|
}
|
|
|
if(bChanged) {
|
|
|
// 第三步:这里只通知当前输入变化,不修改独立快照中的结果井选择。
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QVector<nmCalculationWellRef>
|
|
|
nmDataAnalyzeManager::getIncludedCalculationWells() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getIncludedWells();
|
|
|
}
|
|
|
|
|
|
QVector<nmCalculationWellRef>
|
|
|
nmDataAnalyzeManager::getEffectiveCalculationWells() const
|
|
|
{
|
|
|
QVector<nmCalculationWellRef> vecEffectiveWells;
|
|
|
QSet<QString> setEffectiveWellCodes;
|
|
|
|
|
|
// 第一步:主分析井始终排在第一位。它的主动/观察角色由当前方案保存,
|
|
|
// 不受 Include Other Wells 开关控制。
|
|
|
const QString sPrimaryWellCode = getPrimaryWellCode();
|
|
|
nmDataWellBase* pPrimaryWell = findWellByCode(sPrimaryWellCode);
|
|
|
if(isSupportedNumericalWell(pPrimaryWell)) {
|
|
|
vecEffectiveWells.append(nmCalculationWellRef(
|
|
|
sPrimaryWellCode,
|
|
|
m_oNumericalAnalysisCase.getPrimaryWellMode()));
|
|
|
setEffectiveWellCodes.insert(sPrimaryWellCode);
|
|
|
}
|
|
|
|
|
|
// 第二步:手工包含列表只提供“哪些有产量井被选中”。使用集合查询,
|
|
|
// 最终井顺序仍跟随 Map,保证网格、求解器和结果下拉框顺序稳定。
|
|
|
QSet<QString> setIncludedRateWellCodes;
|
|
|
if(getIncludeOtherWells()) {
|
|
|
const QVector<nmCalculationWellRef> vecIncludedWells =
|
|
|
m_oNumericalAnalysisCase.getIncludedWells();
|
|
|
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
|
|
|
setIncludedRateWellCodes.insert(
|
|
|
vecIncludedWells[nIndex].m_sWellCode);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第三步:遍历 Map 中的其他真实井。无产量井无条件作为观察井加入;
|
|
|
// 有产量井只有打开 Include Other Wells 且被勾选后才作为主动井加入。
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
if(!isSupportedNumericalWell(pWellData) ||
|
|
|
setEffectiveWellCodes.contains(pWellData->getWellCode())) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
const bool bHasValidRate = pWellData->getFlowPoints().size() >= 2;
|
|
|
if(!bHasValidRate) {
|
|
|
vecEffectiveWells.append(nmCalculationWellRef(
|
|
|
pWellData->getWellCode(),
|
|
|
NM_CaseWell_Observation));
|
|
|
setEffectiveWellCodes.insert(pWellData->getWellCode());
|
|
|
} else if(setIncludedRateWellCodes.contains(
|
|
|
pWellData->getWellCode())) {
|
|
|
vecEffectiveWells.append(nmCalculationWellRef(
|
|
|
pWellData->getWellCode(),
|
|
|
NM_CaseWell_RateControlled));
|
|
|
setEffectiveWellCodes.insert(pWellData->getWellCode());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return vecEffectiveWells;
|
|
|
}
|
|
|
|
|
|
NM_CASE_WELL_MODE nmDataAnalyzeManager::getCalculationWellMode(
|
|
|
const QString& sWellCode) const
|
|
|
{
|
|
|
// 求解角色必须从当前有效井集合读取,不能只查看历史 IncludedWells。
|
|
|
// 这样原有主动井清空产量后会立即转成自动观察井,不会继续索要产量制度。
|
|
|
const QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
if(vecEffectiveWells[nIndex].m_sWellCode == sWellCode) {
|
|
|
return vecEffectiveWells[nIndex].m_eMode;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return NM_CaseWell_Observation;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isWellSelectedForCalculation(
|
|
|
const QString& sWellCode) const
|
|
|
{
|
|
|
QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
if(vecEffectiveWells[nIndex].m_sWellCode == sWellCode) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearSolverWellOrder()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.clearSolverWellOrder();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::appendSolverWell(const nmSolverWellRef& oWellRef)
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.appendSolverWell(oWellRef);
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::insertSolverWell(
|
|
|
int nIndex,
|
|
|
const nmSolverWellRef& oWellRef)
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.insertSolverWell(nIndex, oWellRef);
|
|
|
}
|
|
|
|
|
|
QVector<nmSolverWellRef> nmDataAnalyzeManager::getSolverWellOrder() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getSolverWellOrder();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setSolverWellOrder(
|
|
|
const QVector<nmSolverWellRef>& vecSolverWellOrder)
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder);
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::commitPebiGridResult(
|
|
|
quint64 nGridInputRevision,
|
|
|
const QVector<nmSolverWellRef>& vecSolverWellOrder,
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pGrid)
|
|
|
{
|
|
|
// 第一步:空井顺序或空网格都不是可求解成果,不能破坏当前有效网格。
|
|
|
if(vecSolverWellOrder.isEmpty() || pGrid == nullptr ||
|
|
|
pGrid->GetNumberOfCells() <= 0) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:先检查后台任务捕获的输入版本。该函数只在主线程调用,检查和
|
|
|
// 后续写入之间不会处理界面事件,因此可以作为一次不可分割的成果提交。
|
|
|
if(m_oNumericalAnalysisCase.getGridInputRevision() !=
|
|
|
nGridInputRevision) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:整体替换 DLL 槽位顺序,再登记同一输入版本并替换 VTK 网格。
|
|
|
// setSolverWellOrder() 会统一重编号,避免逐井追加期间暴露半成品顺序。
|
|
|
m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder);
|
|
|
if(!m_oNumericalAnalysisCase.markGridBuiltIfCurrent(
|
|
|
nGridInputRevision)) {
|
|
|
return false;
|
|
|
}
|
|
|
m_pVtkUnstructuredGrid = pGrid;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::beginPebiGridGeneration(
|
|
|
quint64 nGridInputRevision)
|
|
|
{
|
|
|
if(nGridInputRevision == 0) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 该状态只协调同一主线程上的窗口流程;后台任务不得直接读写 DataManager。
|
|
|
m_bPebiGridGenerationActive = true;
|
|
|
m_nPebiGridGenerationRevision = nGridInputRevision;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::endPebiGridGeneration(bool bSucceeded)
|
|
|
{
|
|
|
if(!m_bPebiGridGenerationActive) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const quint64 nGridInputRevision = m_nPebiGridGenerationRevision;
|
|
|
m_bPebiGridGenerationActive = false;
|
|
|
m_nPebiGridGenerationRevision = 0;
|
|
|
emit sigPebiGridGenerationFinished(nGridInputRevision, bSucceeded);
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isPebiGridGenerationActive() const
|
|
|
{
|
|
|
return m_bPebiGridGenerationActive;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::markPebiGridBuilt()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.markGridBuilt();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::markPebiGridBuiltIfCurrent(
|
|
|
quint64 nGridInputRevision)
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.markGridBuiltIfCurrent(
|
|
|
nGridInputRevision);
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::invalidatePebiGrid()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::invalidatePebiResults()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isPebiGridValid() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.isGridValid();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::hasPebiResults() const
|
|
|
{
|
|
|
// 结果存在性只有一个事实来源:Manager 是否持有完整只读快照。
|
|
|
return !m_pPebiResultSnapshot.isNull();
|
|
|
}
|
|
|
|
|
|
QSharedPointer<const nmPebiResultSnapshot>
|
|
|
nmDataAnalyzeManager::getPebiResultSnapshot() const
|
|
|
{
|
|
|
return m_pPebiResultSnapshot;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::hasPebiResultSnapshot() const
|
|
|
{
|
|
|
return !m_pPebiResultSnapshot.isNull();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isPebiResultSnapshotCurrent() const
|
|
|
{
|
|
|
return !m_pPebiResultSnapshot.isNull() &&
|
|
|
m_pPebiResultSnapshot->getGridInputRevision() ==
|
|
|
m_oNumericalAnalysisCase.getGridInputRevision() &&
|
|
|
m_pPebiResultSnapshot->getResultInputRevision() ==
|
|
|
m_oNumericalAnalysisCase.getResultInputRevision();
|
|
|
}
|
|
|
|
|
|
QString nmDataAnalyzeManager::getCurrentResultWellInstanceId() const
|
|
|
{
|
|
|
return m_sCurrentResultWellInstanceId;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::setCurrentResultWellInstanceId(
|
|
|
const QString& sWellInstanceId)
|
|
|
{
|
|
|
// 结果井选择属于快照查看状态,不能反向修改当前分析主井。
|
|
|
if(m_pPebiResultSnapshot.isNull())
|
|
|
{
|
|
|
if(!sWellInstanceId.isEmpty())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
m_sCurrentResultWellInstanceId.clear();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QString sValidatedWellInstanceId = sWellInstanceId;
|
|
|
if(sValidatedWellInstanceId.isEmpty())
|
|
|
{
|
|
|
const QStringList& listDisplayWellIds =
|
|
|
m_pPebiResultSnapshot->getDisplayWellInstanceIds();
|
|
|
sValidatedWellInstanceId = listDisplayWellIds.isEmpty()
|
|
|
? QString() : listDisplayWellIds.first();
|
|
|
}
|
|
|
if(!sValidatedWellInstanceId.isEmpty() &&
|
|
|
!m_pPebiResultSnapshot->isDisplayWell(sValidatedWellInstanceId))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
if(m_sCurrentResultWellInstanceId == sValidatedWellInstanceId)
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
m_sCurrentResultWellInstanceId = sValidatedWellInstanceId;
|
|
|
emit sigPebiResultSnapshotChanged();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::commitPebiResultSnapshot(
|
|
|
QSharedPointer<nmPebiResultSnapshot>& pCandidate,
|
|
|
QString* pError)
|
|
|
{
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
pError->clear();
|
|
|
}
|
|
|
|
|
|
QString sError;
|
|
|
QString sNextWellInstanceId;
|
|
|
QSharedPointer<const nmPebiResultSnapshot> pPublishedCandidate;
|
|
|
try
|
|
|
{
|
|
|
// 发布只能发生在 Manager 所属线程,避免工作线程直接替换 UI 正在读取的引用。
|
|
|
if(QThread::currentThread() != thread())
|
|
|
{
|
|
|
sError = "PEBI result snapshot must be committed on its owner thread.";
|
|
|
}
|
|
|
else if(pCandidate.isNull() || !pCandidate->isComplete())
|
|
|
{
|
|
|
sError = "PEBI result snapshot candidate is incomplete.";
|
|
|
}
|
|
|
else if(pCandidate->getGridInputRevision() !=
|
|
|
m_oNumericalAnalysisCase.getGridInputRevision() ||
|
|
|
pCandidate->getResultInputRevision() !=
|
|
|
m_oNumericalAnalysisCase.getResultInputRevision() ||
|
|
|
!m_oNumericalAnalysisCase.isGridValid())
|
|
|
{
|
|
|
sError = "PEBI result snapshot input revisions are stale.";
|
|
|
}
|
|
|
|
|
|
// 即使外部遗漏输入版本递增,也不允许已删除井或同编码新井接收旧任务结果。
|
|
|
QSet<QString> setWellInstanceIds;
|
|
|
for(int nIndex = 0;
|
|
|
sError.isEmpty() && nIndex < pCandidate->getWellCount();
|
|
|
++nIndex)
|
|
|
{
|
|
|
const nmPebiResultWellSnapshot* pWell =
|
|
|
pCandidate->getWellAt(nIndex);
|
|
|
nmDataWellBase* pLiveWell = pWell == NULL ? NULL :
|
|
|
findWellByCode(pWell->m_sWellCode);
|
|
|
if(pWell == NULL || pLiveWell == NULL ||
|
|
|
pLiveWell->getWellInstanceId() != pWell->m_sWellInstanceId ||
|
|
|
setWellInstanceIds.contains(pWell->m_sWellInstanceId))
|
|
|
{
|
|
|
sError = "PEBI result well identity is stale.";
|
|
|
break;
|
|
|
}
|
|
|
setWellInstanceIds.insert(pWell->m_sWellInstanceId);
|
|
|
}
|
|
|
|
|
|
// 当前网格登记的槽位必须与候选完全相同,防止相同版本下的非法外部改写。
|
|
|
const QVector<nmSolverWellRef> vecCurrentOrder =
|
|
|
m_oNumericalAnalysisCase.getSolverWellOrder();
|
|
|
if(sError.isEmpty() &&
|
|
|
vecCurrentOrder.size() != pCandidate->getSolverSlotCount())
|
|
|
{
|
|
|
sError = "PEBI solver slot order has changed.";
|
|
|
}
|
|
|
for(int nIndex = 0;
|
|
|
sError.isEmpty() && nIndex < vecCurrentOrder.size();
|
|
|
++nIndex)
|
|
|
{
|
|
|
const nmSolverWellRef& oCurrent = vecCurrentOrder[nIndex];
|
|
|
const nmPebiResultSolverSlot* pSaved =
|
|
|
pCandidate->getSolverSlotAt(nIndex);
|
|
|
if(pSaved == NULL ||
|
|
|
oCurrent.m_nSolverIndex != pSaved->m_nSolverIndex ||
|
|
|
oCurrent.m_eEntryKind != pSaved->m_eEntryKind ||
|
|
|
oCurrent.m_sWellCode != pSaved->m_sWellCode ||
|
|
|
oCurrent.m_eWellType != pSaved->m_eWellType)
|
|
|
{
|
|
|
sError = "PEBI solver slot order has changed.";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(sError.isEmpty())
|
|
|
{
|
|
|
pPublishedCandidate =
|
|
|
QSharedPointer<const nmPebiResultSnapshot>(pCandidate);
|
|
|
// 新结果仍包含旧选择时保持用户上下文,否则退到第一口可显示井。
|
|
|
sNextWellInstanceId = m_sCurrentResultWellInstanceId;
|
|
|
if(!pPublishedCandidate->isDisplayWell(sNextWellInstanceId))
|
|
|
{
|
|
|
const QStringList& listDisplayWellIds =
|
|
|
pPublishedCandidate->getDisplayWellInstanceIds();
|
|
|
sNextWellInstanceId = listDisplayWellIds.isEmpty()
|
|
|
? QString() : listDisplayWellIds.first();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
// 校验期间内存不足时尚未触碰发布点,旧快照和旧结果井选择保持不变。
|
|
|
pCandidate.clear();
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
*pError = "Not enough memory to validate PEBI result snapshot.";
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!sError.isEmpty())
|
|
|
{
|
|
|
// 提交失败也要消除可写别名,但旧快照和旧结果井选择保持不变。
|
|
|
pCandidate.clear();
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
*pError = sError;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 发布点只替换只读引用;先前快照由仍在查看它的窗口延迟释放。
|
|
|
m_pPebiResultSnapshot = pPublishedCandidate;
|
|
|
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
|
|
|
pCandidate.clear();
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
emit sigPebiResultSnapshotChanged();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells()
|
|
|
{
|
|
|
QWeakPointer<const nmPebiResultSnapshot> pWeakSnapshot;
|
|
|
if(!m_pPebiResultSnapshot.isNull())
|
|
|
{
|
|
|
pWeakSnapshot = QWeakPointer<const nmPebiResultSnapshot>(
|
|
|
m_pPebiResultSnapshot);
|
|
|
}
|
|
|
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex)
|
|
|
{
|
|
|
nmDataWellBase* pWell = m_vWellData[nIndex];
|
|
|
if(pWell == NULL)
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
const bool bBelongsToSnapshot =
|
|
|
!m_pPebiResultSnapshot.isNull() &&
|
|
|
m_pPebiResultSnapshot->findWell(
|
|
|
pWell->getWellInstanceId()) != NULL;
|
|
|
pWell->bindPebiResultSnapshot(bBelongsToSnapshot
|
|
|
? pWeakSnapshot
|
|
|
: QWeakPointer<const nmPebiResultSnapshot>());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearPebiResultSnapshot()
|
|
|
{
|
|
|
if(m_pPebiResultSnapshot.isNull() &&
|
|
|
m_sCurrentResultWellInstanceId.isEmpty())
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_pPebiResultSnapshot.clear();
|
|
|
m_sCurrentResultWellInstanceId.clear();
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
emit sigPebiResultSnapshotChanged();
|
|
|
}
|
|
|
|
|
|
// 设置当前查看井
|
|
|
void nmDataAnalyzeManager::setCurWellData(nmDataWellBase * wellData)
|
|
|
{
|
|
|
m_pCurDataWell = wellData;
|
|
|
}
|
|
|
|
|
|
// 获取当前查看井
|
|
|
nmDataWellBase* nmDataAnalyzeManager::getCurWellData()
|
|
|
{
|
|
|
return m_pCurDataWell;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::notifyDataChanged()
|
|
|
{
|
|
|
// 现有属性编辑链路尚未细分“几何变化”和“仅求解输入变化”,
|
|
|
// 为保证正确性先采用保守策略:任一计算数据变化都使网格失效。
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::notifyGeometryListChanged()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
emit sigGeometryListChanged();
|
|
|
emit dataChanged();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::notifyParameterObjectNameChanged()
|
|
|
{
|
|
|
emit sigGeometryListChanged();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setDisplaySettings(const QVector<DisplaySetting>& displaySettings)
|
|
|
{
|
|
|
m_vecDisplaySettings = displaySettings;
|
|
|
}
|
|
|
|
|
|
QVector<DisplaySetting> nmDataAnalyzeManager::getDisplaySettings() const
|
|
|
{
|
|
|
return m_vecDisplaySettings;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::updateCategoryDisplaySetting(const QString & categoryName, const CategoryDisplayInfo & info)
|
|
|
{
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == categoryName) {
|
|
|
m_vecDisplaySettings[i].value = info;
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::initDefaultDisplaySettings()
|
|
|
{
|
|
|
m_vecDisplaySettings.clear();
|
|
|
|
|
|
// Contour
|
|
|
DisplaySetting contourSetting;
|
|
|
contourSetting.key = tr("Contour");
|
|
|
contourSetting.value.isRootChecked = true; // 默认选中
|
|
|
m_vecDisplaySettings.append(contourSetting);
|
|
|
|
|
|
// Faults
|
|
|
DisplaySetting faultsSetting;
|
|
|
faultsSetting.key = tr("Faults");
|
|
|
faultsSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(faultsSetting);
|
|
|
|
|
|
// Images
|
|
|
DisplaySetting imagesSetting;
|
|
|
imagesSetting.key = tr("Images");
|
|
|
imagesSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(imagesSetting);
|
|
|
|
|
|
// Wells
|
|
|
DisplaySetting wellsSetting;
|
|
|
wellsSetting.key = tr("Wells");
|
|
|
wellsSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(wellsSetting);
|
|
|
|
|
|
// Fractures
|
|
|
DisplaySetting fracturesSetting;
|
|
|
fracturesSetting.key = tr("Fractures");
|
|
|
fracturesSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(fracturesSetting);
|
|
|
|
|
|
// Limits
|
|
|
DisplaySetting limitsSetting;
|
|
|
limitsSetting.key = tr("Regions");
|
|
|
limitsSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(limitsSetting);
|
|
|
|
|
|
// RegionMarks
|
|
|
DisplaySetting regionsSetting;
|
|
|
regionsSetting.key = tr("RegionMarks");
|
|
|
regionsSetting.value.isRootChecked = true;
|
|
|
m_vecDisplaySettings.append(regionsSetting);
|
|
|
}
|
|
|
|
|
|
CategoryDisplayInfo* nmDataAnalyzeManager::findCategoryDisplayInfo(const QString & categoryName)
|
|
|
{
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == categoryName) {
|
|
|
return &m_vecDisplaySettings[i].value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::appendDisplaySetting(const DisplaySetting & displaySetting)
|
|
|
{
|
|
|
// 检查是否已存在相同key的设置
|
|
|
foreach(const auto& setting, m_vecDisplaySettings) {
|
|
|
if(setting.key == displaySetting.key) {
|
|
|
return false; // 已存在相同key,添加失败
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 添加新的显示设置
|
|
|
m_vecDisplaySettings.append(displaySetting);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeDisplaySetting(const QString & categoryName)
|
|
|
{
|
|
|
// 遍历查找并删除指定key的设置
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == categoryName) {
|
|
|
m_vecDisplaySettings.remove(i);
|
|
|
return true; // 删除成功
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false; // 未找到指定key的设置,删除失败
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::addChildItemsToCategory(const QString & parentCategory,
|
|
|
const QStringList & childNames,
|
|
|
bool defaultChecked)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个子项名称创建ChildCategoryDisplayInfo并添加到父类别
|
|
|
for(int j = 0; j < childNames.size(); ++j) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = childNames.at(j);
|
|
|
childInfo.isChecked = defaultChecked;
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked; // 子项启用状态取决于父项
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::refreshChildItemsDisplay()
|
|
|
{
|
|
|
// 步骤1: 清空所有父节点的子节点
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
m_vecDisplaySettings[i].value.childItems.clear();
|
|
|
}
|
|
|
|
|
|
// 更新边界和位图
|
|
|
CategoryDisplayInfo outlineInfo, imageInfo;
|
|
|
outlineInfo.isRootChecked = m_outlineData->getPlotVisible();
|
|
|
imageInfo.isRootChecked = this->m_backgroundImageInfo.bIsVisible;
|
|
|
this->updateCategoryDisplaySetting(tr("Contour"), outlineInfo);
|
|
|
this->updateCategoryDisplaySetting(tr("Images"), imageInfo);
|
|
|
|
|
|
// 步骤2: 重新添加子节点,根据不同数据容器中的对象名称生成子节点
|
|
|
// 为 "Wells" 分类添加子节点
|
|
|
addWellChildItemsToCategory(tr("Wells"));
|
|
|
|
|
|
// 为 "Faults" 分类添加子节点
|
|
|
addFaultChildItemsToCategory(tr("Faults"));
|
|
|
|
|
|
// 为 "Fractures" 分类添加子节点
|
|
|
addFractureChildItemsToCategory(tr("Fractures"));
|
|
|
|
|
|
// 为 "Regions" 分类添加子节点
|
|
|
addRegionChildItemsToCategory(tr("Regions"));
|
|
|
|
|
|
// 为 "RegionMarks" 分类添加子节点
|
|
|
addRegionMarkChildItemsToCategory(tr("RegionMarks"));
|
|
|
}
|
|
|
|
|
|
// 为Wells分类添加子节点
|
|
|
void nmDataAnalyzeManager::addWellChildItemsToCategory(const QString & parentCategory)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个井对象创建子节点
|
|
|
for(int j = 0; j < m_vWellData.size(); ++j) {
|
|
|
if(m_vWellData[j]) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = m_vWellData[j]->getWellName();
|
|
|
childInfo.isChecked = m_vWellData[j]->getPlotVisible();
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked;
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为Faults分类添加子节点
|
|
|
void nmDataAnalyzeManager::addFaultChildItemsToCategory(const QString & parentCategory)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个断层对象创建子节点
|
|
|
for(int j = 0; j < m_vFaultData.size(); ++j) {
|
|
|
if(m_vFaultData[j]) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = m_vFaultData[j]->getFaultName();
|
|
|
childInfo.isChecked = m_vFaultData[j]->getPlotVisible();
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked;
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为Fractures分类添加子节点
|
|
|
void nmDataAnalyzeManager::addFractureChildItemsToCategory(const QString & parentCategory)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个裂缝对象创建子节点
|
|
|
for(int j = 0; j < m_vFractureData.size(); ++j) {
|
|
|
if(m_vFractureData[j]) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = m_vFractureData[j]->getFractureName();
|
|
|
childInfo.isChecked = m_vFractureData[j]->getPlotVisible();
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked;
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为Regions分类添加子节点
|
|
|
void nmDataAnalyzeManager::addRegionChildItemsToCategory(const QString & parentCategory)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个复合区对象创建子节点
|
|
|
for(int j = 0; j < m_vRegionData.size(); ++j) {
|
|
|
if(m_vRegionData[j]) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = m_vRegionData[j]->getRegoinName();
|
|
|
childInfo.isChecked = m_vRegionData[j]->getPlotVisible();
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked;
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为RegionMarks分类添加子节点
|
|
|
void nmDataAnalyzeManager::addRegionMarkChildItemsToCategory(const QString & parentCategory)
|
|
|
{
|
|
|
// 查找父类别
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
if(m_vecDisplaySettings[i].key == parentCategory) {
|
|
|
// 为每个区域标记对象创建子节点
|
|
|
for(int j = 0; j < m_vRegionMarkData.size(); ++j) {
|
|
|
if(m_vRegionMarkData[j]) {
|
|
|
ChildCategoryDisplayInfo childInfo;
|
|
|
childInfo.name = m_vRegionMarkData[j]->getRegionMarkName();
|
|
|
childInfo.isChecked = m_vRegionMarkData[j]->getPlotVisible();
|
|
|
childInfo.isEnabled = m_vecDisplaySettings[i].value.isRootChecked;
|
|
|
|
|
|
m_vecDisplaySettings[i].value.childItems.append(childInfo);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setAllCategoriesRootVisibility(bool bIsVisible)
|
|
|
{
|
|
|
for(int i = 0; i < m_vecDisplaySettings.size(); ++i) {
|
|
|
m_vecDisplaySettings[i].value.isRootChecked = bIsVisible;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setBackgroundImageInfo(const BackgroundImageInfo & info)
|
|
|
{
|
|
|
m_backgroundImageInfo = info;
|
|
|
}
|
|
|
|
|
|
const BackgroundImageInfo& nmDataAnalyzeManager::getBackgroundImageInfo() const
|
|
|
{
|
|
|
return m_backgroundImageInfo;
|
|
|
}
|
|
|
|
|
|
QVector<nmPropertyInterpolationDataSet> nmDataAnalyzeManager::getPropertyInterpolationDataSets() const
|
|
|
{
|
|
|
return m_vecPropertyInterpolationDataSets;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setPropertyInterpolationDataSets(
|
|
|
const QVector<nmPropertyInterpolationDataSet>& dataSets)
|
|
|
{
|
|
|
m_vecPropertyInterpolationDataSets = dataSets;
|
|
|
// 属性插值只覆盖求解器单元属性,不改变二维 PEBI 拓扑。
|
|
|
m_oNumericalAnalysisCase.invalidateResults();
|
|
|
}
|
|
|
|
|
|
// 从 JSON 文件读取数据到 C++ 对象
|
|
|
bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
|
|
|
{
|
|
|
rapidjson::Document doc;
|
|
|
|
|
|
// 调用 nmDataJsonTools 读取 JSON 文件到 Document
|
|
|
if(!nmDataJsonTools::ReadDomFromFile(filePath, doc)) {
|
|
|
qDebug() << "Error: Failed to read DOM from file:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
// 新数据模型不兼容旧项目。明确校验版本,避免按井名或对象顺序猜测关联关系。
|
|
|
const int nSupportedProjectVersion = 3;
|
|
|
if(!doc.HasMember("NumericalProjectVersion") ||
|
|
|
!doc["NumericalProjectVersion"].IsInt() ||
|
|
|
doc["NumericalProjectVersion"].GetInt() != nSupportedProjectVersion) {
|
|
|
qWarning() << "Unsupported numerical project version:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
quint64 nSavedGridInputRevision = 0;
|
|
|
quint64 nSavedBuiltGridRevision = 0;
|
|
|
quint64 nSavedResultInputRevision = 0;
|
|
|
if(!doc.HasMember("InputRevisions") ||
|
|
|
!doc["InputRevisions"].IsObject()) {
|
|
|
qWarning() << "Numerical v3 project has no input revisions:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
const rapidjson::Value& oRevisionJson = doc["InputRevisions"];
|
|
|
if(!oRevisionJson.HasMember("GridInputRevision") ||
|
|
|
!oRevisionJson["GridInputRevision"].IsUint64() ||
|
|
|
!oRevisionJson.HasMember("BuiltGridRevision") ||
|
|
|
!oRevisionJson["BuiltGridRevision"].IsUint64() ||
|
|
|
!oRevisionJson.HasMember("ResultInputRevision") ||
|
|
|
!oRevisionJson["ResultInputRevision"].IsUint64()) {
|
|
|
qWarning() << "Numerical v3 project contains invalid input revisions:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
nSavedGridInputRevision =
|
|
|
oRevisionJson["GridInputRevision"].GetUint64();
|
|
|
nSavedBuiltGridRevision =
|
|
|
oRevisionJson["BuiltGridRevision"].GetUint64();
|
|
|
nSavedResultInputRevision =
|
|
|
oRevisionJson["ResultInputRevision"].GetUint64();
|
|
|
if(nSavedGridInputRevision == 0 || nSavedResultInputRevision == 0 ||
|
|
|
(nSavedBuiltGridRevision != 0 &&
|
|
|
nSavedBuiltGridRevision != nSavedGridInputRevision)) {
|
|
|
qWarning() << "Numerical v3 project input revision values are invalid:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString sSavedPrimaryWellCode;
|
|
|
NM_CASE_WELL_MODE eSavedPrimaryWellMode = NM_CaseWell_Observation;
|
|
|
bool bSavedIncludeOtherWells = false;
|
|
|
double dSavedPebiGridControl = 150.0;
|
|
|
QVector<nmCalculationWellRef> vecSavedIncludedWells;
|
|
|
QVector<nmSolverWellRef> vecSavedSolverOrder;
|
|
|
|
|
|
// 第一步:v3 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。
|
|
|
if(!doc.HasMember("NumericalAnalysisCase") ||
|
|
|
!doc["NumericalAnalysisCase"].IsObject() ||
|
|
|
!doc.HasMember("Wells") ||
|
|
|
!doc["Wells"].IsArray()) {
|
|
|
qWarning() << "Numerical project has no complete analysis case:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const rapidjson::Value& oCaseJson = doc["NumericalAnalysisCase"];
|
|
|
if(!oCaseJson.HasMember("PrimaryWellCode") ||
|
|
|
!oCaseJson["PrimaryWellCode"].IsString() ||
|
|
|
!oCaseJson.HasMember("PrimaryWellMode") ||
|
|
|
!oCaseJson["PrimaryWellMode"].IsInt() ||
|
|
|
!oCaseJson.HasMember("IncludeOtherWells") ||
|
|
|
!oCaseJson["IncludeOtherWells"].IsBool() ||
|
|
|
!oCaseJson.HasMember("PebiGridControl") ||
|
|
|
!oCaseJson["PebiGridControl"].IsNumber() ||
|
|
|
!oCaseJson.HasMember("IncludedWells") ||
|
|
|
!oCaseJson["IncludedWells"].IsArray() ||
|
|
|
!oCaseJson.HasMember("SolverWellOrder") ||
|
|
|
!oCaseJson["SolverWellOrder"].IsArray()) {
|
|
|
qWarning() << "Numerical analysis case contains invalid fields:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
sSavedPrimaryWellCode = QString::fromUtf8(
|
|
|
oCaseJson["PrimaryWellCode"].GetString());
|
|
|
eSavedPrimaryWellMode = static_cast<NM_CASE_WELL_MODE>(
|
|
|
oCaseJson["PrimaryWellMode"].GetInt());
|
|
|
bSavedIncludeOtherWells = oCaseJson["IncludeOtherWells"].GetBool();
|
|
|
dSavedPebiGridControl = oCaseJson["PebiGridControl"].GetDouble();
|
|
|
|
|
|
const bool bPrimaryModeValid =
|
|
|
eSavedPrimaryWellMode == NM_CaseWell_RateControlled ||
|
|
|
eSavedPrimaryWellMode == NM_CaseWell_Observation;
|
|
|
if(sSavedPrimaryWellCode.isEmpty() ||
|
|
|
!bPrimaryModeValid ||
|
|
|
!qIsFinite(dSavedPebiGridControl) ||
|
|
|
dSavedPebiGridControl <= 0.0) {
|
|
|
qWarning() << "Numerical analysis case contains invalid base values:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:先从 JSON 井数组建立唯一 WellCode 到井型的索引,尚不修改当前内存数据。
|
|
|
QMap<QString, NM_WELL_MODEL> mapSavedWellTypes;
|
|
|
QSet<QString> setSavedWellInstanceIds;
|
|
|
const rapidjson::Value& vecWellsJson = doc["Wells"];
|
|
|
for(rapidjson::SizeType nIndex = 0; nIndex < vecWellsJson.Size(); ++nIndex) {
|
|
|
const rapidjson::Value& oWellJson = vecWellsJson[nIndex];
|
|
|
if(!oWellJson.IsObject() ||
|
|
|
!oWellJson.HasMember("WellCode") ||
|
|
|
!oWellJson["WellCode"].IsString() ||
|
|
|
!oWellJson.HasMember("WellInstanceId") ||
|
|
|
!oWellJson["WellInstanceId"].IsString() ||
|
|
|
!oWellJson.HasMember("WellType") ||
|
|
|
!oWellJson["WellType"].IsInt()) {
|
|
|
qWarning() << "Numerical project contains an invalid well entry:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sWellCode = QString::fromUtf8(
|
|
|
oWellJson["WellCode"].GetString());
|
|
|
const QString sWellInstanceId = QString::fromUtf8(
|
|
|
oWellJson["WellInstanceId"].GetString());
|
|
|
const QUuid oWellInstanceUuid(sWellInstanceId);
|
|
|
const QString sNormalizedWellInstanceId =
|
|
|
oWellInstanceUuid.toString().remove('{').remove('}');
|
|
|
const NM_WELL_MODEL eWellType = static_cast<NM_WELL_MODEL>(
|
|
|
oWellJson["WellType"].GetInt());
|
|
|
const bool bSupportedWellType =
|
|
|
eWellType == NM_WELL_MODEL::Vertical_Well ||
|
|
|
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
|
|
|
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
|
|
|
if(sWellCode.isEmpty() || !bSupportedWellType ||
|
|
|
mapSavedWellTypes.contains(sWellCode) ||
|
|
|
oWellInstanceUuid.isNull() ||
|
|
|
sNormalizedWellInstanceId != sWellInstanceId ||
|
|
|
setSavedWellInstanceIds.contains(sWellInstanceId)) {
|
|
|
qWarning() << "Numerical project contains an invalid well identity:"
|
|
|
<< sWellCode << sWellInstanceId;
|
|
|
return false;
|
|
|
}
|
|
|
mapSavedWellTypes.insert(sWellCode, eWellType);
|
|
|
setSavedWellInstanceIds.insert(sWellInstanceId);
|
|
|
}
|
|
|
if(!mapSavedWellTypes.contains(sSavedPrimaryWellCode)) {
|
|
|
qWarning() << "Primary WellCode is absent from Wells:" << sSavedPrimaryWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:校验包含井,禁止空编码、重复编码、主井重复和非法角色。
|
|
|
QSet<QString> setEffectiveWellCodes;
|
|
|
setEffectiveWellCodes.insert(sSavedPrimaryWellCode);
|
|
|
QSet<QString> setIncludedWellCodes;
|
|
|
const rapidjson::Value& vecIncludedJson = oCaseJson["IncludedWells"];
|
|
|
for(rapidjson::SizeType nIndex = 0;
|
|
|
nIndex < vecIncludedJson.Size();
|
|
|
++nIndex) {
|
|
|
const rapidjson::Value& oWellJson = vecIncludedJson[nIndex];
|
|
|
if(!oWellJson.IsObject() ||
|
|
|
!oWellJson.HasMember("WellCode") ||
|
|
|
!oWellJson["WellCode"].IsString() ||
|
|
|
!oWellJson.HasMember("Mode") ||
|
|
|
!oWellJson["Mode"].IsInt()) {
|
|
|
qWarning() << "IncludedWells contains an invalid entry:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sWellCode = QString::fromUtf8(
|
|
|
oWellJson["WellCode"].GetString());
|
|
|
const NM_CASE_WELL_MODE eMode = static_cast<NM_CASE_WELL_MODE>(
|
|
|
oWellJson["Mode"].GetInt());
|
|
|
// Include Other Wells 只保存主动生产/注入井;无产量观察井由 Map
|
|
|
// 自动推导,不允许以观察角色混入这个持久化列表。
|
|
|
const bool bModeValid = eMode == NM_CaseWell_RateControlled;
|
|
|
if(sWellCode == sSavedPrimaryWellCode ||
|
|
|
!mapSavedWellTypes.contains(sWellCode) ||
|
|
|
setIncludedWellCodes.contains(sWellCode) ||
|
|
|
!bModeValid) {
|
|
|
qWarning() << "IncludedWells contains an invalid WellCode or role:"
|
|
|
<< sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
setIncludedWellCodes.insert(sWellCode);
|
|
|
if(bSavedIncludeOtherWells) {
|
|
|
setEffectiveWellCodes.insert(sWellCode);
|
|
|
}
|
|
|
vecSavedIncludedWells.append(nmCalculationWellRef(sWellCode, eMode));
|
|
|
}
|
|
|
|
|
|
// 第四步:此时井的历史流量尚未恢复,无法判断一口 Map 井最终属于
|
|
|
// 主动井还是自动观察井。因此这里只校验真实井存在、井型一致且不重复;
|
|
|
// 历史数据加载完成后,再与最终有效计算井集合做严格一致性校验。
|
|
|
QSet<QString> setOrderedWellCodes;
|
|
|
const rapidjson::Value& vecOrderJson = oCaseJson["SolverWellOrder"];
|
|
|
for(rapidjson::SizeType nIndex = 0;
|
|
|
nIndex < vecOrderJson.Size();
|
|
|
++nIndex) {
|
|
|
const rapidjson::Value& oOrderJson = vecOrderJson[nIndex];
|
|
|
if(!oOrderJson.IsObject() ||
|
|
|
!oOrderJson.HasMember("WellCode") ||
|
|
|
!oOrderJson["WellCode"].IsString() ||
|
|
|
!oOrderJson.HasMember("WellType") ||
|
|
|
!oOrderJson["WellType"].IsInt() ||
|
|
|
!oOrderJson.HasMember("EntryKind") ||
|
|
|
!oOrderJson["EntryKind"].IsInt()) {
|
|
|
qWarning() << "SolverWellOrder contains an invalid entry:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sWellCode = QString::fromUtf8(
|
|
|
oOrderJson["WellCode"].GetString());
|
|
|
const NM_WELL_MODEL eWellType = static_cast<NM_WELL_MODEL>(
|
|
|
oOrderJson["WellType"].GetInt());
|
|
|
const NM_SOLVER_ENTRY_KIND eEntryKind =
|
|
|
static_cast<NM_SOLVER_ENTRY_KIND>(
|
|
|
oOrderJson["EntryKind"].GetInt());
|
|
|
|
|
|
if(eEntryKind == NM_SolverEntry_Well) {
|
|
|
if(!mapSavedWellTypes.contains(sWellCode) ||
|
|
|
setOrderedWellCodes.contains(sWellCode) ||
|
|
|
mapSavedWellTypes.value(sWellCode, NM_WELL_MODEL::Unknow_Well) != eWellType) {
|
|
|
qWarning() << "SolverWellOrder contains an invalid real well:"
|
|
|
<< sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
setOrderedWellCodes.insert(sWellCode);
|
|
|
} else if(eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
if(!sWellCode.isEmpty() || eWellType != NM_WELL_MODEL::Unknow_Well) {
|
|
|
qWarning() << "Manual fracture solver entry is malformed:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
} else {
|
|
|
qWarning() << "SolverWellOrder contains an unknown entry kind:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
vecSavedSolverOrder.append(nmSolverWellRef(
|
|
|
static_cast<int>(nIndex), eWellType, sWellCode, eEntryKind));
|
|
|
}
|
|
|
if(!vecSavedSolverOrder.isEmpty()) {
|
|
|
// 主井和已启用的显式包含井无需依赖历史数据即可确定,必须已经在顺序中。
|
|
|
// 顺序中的其他 Map 井可能是稍后才能识别的无产量观察井,当前不能拒绝。
|
|
|
QSet<QString>::const_iterator oEffectiveIt = setEffectiveWellCodes.constBegin();
|
|
|
for(; oEffectiveIt != setEffectiveWellCodes.constEnd(); ++oEffectiveIt) {
|
|
|
if(!setOrderedWellCodes.contains(*oEffectiveIt)) {
|
|
|
qWarning() << "SolverWellOrder does not contain a configured well:"
|
|
|
<< *oEffectiveIt;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 前置校验已完成,从这里开始替换当前输入。旧快照要到全部 v3 载荷
|
|
|
// 成功后才替换,加载失败或内存不足时仍保留上一套已发布快照。
|
|
|
// 清空现有数据,确保从头加载新数据
|
|
|
foreach(nmDataFracture* pFractureData, m_vFractureData) {
|
|
|
delete pFractureData; // 释放堆上分配的 nmDataFracture 对象
|
|
|
}
|
|
|
|
|
|
m_vFractureData.clear();
|
|
|
|
|
|
foreach(nmDataFault* pFaultData, m_vFaultData) {
|
|
|
delete pFaultData; // 释放堆上分配的 nmDataFault 对象
|
|
|
}
|
|
|
|
|
|
m_vFaultData.clear();
|
|
|
|
|
|
foreach(nmDataRegion* pRegionData, m_vRegionData) {
|
|
|
delete pRegionData; // 释放堆上分配的 nmDataRegion 对象
|
|
|
}
|
|
|
|
|
|
m_vRegionData.clear();
|
|
|
|
|
|
foreach(nmDataRegionMark* pRegionMarkData, m_vRegionMarkData) {
|
|
|
delete pRegionMarkData; // 释放堆上分配的 nmDataRegionMark 对象
|
|
|
}
|
|
|
|
|
|
m_vRegionMarkData.clear();
|
|
|
|
|
|
foreach(nmDataLayer* pLayerData, m_vecLayers) {
|
|
|
delete pLayerData; // 释放堆上分配的 nmDataLayer 对象
|
|
|
}
|
|
|
|
|
|
m_vecLayers.clear();
|
|
|
m_vecPropertyInterpolationDataSets.clear();
|
|
|
|
|
|
if(m_reservoirData) {
|
|
|
delete m_reservoirData;
|
|
|
m_reservoirData = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_axisData) {
|
|
|
delete m_axisData;
|
|
|
m_axisData = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_outlineData) {
|
|
|
delete m_outlineData;
|
|
|
m_outlineData = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_pMixedResults) {
|
|
|
delete m_pMixedResults;
|
|
|
m_pMixedResults = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_pGeoRefData) {
|
|
|
delete m_pGeoRefData;
|
|
|
m_pGeoRefData = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_pTimeStep) {
|
|
|
delete m_pTimeStep;
|
|
|
m_pTimeStep = nullptr;
|
|
|
}
|
|
|
|
|
|
if(m_pSensitiveData) {
|
|
|
delete m_pSensitiveData;
|
|
|
m_pSensitiveData = nullptr;
|
|
|
}
|
|
|
|
|
|
//if(m_pPerCloData) {
|
|
|
// delete m_pPerCloData;
|
|
|
// m_pPerCloData = nullptr;
|
|
|
//}
|
|
|
|
|
|
if(m_pAutomaticFittingData) {
|
|
|
delete m_pAutomaticFittingData;
|
|
|
m_pAutomaticFittingData = nullptr;
|
|
|
}
|
|
|
|
|
|
//if(m_pebiPvtPara) {
|
|
|
// delete m_pebiPvtPara;
|
|
|
// m_pebiPvtPara = nullptr;
|
|
|
//}
|
|
|
|
|
|
foreach(nmDataWellBase* pWellData, m_vWellData) {
|
|
|
delete pWellData; // 释放堆上分配的 nmDataWellBase 对象
|
|
|
}
|
|
|
|
|
|
m_vWellData.clear();
|
|
|
m_oNumericalAnalysisCase.clear();
|
|
|
|
|
|
// 解析 "Fractures" 数组
|
|
|
if(doc.HasMember("Fractures") && doc["Fractures"].IsArray()) {
|
|
|
const rapidjson::Value& fracturesJson = doc["Fractures"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < fracturesJson.Size(); ++i) {
|
|
|
// 动态创建 nmDataFracture 对象,并获取其指针
|
|
|
nmDataFracture* fracture = new nmDataFracture();
|
|
|
fracture->FromJsonValue(fracturesJson[i]); // 调用 nmDataFracture 自身的反序列化方法
|
|
|
m_vFractureData.append(fracture); // 将指针添加到 QVector
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析 "Faults" 数组
|
|
|
if(doc.HasMember("Faults") && doc["Faults"].IsArray()) {
|
|
|
const rapidjson::Value& faultsJson = doc["Faults"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < faultsJson.Size(); ++i) {
|
|
|
// 动态创建 nmDataFault 对象,并获取其指针
|
|
|
nmDataFault* fault = new nmDataFault();
|
|
|
fault->FromJsonValue(faultsJson[i]); // 调用 nmDataFault 自身的反序列化方法
|
|
|
m_vFaultData.append(fault); // 将指针添加到 QVector
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析 "Regions" 数组
|
|
|
if(doc.HasMember("Regions") && doc["Regions"].IsArray()) {
|
|
|
const rapidjson::Value& regionsJson = doc["Regions"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < regionsJson.Size(); ++i) {
|
|
|
// 动态创建 nmDataRegion 对象,并获取其指针
|
|
|
nmDataRegion* region = new nmDataRegion(); // 传入this作为父对象
|
|
|
region->FromJsonValue(regionsJson[i]); // 调用 nmDataRegion 自身的反序列化方法
|
|
|
m_vRegionData.append(region); // 将指针添加到 QVector
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析 "RegionMarks" 数组
|
|
|
if(doc.HasMember("RegionMarks") && doc["RegionMarks"].IsArray()) {
|
|
|
const rapidjson::Value& regionMarksJson = doc["RegionMarks"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < regionMarksJson.Size(); ++i) {
|
|
|
// 动态创建 nmDataRegionMark 对象,并获取其指针
|
|
|
nmDataRegionMark* regionMark = new nmDataRegionMark(); // 传入this作为父对象
|
|
|
regionMark->FromJsonValue(regionMarksJson[i]); // 调用 nmDataRegion 自身的反序列化方法
|
|
|
m_vRegionMarkData.append(regionMark); // 将指针添加到 QVector
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析 "Layers" 数组
|
|
|
if(doc.HasMember("Layers") && doc["Layers"].IsArray()) {
|
|
|
const rapidjson::Value& layersJson = doc["Layers"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < layersJson.Size(); ++i) {
|
|
|
// 动态创建 nmDataRegion 对象,并获取其指针
|
|
|
nmDataLayer* layer = new nmDataLayer(); // 传入this作为父对象
|
|
|
layer->FromJsonValue(layersJson[i]); // 调用 nmDataLayer 自身的反序列化方法
|
|
|
m_vecLayers.append(layer); // 将指针添加到 QVector
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 读取属性插值数据组 */
|
|
|
if(doc.HasMember("PropertyInterpolationDataSets") &&
|
|
|
doc["PropertyInterpolationDataSets"].IsArray()) {
|
|
|
const rapidjson::Value& dataSetsJson = doc["PropertyInterpolationDataSets"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < dataSetsJson.Size(); ++i) {
|
|
|
const rapidjson::Value& dataSetJson = dataSetsJson[i];
|
|
|
if(!dataSetJson.IsObject()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
nmPropertyInterpolationDataSet dataSet;
|
|
|
if(dataSetJson.HasMember("Name") && dataSetJson["Name"].IsString()) {
|
|
|
dataSet.name = QString::fromUtf8(dataSetJson["Name"].GetString());
|
|
|
}
|
|
|
if(dataSetJson.HasMember("Property") && dataSetJson["Property"].IsString()) {
|
|
|
dataSet.property = QString::fromUtf8(dataSetJson["Property"].GetString());
|
|
|
}
|
|
|
if(dataSet.property == "h") {
|
|
|
dataSet.valueDisplayUnit = "m";
|
|
|
}
|
|
|
else if(dataSet.property == "phi") {
|
|
|
dataSet.valueDisplayUnit.clear();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("XDisplayUnit") &&
|
|
|
dataSetJson["XDisplayUnit"].IsString()) {
|
|
|
dataSet.xDisplayUnit = QString::fromUtf8(
|
|
|
dataSetJson["XDisplayUnit"].GetString());
|
|
|
}
|
|
|
if(dataSetJson.HasMember("YDisplayUnit") &&
|
|
|
dataSetJson["YDisplayUnit"].IsString()) {
|
|
|
dataSet.yDisplayUnit = QString::fromUtf8(
|
|
|
dataSetJson["YDisplayUnit"].GetString());
|
|
|
}
|
|
|
if(dataSetJson.HasMember("ValueDisplayUnit") &&
|
|
|
dataSetJson["ValueDisplayUnit"].IsString()) {
|
|
|
dataSet.valueDisplayUnit = QString::fromUtf8(
|
|
|
dataSetJson["ValueDisplayUnit"].GetString());
|
|
|
}
|
|
|
if(dataSetJson.HasMember("RangeDisplayUnit") &&
|
|
|
dataSetJson["RangeDisplayUnit"].IsString()) {
|
|
|
dataSet.rangeDisplayUnit = QString::fromUtf8(
|
|
|
dataSetJson["RangeDisplayUnit"].GetString());
|
|
|
}
|
|
|
if(dataSetJson.HasMember("UseForCalculation") &&
|
|
|
dataSetJson["UseForCalculation"].IsBool()) {
|
|
|
dataSet.useForCalculation = dataSetJson["UseForCalculation"].GetBool();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("ShowPoints") && dataSetJson["ShowPoints"].IsBool()) {
|
|
|
dataSet.showPoints = dataSetJson["ShowPoints"].GetBool();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("ShowLabels") && dataSetJson["ShowLabels"].IsBool()) {
|
|
|
dataSet.showLabels = dataSetJson["ShowLabels"].GetBool();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("Nugget") && dataSetJson["Nugget"].IsNumber()) {
|
|
|
dataSet.nugget = dataSetJson["Nugget"].GetDouble();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("Sill") && dataSetJson["Sill"].IsNumber()) {
|
|
|
dataSet.sill = dataSetJson["Sill"].GetDouble();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("Range") && dataSetJson["Range"].IsNumber()) {
|
|
|
dataSet.range = dataSetJson["Range"].GetDouble();
|
|
|
}
|
|
|
if(dataSetJson.HasMember("Model") && dataSetJson["Model"].IsInt()) {
|
|
|
dataSet.model = dataSetJson["Model"].GetInt();
|
|
|
}
|
|
|
|
|
|
if(dataSetJson.HasMember("Points") && dataSetJson["Points"].IsArray()) {
|
|
|
const rapidjson::Value& pointsJson = dataSetJson["Points"];
|
|
|
for(rapidjson::SizeType pointIndex = 0; pointIndex < pointsJson.Size(); ++pointIndex) {
|
|
|
const rapidjson::Value& pointJson = pointsJson[pointIndex];
|
|
|
if(!pointJson.IsObject() ||
|
|
|
!pointJson.HasMember("X") || !pointJson["X"].IsNumber() ||
|
|
|
!pointJson.HasMember("Y") || !pointJson["Y"].IsNumber() ||
|
|
|
!pointJson.HasMember("Value") || !pointJson["Value"].IsNumber()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
dataSet.points.append(nmPropertyInterpolationPointData(
|
|
|
pointJson["X"].GetDouble(),
|
|
|
pointJson["Y"].GetDouble(),
|
|
|
pointJson["Value"].GetDouble()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
m_vecPropertyInterpolationDataSets.append(dataSet);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 解析 "Reservoir" 对象 */
|
|
|
if(doc.HasMember("Reservoir") && doc["Reservoir"].IsObject()) {
|
|
|
m_reservoirData = new nmDataReservoir;
|
|
|
m_reservoirData->FromJsonValue(doc["Reservoir"]);
|
|
|
|
|
|
// 注册储层属性到 nmAttrRegistry(与 createReservoir 保持一致)
|
|
|
if (m_pAttrRegistry) {
|
|
|
m_pAttrRegistry->clear();
|
|
|
m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness());
|
|
|
m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure());
|
|
|
m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability());
|
|
|
m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity());
|
|
|
m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt());
|
|
|
m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf());
|
|
|
m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi());
|
|
|
m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解析 "Axis" 对象
|
|
|
if(doc.HasMember("Axis") && doc["Axis"].IsObject()) {
|
|
|
m_axisData = new nmDataAxis;
|
|
|
m_axisData->FromJsonValue(doc["Axis"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "Outline" 对象
|
|
|
if(doc.HasMember("Outline") && doc["Outline"].IsObject()) {
|
|
|
m_outlineData = new nmDataOutline;
|
|
|
m_outlineData->FromJsonValue(doc["Outline"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "GeoRef" 对象
|
|
|
if(doc.HasMember("GeoReference") && doc["GeoReference"].IsObject()) {
|
|
|
m_pGeoRefData = new nmDataGeoRef;
|
|
|
m_pGeoRefData->FromJsonValue(doc["GeoReference"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "AutomaticFitting" 对象
|
|
|
if(doc.HasMember("AutomaticFitting") && doc["AutomaticFitting"].IsObject()) {
|
|
|
m_pAutomaticFittingData = new nmDataAutomaticFitting;
|
|
|
m_pAutomaticFittingData->FromJsonValue(doc["AutomaticFitting"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "Sensitive" 对象
|
|
|
if(doc.HasMember("Sensitive") && doc["Sensitive"].IsObject()) {
|
|
|
m_pSensitiveData = new nmDataSensitive;
|
|
|
m_pSensitiveData->FromJsonValue(doc["Sensitive"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "PVT" 对象
|
|
|
//if(doc.HasMember("PVT") && doc["PVT"].IsObject()) {
|
|
|
// m_pebiPvtPara = new nmDataPvtParaForPebi;
|
|
|
// m_pebiPvtPara->FromJsonValue(doc["PVT"]);
|
|
|
//}
|
|
|
|
|
|
// 解析 "MixResult" 对象
|
|
|
if(doc.HasMember("MixResult") && doc["MixResult"].IsObject()) {
|
|
|
m_pMixedResults = new nmDataMixedResults;
|
|
|
m_pMixedResults->FromJsonValue(doc["MixResult"]);
|
|
|
}
|
|
|
|
|
|
// 解析 "Wells" 数组
|
|
|
if(doc.HasMember("Wells") && doc["Wells"].IsArray()) {
|
|
|
const rapidjson::Value& wellsJson = doc["Wells"];
|
|
|
|
|
|
for(rapidjson::SizeType i = 0; i < wellsJson.Size(); ++i) {
|
|
|
//// 动态创建 nmDataFault 对象,并获取其指针
|
|
|
//nmDataWellBase* well = new nmDataWellBase(); // 传入this作为父对象
|
|
|
//well->FromJsonValue(wellsJson[i]); // 调用 nmDataFault 自身的反序列化方法
|
|
|
//m_vWellData.append(well); // 将指针添加到 QVector
|
|
|
|
|
|
// 获取当前井的JSON对象
|
|
|
const rapidjson::Value& wellItemJson = wellsJson[i];
|
|
|
|
|
|
// 临时指针,用于指向新创建的井对象
|
|
|
nmDataWellBase* pWell = nullptr;
|
|
|
// 井类型
|
|
|
NM_WELL_MODEL eWellType = NM_WELL_MODEL::Unknow_Well;
|
|
|
|
|
|
// 1. 读取 wellType
|
|
|
if(wellItemJson.HasMember("WellType") && wellItemJson["WellType"].IsInt()) {
|
|
|
eWellType = static_cast<NM_WELL_MODEL>(wellItemJson["WellType"].GetInt());
|
|
|
}
|
|
|
|
|
|
// 2. 根据 eWellType 动态创建不同的井类型实例
|
|
|
switch(eWellType) {
|
|
|
case NM_WELL_MODEL::Vertical_Well:
|
|
|
pWell = new nmDataVerticalWell;
|
|
|
break;
|
|
|
|
|
|
case NM_WELL_MODEL::Vertical_Fractured_Well:
|
|
|
pWell = new nmDataVerticalFracturedWell;
|
|
|
break;
|
|
|
|
|
|
case NM_WELL_MODEL::Horizontal_Fractured_Well:
|
|
|
pWell = new nmDataHorizontalFracturedWell;
|
|
|
break;
|
|
|
|
|
|
// TODO: 添加其他具体的井类型
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 3. 调用具体井类型的 FromJsonValue 方法进行反序列化
|
|
|
if(pWell) { // 确保 well 对象已成功创建
|
|
|
pWell->FromJsonValue(wellItemJson); // 调用其自身的反序列化方法
|
|
|
m_vWellData.append(pWell); // 将指针添加到 QVector
|
|
|
|
|
|
// TODO:将最后一口井设置为当前井(临时)
|
|
|
//this->setCurWellData(pWell);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 反序列化函数没有返回值,因此必须再次核对实际创建的井目录。
|
|
|
// JSON 预校验目录与内存目录必须在 WellCode、井型和数量上完全一致。
|
|
|
QMap<QString, NM_WELL_MODEL> mapLoadedWellTypes;
|
|
|
QSet<QString> setLoadedWellInstanceIds;
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
|
|
|
mapLoadedWellTypes.contains(pWellData->getWellCode()) ||
|
|
|
!setSavedWellInstanceIds.contains(
|
|
|
pWellData->getWellInstanceId()) ||
|
|
|
setLoadedWellInstanceIds.contains(
|
|
|
pWellData->getWellInstanceId())) {
|
|
|
qWarning() << "Loaded numerical project contains an invalid WellCode:";
|
|
|
return false;
|
|
|
}
|
|
|
mapLoadedWellTypes.insert(pWellData->getWellCode(),
|
|
|
pWellData->getWellType());
|
|
|
setLoadedWellInstanceIds.insert(pWellData->getWellInstanceId());
|
|
|
}
|
|
|
if(mapLoadedWellTypes != mapSavedWellTypes ||
|
|
|
setLoadedWellInstanceIds != setSavedWellInstanceIds) {
|
|
|
qWarning() << "Loaded well directory differs from validated JSON:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 井对象全部加载完成后,先按 WellCode 原样恢复已经通过结构校验的分析方案。
|
|
|
// 这里不能调用会检查流量点的公开 setter,因为历史流量要到 loadNmResult()
|
|
|
// 的二进制恢复阶段才可用;业务有效性在历史数据恢复后统一确认。
|
|
|
nmDataWellBase* pPrimaryWell = findWellByCode(sSavedPrimaryWellCode);
|
|
|
if(pPrimaryWell == nullptr) {
|
|
|
qWarning() << "PrimaryWellCode is missing from numerical project:"
|
|
|
<< sSavedPrimaryWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
m_pCurDataWell = pPrimaryWell;
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellCode(sSavedPrimaryWellCode);
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellMode(eSavedPrimaryWellMode);
|
|
|
m_oNumericalAnalysisCase.setPebiGridControl(dSavedPebiGridControl);
|
|
|
m_oNumericalAnalysisCase.setIncludedWells(vecSavedIncludedWells);
|
|
|
m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells);
|
|
|
m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder);
|
|
|
|
|
|
|
|
|
// 解析 时间步 对象
|
|
|
if(doc.HasMember("TimeStep") && doc["TimeStep"].IsObject()) {
|
|
|
m_pTimeStep = new nmDataTimeStepSetting;
|
|
|
m_pTimeStep->FromJsonValue(doc["TimeStep"]);
|
|
|
}
|
|
|
|
|
|
// 恢复当前求解器模型类型
|
|
|
if(doc.HasMember("SolverModelType") && doc["SolverModelType"].IsInt()) {
|
|
|
m_eSolverModelType = static_cast<NM_SOLVER_MODEL_TYPE>(doc["SolverModelType"].GetInt());
|
|
|
}
|
|
|
if(doc.HasMember("PebiSolverType") && doc["PebiSolverType"].IsInt()) {
|
|
|
setPebiSolverType(doc["PebiSolverType"].GetInt());
|
|
|
}
|
|
|
if(doc.HasMember("PebiOmpThreads") && doc["PebiOmpThreads"].IsInt()) {
|
|
|
setPebiOmpThreads(doc["PebiOmpThreads"].GetInt());
|
|
|
}
|
|
|
if(doc.HasMember("PebiIluReuseSteps") && doc["PebiIluReuseSteps"].IsInt()) {
|
|
|
setPebiIluReuseSteps(doc["PebiIluReuseSteps"].GetInt());
|
|
|
}
|
|
|
if(!m_oNumericalAnalysisCase.restoreInputRevisions(
|
|
|
nSavedGridInputRevision,
|
|
|
nSavedBuiltGridRevision,
|
|
|
nSavedResultInputRevision)) {
|
|
|
qWarning() << "Failed to restore numerical v3 input revisions:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 将 C++ 对象数据写入 JSON 文件
|
|
|
bool nmDataAnalyzeManager::WriteProjectData(
|
|
|
const QString & filePath,
|
|
|
const nmNumericalWindowPayloadReferences* pPayloadReferences)
|
|
|
{
|
|
|
if(pPayloadReferences == NULL) {
|
|
|
qWarning() << "Numerical v3 project requires explicit payload references.";
|
|
|
return false;
|
|
|
}
|
|
|
// 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。
|
|
|
QSet<QString> setWellCodes;
|
|
|
QSet<QString> setWellInstanceIds;
|
|
|
QMap<QString, NM_WELL_MODEL> mapWellTypes;
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
const QString sWellInstanceId = pWellData == nullptr
|
|
|
? QString() : pWellData->getWellInstanceId();
|
|
|
const QUuid oWellInstanceUuid(sWellInstanceId);
|
|
|
const QString sNormalizedWellInstanceId =
|
|
|
oWellInstanceUuid.toString().remove('{').remove('}');
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
|
|
|
setWellCodes.contains(pWellData->getWellCode()) ||
|
|
|
oWellInstanceUuid.isNull() ||
|
|
|
sNormalizedWellInstanceId != sWellInstanceId ||
|
|
|
setWellInstanceIds.contains(sWellInstanceId)) {
|
|
|
qWarning() << "Cannot save numerical project: invalid well identity.";
|
|
|
return false;
|
|
|
}
|
|
|
setWellCodes.insert(pWellData->getWellCode());
|
|
|
setWellInstanceIds.insert(sWellInstanceId);
|
|
|
mapWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType());
|
|
|
}
|
|
|
|
|
|
const QString sPrimaryWellCode = getPrimaryWellCode();
|
|
|
if(!setWellCodes.contains(sPrimaryWellCode) ||
|
|
|
!qIsFinite(getPebiGridControl()) ||
|
|
|
getPebiGridControl() <= 0.0) {
|
|
|
qWarning() << "Cannot save numerical project: invalid primary well or GridControl.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:校验有效计算井和当前网格的求解器顺序使用同一组 WellCode。
|
|
|
QSet<QString> setEffectiveWellCodes;
|
|
|
QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
|
|
|
if(!setWellCodes.contains(oWellRef.m_sWellCode) ||
|
|
|
setEffectiveWellCodes.contains(oWellRef.m_sWellCode)) {
|
|
|
qWarning() << "Cannot save numerical project: invalid effective WellCode:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
setEffectiveWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
}
|
|
|
QSet<QString> setOrderedWellCodes;
|
|
|
QVector<nmSolverWellRef> vecValidatedSolverOrder = getSolverWellOrder();
|
|
|
// 求解器顺序只属于有效网格。网格已经失效时保存空顺序,
|
|
|
// 防止旧顺序与未保存的旧网格组成一个自相矛盾的项目。
|
|
|
if(!isPebiGridValid()) {
|
|
|
vecValidatedSolverOrder.clear();
|
|
|
}
|
|
|
for(int nIndex = 0; nIndex < vecValidatedSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecValidatedSolverOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
if(!setEffectiveWellCodes.contains(oWellRef.m_sWellCode) ||
|
|
|
setOrderedWellCodes.contains(oWellRef.m_sWellCode) ||
|
|
|
mapWellTypes.value(oWellRef.m_sWellCode,
|
|
|
NM_WELL_MODEL::Unknow_Well) != oWellRef.m_eWellType) {
|
|
|
qWarning() << "Cannot save numerical project: invalid solver WellCode:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
setOrderedWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
} else if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
if(!oWellRef.m_sWellCode.isEmpty() ||
|
|
|
oWellRef.m_eWellType != NM_WELL_MODEL::Unknow_Well) {
|
|
|
qWarning() << "Cannot save numerical project: malformed manual fracture entry.";
|
|
|
return false;
|
|
|
}
|
|
|
} else {
|
|
|
qWarning() << "Cannot save numerical project: unknown solver entry kind.";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
if(!vecValidatedSolverOrder.isEmpty() &&
|
|
|
setOrderedWellCodes != setEffectiveWellCodes) {
|
|
|
qWarning() << "Cannot save numerical project: solver order is incomplete.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:基础身份校验通过后再构造 JSON,避免生成无法重新加载的半成品文件。
|
|
|
rapidjson::Document doc;
|
|
|
doc.SetObject(); // 根节点是对象
|
|
|
rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); // 获取内存分配器
|
|
|
|
|
|
doc.AddMember("NumericalProjectVersion", 3, allocator);
|
|
|
rapidjson::Value oRevisionJson(rapidjson::kObjectType);
|
|
|
oRevisionJson.AddMember("GridInputRevision",
|
|
|
m_oNumericalAnalysisCase.getGridInputRevision(), allocator);
|
|
|
oRevisionJson.AddMember("BuiltGridRevision",
|
|
|
m_oNumericalAnalysisCase.getBuiltGridRevision(), allocator);
|
|
|
oRevisionJson.AddMember("ResultInputRevision",
|
|
|
m_oNumericalAnalysisCase.getResultInputRevision(), allocator);
|
|
|
doc.AddMember("InputRevisions", oRevisionJson, allocator);
|
|
|
doc.AddMember("CurrentResultWellInstanceId", numericalJsonString(
|
|
|
m_sCurrentResultWellInstanceId, allocator), allocator);
|
|
|
|
|
|
// 构建 "Fractures" 数组的 JSON Value
|
|
|
rapidjson::Value fracturesJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataFracture 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataFracture* pFractureData, m_vFractureData) {
|
|
|
if(pFractureData) { // 检查指针是否有效
|
|
|
fracturesJsonArray.PushBack(pFractureData->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("Fractures", fracturesJsonArray, allocator);
|
|
|
|
|
|
// 构建 "Faults" 数组的 JSON Value
|
|
|
rapidjson::Value faultsJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataFault 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataFault* pFaultData, m_vFaultData) {
|
|
|
if(pFaultData) { // 检查指针是否有效
|
|
|
faultsJsonArray.PushBack(pFaultData->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("Faults", faultsJsonArray, allocator);
|
|
|
|
|
|
// 构建 "Regions" 数组的 JSON Value
|
|
|
rapidjson::Value regionsJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataRegion 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataRegion* pRegionData, m_vRegionData) {
|
|
|
if(pRegionData) { // 检查指针是否有效
|
|
|
regionsJsonArray.PushBack(pRegionData->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("Regions", regionsJsonArray, allocator);
|
|
|
|
|
|
// 构建 "RegionMarks" 数组的 JSON Value
|
|
|
rapidjson::Value regionMarksJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataRegionMark 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataRegionMark* pRegionMarkData, m_vRegionMarkData) {
|
|
|
if(pRegionMarkData) { // 检查指针是否有效
|
|
|
regionMarksJsonArray.PushBack(pRegionMarkData->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("RegionMarks", regionMarksJsonArray, allocator);
|
|
|
|
|
|
// 构建 "Layers" 数组的 JSON Value
|
|
|
rapidjson::Value layersJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataLayer 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataLayer* pLayerData, m_vecLayers) {
|
|
|
if(pLayerData) { // 检查指针是否有效
|
|
|
layersJsonArray.PushBack(pLayerData->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("Layers", layersJsonArray, allocator);
|
|
|
|
|
|
/* 保存属性插值数据组;数值统一使用m、D等求解器基准单位 */
|
|
|
rapidjson::Value interpolationDataSetsJson(rapidjson::kArrayType);
|
|
|
for(int dataSetIndex = 0;
|
|
|
dataSetIndex < m_vecPropertyInterpolationDataSets.size();
|
|
|
++dataSetIndex) {
|
|
|
const nmPropertyInterpolationDataSet& dataSet =
|
|
|
m_vecPropertyInterpolationDataSets[dataSetIndex];
|
|
|
rapidjson::Value dataSetJson(rapidjson::kObjectType);
|
|
|
|
|
|
QByteArray nameUtf8 = dataSet.name.toUtf8();
|
|
|
QByteArray propertyUtf8 = dataSet.property.toUtf8();
|
|
|
dataSetJson.AddMember("Name",
|
|
|
rapidjson::Value(nameUtf8.constData(), allocator).Move(), allocator);
|
|
|
dataSetJson.AddMember("Property",
|
|
|
rapidjson::Value(propertyUtf8.constData(), allocator).Move(), allocator);
|
|
|
QByteArray xDisplayUnitUtf8 = dataSet.xDisplayUnit.toUtf8();
|
|
|
QByteArray yDisplayUnitUtf8 = dataSet.yDisplayUnit.toUtf8();
|
|
|
QByteArray valueDisplayUnitUtf8 = dataSet.valueDisplayUnit.toUtf8();
|
|
|
QByteArray rangeDisplayUnitUtf8 = dataSet.rangeDisplayUnit.toUtf8();
|
|
|
dataSetJson.AddMember("XDisplayUnit",
|
|
|
rapidjson::Value(xDisplayUnitUtf8.constData(), allocator).Move(), allocator);
|
|
|
dataSetJson.AddMember("YDisplayUnit",
|
|
|
rapidjson::Value(yDisplayUnitUtf8.constData(), allocator).Move(), allocator);
|
|
|
dataSetJson.AddMember("ValueDisplayUnit",
|
|
|
rapidjson::Value(valueDisplayUnitUtf8.constData(), allocator).Move(), allocator);
|
|
|
dataSetJson.AddMember("RangeDisplayUnit",
|
|
|
rapidjson::Value(rangeDisplayUnitUtf8.constData(), allocator).Move(), allocator);
|
|
|
dataSetJson.AddMember("UseForCalculation", dataSet.useForCalculation, allocator);
|
|
|
dataSetJson.AddMember("ShowPoints", dataSet.showPoints, allocator);
|
|
|
dataSetJson.AddMember("ShowLabels", dataSet.showLabels, allocator);
|
|
|
dataSetJson.AddMember("Nugget", dataSet.nugget, allocator);
|
|
|
dataSetJson.AddMember("Sill", dataSet.sill, allocator);
|
|
|
dataSetJson.AddMember("Range", dataSet.range, allocator);
|
|
|
dataSetJson.AddMember("Model", dataSet.model, allocator);
|
|
|
|
|
|
rapidjson::Value pointsJson(rapidjson::kArrayType);
|
|
|
for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) {
|
|
|
const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex];
|
|
|
rapidjson::Value pointJson(rapidjson::kObjectType);
|
|
|
pointJson.AddMember("X", point.x, allocator);
|
|
|
pointJson.AddMember("Y", point.y, allocator);
|
|
|
pointJson.AddMember("Value", point.value, allocator);
|
|
|
pointsJson.PushBack(pointJson, allocator);
|
|
|
}
|
|
|
dataSetJson.AddMember("Points", pointsJson, allocator);
|
|
|
interpolationDataSetsJson.PushBack(dataSetJson, allocator);
|
|
|
}
|
|
|
doc.AddMember("PropertyInterpolationDataSets", interpolationDataSetsJson, allocator);
|
|
|
|
|
|
// 序列化 "Reservoir"
|
|
|
if(m_reservoirData) {
|
|
|
rapidjson::Value reservoirJson(rapidjson::kObjectType);
|
|
|
reservoirJson = m_reservoirData->ToJsonValue(allocator);
|
|
|
doc.AddMember("Reservoir", reservoirJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 序列化 "Axis"
|
|
|
if(m_axisData) {
|
|
|
rapidjson::Value axisJson(rapidjson::kObjectType);
|
|
|
axisJson = m_axisData->ToJsonValue(allocator);
|
|
|
doc.AddMember("Axis", axisJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 序列化 "Outline"
|
|
|
if(m_outlineData) {
|
|
|
rapidjson::Value outlineJson(rapidjson::kObjectType);
|
|
|
outlineJson = m_outlineData->ToJsonValue(allocator);
|
|
|
doc.AddMember("Outline", outlineJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 序列化 "Reservoir"
|
|
|
if(m_pGeoRefData) {
|
|
|
rapidjson::Value geoRefJson(rapidjson::kObjectType);
|
|
|
geoRefJson = m_pGeoRefData->ToJsonValue(allocator);
|
|
|
doc.AddMember("GeoReference", geoRefJson, allocator);
|
|
|
}
|
|
|
|
|
|
//// 序列化 "PerforationClosing"
|
|
|
//if(m_pPerCloData) {
|
|
|
// rapidjson::Value perCloJson(rapidjson::kObjectType);
|
|
|
// perCloJson = m_pPerCloData->ToJsonValue(allocator);
|
|
|
// doc.AddMember("PerforationClosing", perCloJson, allocator);
|
|
|
//}
|
|
|
|
|
|
//// 序列化 "SkinVsRate"
|
|
|
//if(m_pSkinVsRateData) {
|
|
|
// rapidjson::Value skinJson(rapidjson::kObjectType);
|
|
|
// skinJson = m_pSkinVsRateData->ToJsonValue(allocator);
|
|
|
// doc.AddMember("SkinVsRate", skinJson, allocator);
|
|
|
//}
|
|
|
|
|
|
// 序列化 "AutomaticFitting"
|
|
|
if(m_pAutomaticFittingData) {
|
|
|
rapidjson::Value autofitJson(rapidjson::kObjectType);
|
|
|
autofitJson = m_pAutomaticFittingData->ToJsonValue(allocator);
|
|
|
doc.AddMember("AutomaticFitting", autofitJson, allocator);
|
|
|
}
|
|
|
|
|
|
|
|
|
// 序列化 "PVT"
|
|
|
//if(m_pebiPvtPara) {
|
|
|
// rapidjson::Value pvtJson(rapidjson::kObjectType);
|
|
|
// pvtJson = m_pebiPvtPara->ToJsonValue(allocator);
|
|
|
// doc.AddMember("PVT", pvtJson, allocator);
|
|
|
//}
|
|
|
|
|
|
// 序列化 "MixResult"
|
|
|
if(m_pMixedResults) {
|
|
|
rapidjson::Value mixResultsJson(rapidjson::kObjectType);
|
|
|
mixResultsJson = m_pMixedResults->ToJsonValue(allocator);
|
|
|
doc.AddMember("MixResult", mixResultsJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 序列化 "Sensitive"
|
|
|
if(m_pSensitiveData) {
|
|
|
rapidjson::Value sensitiveJson(rapidjson::kObjectType);
|
|
|
sensitiveJson = m_pSensitiveData->ToJsonValue(allocator);
|
|
|
doc.AddMember("Sensitive", sensitiveJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 构建 "Wells" 数组的 JSON Value
|
|
|
rapidjson::Value wellsJsonArray(rapidjson::kArrayType);
|
|
|
|
|
|
// 遍历存储 nmDataWellBase 指针的 QVector,并解引用指针来调用 ToJsonValue 方法
|
|
|
foreach(nmDataWellBase* pWellData, m_vWellData) {
|
|
|
if(nmDataVerticalFracturedWell * pVerticalFracturedWell = dynamic_cast<nmDataVerticalFracturedWell * >(pWellData)) { // 检查指针是否有效
|
|
|
wellsJsonArray.PushBack(pVerticalFracturedWell->ToJsonValue(allocator), allocator);
|
|
|
} else if(nmDataHorizontalFracturedWell * pHorizontalFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell * >(pWellData)) { // 检查指针是否有效
|
|
|
wellsJsonArray.PushBack(pHorizontalFracturedWell->ToJsonValue(allocator), allocator);
|
|
|
} else if(nmDataHorizontalWell * pHorizontalWell = dynamic_cast<nmDataHorizontalWell * >(pWellData)) { // 检查指针是否有效
|
|
|
wellsJsonArray.PushBack(pHorizontalWell->ToJsonValue(allocator), allocator);
|
|
|
} else if(nmDataVerticalWell * pVerticalWell = dynamic_cast<nmDataVerticalWell * >(pWellData)) { // 检查指针是否有效
|
|
|
wellsJsonArray.PushBack(pVerticalWell->ToJsonValue(allocator), allocator);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
doc.AddMember("Wells", wellsJsonArray, allocator);
|
|
|
|
|
|
|
|
|
// 数值分析方案独立保存:井名称不参与身份、顺序和结果映射。
|
|
|
rapidjson::Value oCaseJson(rapidjson::kObjectType);
|
|
|
QByteArray baPrimaryWellCode = getPrimaryWellCode().toUtf8();
|
|
|
oCaseJson.AddMember("PrimaryWellCode",
|
|
|
rapidjson::Value(baPrimaryWellCode.constData(), allocator).Move(),
|
|
|
allocator);
|
|
|
oCaseJson.AddMember("PrimaryWellMode",
|
|
|
static_cast<int>(m_oNumericalAnalysisCase.getPrimaryWellMode()),
|
|
|
allocator);
|
|
|
oCaseJson.AddMember("IncludeOtherWells",
|
|
|
m_oNumericalAnalysisCase.getIncludeOtherWells(),
|
|
|
allocator);
|
|
|
oCaseJson.AddMember("PebiGridControl",
|
|
|
m_oNumericalAnalysisCase.getPebiGridControl(),
|
|
|
allocator);
|
|
|
|
|
|
rapidjson::Value vecIncludedJson(rapidjson::kArrayType);
|
|
|
QVector<nmCalculationWellRef> vecIncludedWells =
|
|
|
getIncludedCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
|
|
|
const nmCalculationWellRef& oWellRef = vecIncludedWells[nIndex];
|
|
|
rapidjson::Value oWellJson(rapidjson::kObjectType);
|
|
|
QByteArray baWellCode = oWellRef.m_sWellCode.toUtf8();
|
|
|
oWellJson.AddMember("WellCode",
|
|
|
rapidjson::Value(baWellCode.constData(), allocator).Move(),
|
|
|
allocator);
|
|
|
oWellJson.AddMember("Mode", static_cast<int>(oWellRef.m_eMode), allocator);
|
|
|
vecIncludedJson.PushBack(oWellJson, allocator);
|
|
|
}
|
|
|
oCaseJson.AddMember("IncludedWells", vecIncludedJson, allocator);
|
|
|
|
|
|
rapidjson::Value vecSolverOrderJson(rapidjson::kArrayType);
|
|
|
QVector<nmSolverWellRef> vecSolverOrder = vecValidatedSolverOrder;
|
|
|
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
|
|
|
rapidjson::Value oOrderJson(rapidjson::kObjectType);
|
|
|
QByteArray baWellCode = oWellRef.m_sWellCode.toUtf8();
|
|
|
oOrderJson.AddMember("WellCode",
|
|
|
rapidjson::Value(baWellCode.constData(), allocator).Move(),
|
|
|
allocator);
|
|
|
oOrderJson.AddMember("WellType",
|
|
|
static_cast<int>(oWellRef.m_eWellType), allocator);
|
|
|
oOrderJson.AddMember("EntryKind",
|
|
|
static_cast<int>(oWellRef.m_eEntryKind), allocator);
|
|
|
vecSolverOrderJson.PushBack(oOrderJson, allocator);
|
|
|
}
|
|
|
oCaseJson.AddMember("SolverWellOrder", vecSolverOrderJson, allocator);
|
|
|
doc.AddMember("NumericalAnalysisCase", oCaseJson, allocator);
|
|
|
|
|
|
// 序列化 时间步数据
|
|
|
if(m_pTimeStep) {
|
|
|
rapidjson::Value timeStepJson(rapidjson::kObjectType);
|
|
|
timeStepJson = m_pTimeStep->ToJsonValue(allocator);
|
|
|
doc.AddMember("TimeStep", timeStepJson, allocator);
|
|
|
}
|
|
|
|
|
|
// 保存当前求解器模型类型
|
|
|
doc.AddMember("SolverModelType", static_cast<int>(m_eSolverModelType), allocator);
|
|
|
doc.AddMember("PebiSolverType", m_nPebiSolverType, allocator);
|
|
|
doc.AddMember("PebiOmpThreads", m_nPebiOmpThreads, allocator);
|
|
|
doc.AddMember("PebiIluReuseSteps", m_nPebiIluReuseSteps, allocator);
|
|
|
|
|
|
// 主 JSON 只保存引用,不通过目录扫描推断网格、历史或快照载荷。
|
|
|
rapidjson::Value oPayloadJson(rapidjson::kObjectType);
|
|
|
if(pPayloadReferences->m_bHasCurrentGrid) {
|
|
|
addNumericalFileReference(oPayloadJson, "CurrentGrid",
|
|
|
pPayloadReferences->m_oCurrentGrid, allocator);
|
|
|
} else {
|
|
|
oPayloadJson.AddMember("CurrentGrid",
|
|
|
rapidjson::Value(rapidjson::kNullType).Move(), allocator);
|
|
|
}
|
|
|
rapidjson::Value oHistoryJson(rapidjson::kArrayType);
|
|
|
QMap<QString, nmNumericalFileReference>::const_iterator oHistoryIt =
|
|
|
pPayloadReferences->m_mapWellHistories.constBegin();
|
|
|
for(; oHistoryIt != pPayloadReferences->m_mapWellHistories.constEnd();
|
|
|
++oHistoryIt) {
|
|
|
rapidjson::Value oWellJson(rapidjson::kObjectType);
|
|
|
oWellJson.AddMember("WellInstanceId", numericalJsonString(
|
|
|
oHistoryIt.key(), allocator), allocator);
|
|
|
addNumericalFileReference(oWellJson, "File",
|
|
|
oHistoryIt.value(), allocator);
|
|
|
oHistoryJson.PushBack(oWellJson, allocator);
|
|
|
}
|
|
|
oPayloadJson.AddMember("WellHistories", oHistoryJson, allocator);
|
|
|
if(pPayloadReferences->m_bHasSnapshot) {
|
|
|
addNumericalFileReference(oPayloadJson, "Snapshot",
|
|
|
pPayloadReferences->m_oSnapshot, allocator);
|
|
|
} else {
|
|
|
oPayloadJson.AddMember("Snapshot",
|
|
|
rapidjson::Value(rapidjson::kNullType).Move(), allocator);
|
|
|
}
|
|
|
doc.AddMember("PayloadFiles", oPayloadJson, allocator);
|
|
|
|
|
|
// 将最终构建好的 Document 写入文件
|
|
|
if(!nmDataJsonTools::WriteDomToFile(doc, filePath)) {
|
|
|
qDebug() << "Error: Failed to write DOM to file:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::saveNmResultV3(
|
|
|
const QString& sWindowDirectory,
|
|
|
nmNumericalFileReference& oMainJsonReference,
|
|
|
QString* pError)
|
|
|
{
|
|
|
if(pError != NULL) {
|
|
|
pError->clear();
|
|
|
}
|
|
|
oMainJsonReference = nmNumericalFileReference();
|
|
|
|
|
|
try {
|
|
|
if(sWindowDirectory.isEmpty() ||
|
|
|
!ensureDirectoryExists(sWindowDirectory)) {
|
|
|
if(pError != NULL) *pError = "Cannot create numerical v3 window directory.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmNumericalWindowPayloadReferences oReferences;
|
|
|
if(isPebiGridValid()) {
|
|
|
if(m_pVtkUnstructuredGrid == NULL ||
|
|
|
!ensureDirectoryExists(QDir(sWindowDirectory).filePath("Grid"))) {
|
|
|
if(pError != NULL) *pError = "Current PEBI grid is unavailable.";
|
|
|
return false;
|
|
|
}
|
|
|
const QString sCurrentGridPath = QDir(sWindowDirectory).filePath(
|
|
|
"Grid/CurrentGrid.vtu");
|
|
|
if(!nmNumericalResultPersistence::writeGrid(
|
|
|
m_pVtkUnstructuredGrid, sCurrentGridPath, pError) ||
|
|
|
!nmNumericalResultPersistence::buildFileReference(
|
|
|
sCurrentGridPath, "Grid/CurrentGrid.vtu",
|
|
|
oReferences.m_oCurrentGrid)) {
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_bHasCurrentGrid = true;
|
|
|
}
|
|
|
|
|
|
const QString sHistoryDirectory = QDir(sWindowDirectory).filePath(
|
|
|
"WellHistory");
|
|
|
if(!ensureDirectoryExists(sHistoryDirectory)) {
|
|
|
if(pError != NULL) *pError = "Cannot create v3 well history directory.";
|
|
|
return false;
|
|
|
}
|
|
|
QSet<QString> setWellInstanceIds;
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWell = m_vWellData[nIndex];
|
|
|
if(pWell == NULL || pWell->getWellInstanceId().isEmpty() ||
|
|
|
setWellInstanceIds.contains(pWell->getWellInstanceId())) {
|
|
|
if(pError != NULL) *pError = "Live well UUID directory is invalid.";
|
|
|
return false;
|
|
|
}
|
|
|
setWellInstanceIds.insert(pWell->getWellInstanceId());
|
|
|
nmNumericalWellHistoryData oHistory;
|
|
|
oHistory.m_vecPressure = pWell->getHistoryPressure();
|
|
|
oHistory.m_vecLogLog = pWell->getHistoryLogLog();
|
|
|
oHistory.m_vecSemiLog = pWell->getHistorySemiLog();
|
|
|
const QString sRelativePath = QString("WellHistory/%1.bin")
|
|
|
.arg(pWell->getWellInstanceId());
|
|
|
const QString sHistoryPath = QDir(sWindowDirectory).filePath(
|
|
|
sRelativePath);
|
|
|
nmNumericalFileReference oReference;
|
|
|
if(!nmNumericalResultPersistence::writeWellHistory(
|
|
|
sHistoryPath, oHistory, pError) ||
|
|
|
!nmNumericalResultPersistence::buildFileReference(
|
|
|
sHistoryPath, sRelativePath, oReference)) {
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_mapWellHistories.insert(
|
|
|
pWell->getWellInstanceId(), oReference);
|
|
|
}
|
|
|
|
|
|
if(!m_pPebiResultSnapshot.isNull()) {
|
|
|
if(!nmPebiResultSnapshotSerializer::save(
|
|
|
m_pPebiResultSnapshot, sWindowDirectory,
|
|
|
oReferences.m_oSnapshot, pError)) {
|
|
|
return false;
|
|
|
}
|
|
|
oReferences.m_bHasSnapshot = true;
|
|
|
}
|
|
|
|
|
|
// 所有外部载荷成功后才写主 JSON,使引用链不会指向半成品文件。
|
|
|
const QString sMainJsonPath = QDir(sWindowDirectory).filePath(
|
|
|
"Numerical_Parameters.json");
|
|
|
if(!WriteProjectData(sMainJsonPath, &oReferences) ||
|
|
|
!nmNumericalResultPersistence::buildFileReference(
|
|
|
sMainJsonPath, "Numerical_Parameters.json",
|
|
|
oMainJsonReference)) {
|
|
|
if(pError != NULL && pError->isEmpty())
|
|
|
*pError = "Cannot write numerical v3 main JSON.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 返回保存会话前沿主 JSON 的明确引用完整复核本窗口载荷。
|
|
|
return validateNmResultV3Window(
|
|
|
sWindowDirectory, sMainJsonPath, pError);
|
|
|
}
|
|
|
catch(const std::bad_alloc&) {
|
|
|
if(pError != NULL) *pError = "Not enough memory to save numerical v3 result.";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::validateNmResultV3Window(
|
|
|
const QString& sWindowDirectory,
|
|
|
const QString& sMainJsonPath,
|
|
|
QString* pError)
|
|
|
{
|
|
|
if(pError != NULL) {
|
|
|
pError->clear();
|
|
|
}
|
|
|
try {
|
|
|
const QString sExpectedDirectory = QDir::cleanPath(
|
|
|
QFileInfo(sWindowDirectory).absoluteFilePath());
|
|
|
const QFileInfo oMainInfo(sMainJsonPath);
|
|
|
if(!oMainInfo.isFile() ||
|
|
|
QDir::cleanPath(oMainInfo.absolutePath()).compare(
|
|
|
sExpectedDirectory, Qt::CaseInsensitive) != 0) {
|
|
|
if(pError != NULL)
|
|
|
*pError = "Numerical v3 main JSON leaves its window directory.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmNumericalWindowPayloadReferences oReferences;
|
|
|
QString sCurrentResultWellInstanceId;
|
|
|
quint64 nGridInputRevision = 0;
|
|
|
quint64 nBuiltGridRevision = 0;
|
|
|
quint64 nResultInputRevision = 0;
|
|
|
if(!readV3WindowHeader(sMainJsonPath, oReferences,
|
|
|
sCurrentResultWellInstanceId, nGridInputRevision,
|
|
|
nBuiltGridRevision, nResultInputRevision, pError)) {
|
|
|
return false;
|
|
|
}
|
|
|
Q_UNUSED(nGridInputRevision);
|
|
|
Q_UNUSED(nBuiltGridRevision);
|
|
|
Q_UNUSED(nResultInputRevision);
|
|
|
if(!oReferences.m_bHasSnapshot &&
|
|
|
!sCurrentResultWellInstanceId.isEmpty()) {
|
|
|
if(pError != NULL)
|
|
|
*pError = "Numerical v3 result well is selected without a snapshot.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(oReferences.m_bHasCurrentGrid &&
|
|
|
!nmNumericalResultPersistence::validateFileReference(
|
|
|
sWindowDirectory, oReferences.m_oCurrentGrid,
|
|
|
NULL, pError)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QMap<QString, nmNumericalFileReference>::const_iterator oIt =
|
|
|
oReferences.m_mapWellHistories.constBegin();
|
|
|
for(; oIt != oReferences.m_mapWellHistories.constEnd(); ++oIt) {
|
|
|
QString sHistoryPath;
|
|
|
nmNumericalWellHistoryData oValidatedHistory;
|
|
|
if(!nmNumericalResultPersistence::validateFileReference(
|
|
|
sWindowDirectory, oIt.value(), &sHistoryPath, pError) ||
|
|
|
!nmNumericalResultPersistence::readWellHistory(
|
|
|
sHistoryPath, oValidatedHistory, pError)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(oReferences.m_bHasSnapshot &&
|
|
|
!nmPebiResultSnapshotSerializer::validate(
|
|
|
sWindowDirectory, oReferences.m_oSnapshot, pError)) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
catch(const std::bad_alloc&) {
|
|
|
if(pError != NULL)
|
|
|
*pError = "Not enough memory to validate numerical v3 result.";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
|
|
|
{
|
|
|
try {
|
|
|
// v3 先在独立候选对象上完成全部解析和业务校验。
|
|
|
// 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。
|
|
|
nmDataAnalyzeManager oLoadedManager;
|
|
|
oLoadedManager.m_pOwnerFitting = m_pOwnerFitting;
|
|
|
oLoadedManager.initPvtParaFromSubFit();
|
|
|
if(!oLoadedManager.loadNmResultV3InPlace(sLoadAnalDir)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 井构造时不能依赖尚未发布的候选 Manager,完整读取后统一绑定储层。
|
|
|
for(int nIndex = 0; nIndex < oLoadedManager.m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWell = oLoadedManager.m_vWellData[nIndex];
|
|
|
if(pWell != NULL) {
|
|
|
pWell->m_pReservoir = oLoadedManager.m_reservoirData;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
swapLoadedProjectState(oLoadedManager);
|
|
|
rebuildLoadedAttributeRegistry();
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
emit sigPebiResultSnapshotChanged();
|
|
|
return true;
|
|
|
}
|
|
|
catch(const std::bad_alloc&) {
|
|
|
qWarning() << "Not enough memory to stage numerical v3 result.";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadNmResultV3InPlace(
|
|
|
const QString& sLoadAnalDir)
|
|
|
{
|
|
|
try {
|
|
|
// 外部框架仍传入旧的“成果根/窗口标识”逻辑路径;v3 只从根清单解析真实代次。
|
|
|
const QFileInfo oLogicalWindowInfo(QDir::cleanPath(sLoadAnalDir));
|
|
|
const QString sWindowId = oLogicalWindowInfo.fileName();
|
|
|
const QString sResultRootDirectory = oLogicalWindowInfo.dir().absolutePath();
|
|
|
const QString sManifestPath = QDir(sResultRootDirectory).filePath(
|
|
|
"NumericalSaveManifest.json");
|
|
|
if(!QFileInfo(sManifestPath).isFile()) {
|
|
|
const QString sLegacyJsonPath = QDir(sLoadAnalDir).filePath(
|
|
|
"Results/Numerical_Parameters.json");
|
|
|
if(QFileInfo(sLegacyJsonPath).isFile()) {
|
|
|
qWarning() << "Numerical project version 2 is not supported:"
|
|
|
<< sLegacyJsonPath;
|
|
|
} else {
|
|
|
qWarning() << "Numerical v3 root manifest is missing:"
|
|
|
<< sManifestPath;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString sWindowDirectory;
|
|
|
QString sMainJsonPath;
|
|
|
QString sError;
|
|
|
if(!nmNumericalSaveSession::resolveWindowMainJson(
|
|
|
sResultRootDirectory, sWindowId, sWindowDirectory,
|
|
|
sMainJsonPath, NULL, &sError)) {
|
|
|
qWarning() << sError;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmNumericalWindowPayloadReferences oReferences;
|
|
|
QString sSavedResultWellInstanceId;
|
|
|
quint64 nGridInputRevision = 0;
|
|
|
quint64 nBuiltGridRevision = 0;
|
|
|
quint64 nResultInputRevision = 0;
|
|
|
if(!readV3WindowHeader(sMainJsonPath, oReferences,
|
|
|
sSavedResultWellInstanceId, nGridInputRevision,
|
|
|
nBuiltGridRevision, nResultInputRevision, &sError)) {
|
|
|
qWarning() << sError << sMainJsonPath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 所有引用文件先完成哈希和结构读取,任何失败都不能替换已发布快照。
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pLoadedCurrentGrid;
|
|
|
if(oReferences.m_bHasCurrentGrid) {
|
|
|
QString sCurrentGridPath;
|
|
|
if(!nmNumericalResultPersistence::validateFileReference(
|
|
|
sWindowDirectory, oReferences.m_oCurrentGrid,
|
|
|
&sCurrentGridPath, &sError) ||
|
|
|
!nmNumericalResultPersistence::readGrid(
|
|
|
sCurrentGridPath, pLoadedCurrentGrid, &sError)) {
|
|
|
qWarning() << sError;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QMap<QString, nmNumericalWellHistoryData> mapLoadedHistories;
|
|
|
QMap<QString, nmNumericalFileReference>::const_iterator oHistoryIt =
|
|
|
oReferences.m_mapWellHistories.constBegin();
|
|
|
for(; oHistoryIt != oReferences.m_mapWellHistories.constEnd();
|
|
|
++oHistoryIt) {
|
|
|
QString sHistoryPath;
|
|
|
nmNumericalWellHistoryData oHistory;
|
|
|
if(!nmNumericalResultPersistence::validateFileReference(
|
|
|
sWindowDirectory, oHistoryIt.value(),
|
|
|
&sHistoryPath, &sError) ||
|
|
|
!nmNumericalResultPersistence::readWellHistory(
|
|
|
sHistoryPath, oHistory, &sError)) {
|
|
|
qWarning() << sError;
|
|
|
return false;
|
|
|
}
|
|
|
mapLoadedHistories.insert(oHistoryIt.key(), oHistory);
|
|
|
}
|
|
|
|
|
|
QSharedPointer<nmPebiResultSnapshot> pLoadedCandidate;
|
|
|
if(oReferences.m_bHasSnapshot &&
|
|
|
!nmPebiResultSnapshotSerializer::load(
|
|
|
sWindowDirectory, oReferences.m_oSnapshot,
|
|
|
pLoadedCandidate, &sError)) {
|
|
|
qWarning() << sError;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 主 JSON 在外部载荷全部可用后才恢复当前输入;v2 会在版本检查处直接拒绝。
|
|
|
if(!ReadProjectData(sMainJsonPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(m_oNumericalAnalysisCase.getGridInputRevision() !=
|
|
|
nGridInputRevision ||
|
|
|
m_oNumericalAnalysisCase.getBuiltGridRevision() !=
|
|
|
nBuiltGridRevision ||
|
|
|
m_oNumericalAnalysisCase.getResultInputRevision() !=
|
|
|
nResultInputRevision ||
|
|
|
oReferences.m_bHasCurrentGrid !=
|
|
|
m_oNumericalAnalysisCase.isGridValid()) {
|
|
|
qWarning() << "Loaded numerical input revisions and current grid are inconsistent.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 实时井历史按 UUID 一一匹配;井名、井编码和数组顺序都不能替代身份。
|
|
|
if(mapLoadedHistories.size() != m_vWellData.size()) {
|
|
|
qWarning() << "Loaded numerical well history directory is incomplete.";
|
|
|
return false;
|
|
|
}
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWell = m_vWellData[nIndex];
|
|
|
if(pWell == NULL ||
|
|
|
!mapLoadedHistories.contains(pWell->getWellInstanceId())) {
|
|
|
qWarning() << "Loaded numerical well UUID has no history payload.";
|
|
|
return false;
|
|
|
}
|
|
|
const nmNumericalWellHistoryData oHistory =
|
|
|
mapLoadedHistories.take(pWell->getWellInstanceId());
|
|
|
pWell->setHistoryPressure(oHistory.m_vecPressure);
|
|
|
pWell->setHistoryLogLog(oHistory.m_vecLogLog);
|
|
|
pWell->setHistorySemiLog(oHistory.m_vecSemiLog);
|
|
|
}
|
|
|
if(!mapLoadedHistories.isEmpty()) {
|
|
|
qWarning() << "Numerical v3 project contains unreferenced well histories.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
loadWellPreAndFlow();
|
|
|
const QVector<nmCalculationWellRef> vecIncludedWells =
|
|
|
getIncludedCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWell = findWellByCode(
|
|
|
vecIncludedWells[nIndex].m_sWellCode);
|
|
|
if(!isSupportedNumericalWell(pWell) ||
|
|
|
pWell->getFlowPoints().size() < 2) {
|
|
|
qWarning() << "Failed to restore included numerical well:"
|
|
|
<< vecIncludedWells[nIndex].m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!pLoadedCandidate.isNull() &&
|
|
|
!sSavedResultWellInstanceId.isEmpty() &&
|
|
|
!pLoadedCandidate->isDisplayWell(sSavedResultWellInstanceId)) {
|
|
|
qWarning() << "Saved result well UUID is absent from the PEBI snapshot.";
|
|
|
return false;
|
|
|
}
|
|
|
if(pLoadedCandidate.isNull() &&
|
|
|
!sSavedResultWellInstanceId.isEmpty()) {
|
|
|
qWarning() << "Numerical project selects a result well without a snapshot.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 所有可能失败的读取和映射已完成,再一次替换当前网格与规范快照。
|
|
|
m_pVtkUnstructuredGrid = pLoadedCurrentGrid;
|
|
|
|
|
|
if(pLoadedCandidate.isNull()) {
|
|
|
clearPebiResultSnapshot();
|
|
|
} else {
|
|
|
QSharedPointer<const nmPebiResultSnapshot> pPublished(
|
|
|
pLoadedCandidate);
|
|
|
QString sNextWellInstanceId = sSavedResultWellInstanceId;
|
|
|
if(sNextWellInstanceId.isEmpty()) {
|
|
|
const QStringList& listDisplayIds =
|
|
|
pPublished->getDisplayWellInstanceIds();
|
|
|
sNextWellInstanceId = listDisplayIds.isEmpty()
|
|
|
? QString() : listDisplayIds.first();
|
|
|
}
|
|
|
m_pPebiResultSnapshot = pPublished;
|
|
|
m_sCurrentResultWellInstanceId = sNextWellInstanceId;
|
|
|
pLoadedCandidate.clear();
|
|
|
rebindPebiResultSnapshotToWells();
|
|
|
emit sigPebiResultSnapshotChanged();
|
|
|
}
|
|
|
m_bIsLoadData = m_pVtkUnstructuredGrid != NULL ||
|
|
|
!m_pPebiResultSnapshot.isNull();
|
|
|
return true;
|
|
|
}
|
|
|
catch(const std::bad_alloc&) {
|
|
|
qWarning() << "Not enough memory to load numerical v3 result.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::swapLoadedProjectState(
|
|
|
nmDataAnalyzeManager& oLoadedManager)
|
|
|
{
|
|
|
// 这里只交换项目数据所有权。窗口、Plot、后台任务和同步原语仍属于当前 Manager。
|
|
|
qSwap(m_vWellData, oLoadedManager.m_vWellData);
|
|
|
qSwap(m_vFaultData, oLoadedManager.m_vFaultData);
|
|
|
qSwap(m_vFractureData, oLoadedManager.m_vFractureData);
|
|
|
qSwap(m_vRegionData, oLoadedManager.m_vRegionData);
|
|
|
qSwap(m_vRegionMarkData, oLoadedManager.m_vRegionMarkData);
|
|
|
qSwap(m_vecLayers, oLoadedManager.m_vecLayers);
|
|
|
qSwap(m_vecPropertyInterpolationDataSets,
|
|
|
oLoadedManager.m_vecPropertyInterpolationDataSets);
|
|
|
|
|
|
qSwap(m_outlineData, oLoadedManager.m_outlineData);
|
|
|
qSwap(m_axisData, oLoadedManager.m_axisData);
|
|
|
qSwap(m_pAutomaticFittingData,
|
|
|
oLoadedManager.m_pAutomaticFittingData);
|
|
|
qSwap(m_reservoirData, oLoadedManager.m_reservoirData);
|
|
|
qSwap(m_pGeoRefData, oLoadedManager.m_pGeoRefData);
|
|
|
qSwap(m_pSensitiveData, oLoadedManager.m_pSensitiveData);
|
|
|
qSwap(m_pebiPvtPara, oLoadedManager.m_pebiPvtPara);
|
|
|
qSwap(m_pMixedResults, oLoadedManager.m_pMixedResults);
|
|
|
qSwap(m_pTimeStep, oLoadedManager.m_pTimeStep);
|
|
|
qSwap(m_pCurDataWell, oLoadedManager.m_pCurDataWell);
|
|
|
|
|
|
qSwap(m_eSolverModelType, oLoadedManager.m_eSolverModelType);
|
|
|
qSwap(m_nPebiSolverType, oLoadedManager.m_nPebiSolverType);
|
|
|
qSwap(m_nPebiOmpThreads, oLoadedManager.m_nPebiOmpThreads);
|
|
|
qSwap(m_nPebiIluReuseSteps, oLoadedManager.m_nPebiIluReuseSteps);
|
|
|
qSwap(m_oNumericalAnalysisCase,
|
|
|
oLoadedManager.m_oNumericalAnalysisCase);
|
|
|
qSwap(m_pPebiResultSnapshot,
|
|
|
oLoadedManager.m_pPebiResultSnapshot);
|
|
|
qSwap(m_sCurrentResultWellInstanceId,
|
|
|
oLoadedManager.m_sCurrentResultWellInstanceId);
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pGrid =
|
|
|
m_pVtkUnstructuredGrid;
|
|
|
m_pVtkUnstructuredGrid = oLoadedManager.m_pVtkUnstructuredGrid;
|
|
|
oLoadedManager.m_pVtkUnstructuredGrid = pGrid;
|
|
|
qSwap(m_bIsLoadData, oLoadedManager.m_bIsLoadData);
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::rebuildLoadedAttributeRegistry()
|
|
|
{
|
|
|
if(m_pAttrRegistry == NULL) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 注册表对象本身不替换,避免破坏已经建立的 UI 信号连接。
|
|
|
m_pAttrRegistry->clear();
|
|
|
if(m_reservoirData != NULL) {
|
|
|
m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness());
|
|
|
m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure());
|
|
|
m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability());
|
|
|
m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity());
|
|
|
m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt());
|
|
|
m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf());
|
|
|
m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi());
|
|
|
m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi());
|
|
|
}
|
|
|
syncWellAttrs();
|
|
|
syncGeometryAttrs();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath)
|
|
|
{
|
|
|
QDir dir(dirPath);
|
|
|
|
|
|
if(!dir.exists()) {
|
|
|
qDebug() << QString("Directory does not exist, attempting to create: %1").arg(dirPath);
|
|
|
|
|
|
if(!dir.mkpath(dirPath)) {
|
|
|
qDebug() << QString("Failed to create directory: %1").arg(dirPath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
qDebug() << QString("Directory created successfully: %1").arg(dirPath);
|
|
|
} else {
|
|
|
qDebug() << QString("Directory already exists: %1").arg(dirPath);
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::loadWellPreAndFlow()
|
|
|
{
|
|
|
// 获取当前工区里所有的ZxDataWell井
|
|
|
if(zxCurProject == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
|
|
|
|
|
|
foreach(nmDataWellBase* pWell, m_vWellData) {
|
|
|
if(pWell == nullptr) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
QString sWellCode = pWell->getWellCode();
|
|
|
|
|
|
ZxDataWell* pWellData = nullptr;
|
|
|
|
|
|
for(int i = 0; i < wellList.size(); i++) {
|
|
|
ZxDataWell* pCandidateWell = dynamic_cast<ZxDataWell*>(wellList[i]);
|
|
|
|
|
|
if(pCandidateWell != nullptr &&
|
|
|
pCandidateWell->getCode() == sWellCode) {
|
|
|
pWellData = pCandidateWell;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 拿到了井的数据
|
|
|
if(pWellData) {
|
|
|
// 获取当前井的压力、流量数据
|
|
|
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
|
|
|
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
|
|
|
|
|
|
ZxDataGaugeP* pGaugeP = nullptr;
|
|
|
ZxDataGaugeF* pGaugeF = nullptr;
|
|
|
|
|
|
// 遍历压力数据列表
|
|
|
for(int i = 0; i < m_listGaugeP.size(); ++i) {
|
|
|
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 遍历流量数据列表
|
|
|
for(int i = 0; i < m_listGaugeF.size(); ++i) {
|
|
|
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取的压力、流量数据
|
|
|
QVector<QPointF> vecPtsP, vecPtsF;
|
|
|
// 临时存储x,y坐标
|
|
|
VecDouble vecX, vecY;
|
|
|
|
|
|
if(pGaugeP != nullptr) {
|
|
|
// 获取压力数据
|
|
|
if(pGaugeP->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsP.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(pGaugeF != nullptr) {
|
|
|
vecX.clear();
|
|
|
vecY.clear();
|
|
|
|
|
|
// 获取流量数据
|
|
|
if(pGaugeF->getDataVecXY(vecX, vecY)) {
|
|
|
int pointCount = vecX.size();
|
|
|
if(pointCount > vecY.size()) {
|
|
|
pointCount = vecY.size();
|
|
|
}
|
|
|
for(int i = 0; i < pointCount; ++i) {
|
|
|
QPointF pt(vecX[i], vecY[i]);
|
|
|
vecPtsF.append(pt);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置井的压力数据、流量数据
|
|
|
pWell->setPressurePoints(vecPtsP);
|
|
|
pWell->setFlowPoints(vecPtsF);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> nmDataAnalyzeManager::getUnstructuredGrid() const
|
|
|
{
|
|
|
return m_pVtkUnstructuredGrid;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setUnstructuredGrid(vtkSmartPointer<vtkUnstructuredGrid> grid)
|
|
|
{
|
|
|
m_pVtkUnstructuredGrid = grid;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearUnstructuredGrid()
|
|
|
{
|
|
|
// 释放当前分析持有的实时VTK网格对象,关闭网格窗口后不再保留旧网格。
|
|
|
m_pVtkUnstructuredGrid = nullptr;
|
|
|
}
|
|
|
|
|
|
//void nmDataAnalyzeManager::createTimeStep()
|
|
|
//{
|
|
|
// if (m_pTimeStep != nullptr)
|
|
|
// {
|
|
|
// delete m_pTimeStep;
|
|
|
// m_pTimeStep = nullptr;
|
|
|
// }
|
|
|
//
|
|
|
// // 获取当前井下流量的时间范围
|
|
|
// QVector<QPointF> vecFlowPoints;
|
|
|
// if (m_pCurDataWell){
|
|
|
// vecFlowPoints = m_pCurDataWell->getFlowPoints();
|
|
|
// }
|
|
|
//
|
|
|
// // 计算起始时间和终止时间
|
|
|
// // 检查坐标数组是否为空,以防止访问越界
|
|
|
// if (!vecFlowPoints.isEmpty()) {
|
|
|
// // 起始时间就是第一个点的横坐标
|
|
|
// double dStartTime = vecFlowPoints.first().x();
|
|
|
//
|
|
|
// // 终止时间是所有点的横坐标之和
|
|
|
// double dEndTime = 0.0;
|
|
|
// foreach (const QPointF& point, vecFlowPoints) {
|
|
|
// dEndTime += point.x();
|
|
|
// }
|
|
|
//
|
|
|
// m_pTimeStep = new nmDataTimeStepSetting(dStartTime,dEndTime);
|
|
|
// }
|
|
|
//}
|
|
|
|
|
|
nmDataTimeStepSetting* nmDataAnalyzeManager::createTimeStep()
|
|
|
{
|
|
|
if (m_pTimeStep != nullptr)
|
|
|
{
|
|
|
delete m_pTimeStep;
|
|
|
m_pTimeStep = nullptr;
|
|
|
}
|
|
|
|
|
|
m_pTimeStep = new nmDataTimeStepSetting;
|
|
|
return m_pTimeStep;
|
|
|
}
|
|
|
|
|
|
nmDataTimeStepSetting* nmDataAnalyzeManager::getTimeStep()
|
|
|
{
|
|
|
return m_pTimeStep;
|
|
|
}
|
|
|
|
|
|
// 获取许可证路径
|
|
|
void nmDataAnalyzeManager::setLicensePath(const QString& licensePath)
|
|
|
{
|
|
|
m_licensePath = licensePath;
|
|
|
}
|
|
|
|
|
|
QString nmDataAnalyzeManager::getLicensePath() const
|
|
|
{
|
|
|
return m_licensePath;
|
|
|
}
|