|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
|
#include "nmAttrRegistry.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 "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 <vector>
|
|
|
#include "singlePhaseSolver.h"
|
|
|
|
|
|
#include <vtkUnstructuredGridReader.h>
|
|
|
#include <vtkXMLUnstructuredGridReader.h>
|
|
|
#include <vtkUnstructuredGridWriter.h>
|
|
|
#include <vtkXMLUnstructuredGridWriter.h>
|
|
|
#include <vtkNew.h>
|
|
|
|
|
|
#include "nmDataTimeStepSetting.h"
|
|
|
|
|
|
#include <QByteArray>
|
|
|
#include <QCoreApplication>
|
|
|
#include <QDebug>
|
|
|
#include <QDir>
|
|
|
#include <QFile>
|
|
|
#include <QFileInfo>
|
|
|
#include <QMessageBox>
|
|
|
|
|
|
// 井基参数名 → 数据成员访问器的映射描述
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager)
|
|
|
|
|
|
nmDataAnalyzeManager::nmDataAnalyzeManager(): ZxDataObjectBin(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;
|
|
|
m_dScalarRangeP[0] = 0.0;
|
|
|
m_dScalarRangeP[1] = 0.0;
|
|
|
|
|
|
// 初始化混合参数数据(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()
|
|
|
{
|
|
|
// 图元只引用数据,数据统一由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();
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
// 实现 removeWell 函数
|
|
|
bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData)
|
|
|
{
|
|
|
// 第一步:只有当前管理器真正拥有的井才允许删除,非法指针不能先污染分析方案。
|
|
|
if(pWellData == nullptr || !m_vWellData.contains(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(m_oNumericalAnalysisCase.getPrimaryWellCode() == removedWellCode) {
|
|
|
m_oNumericalAnalysisCase.setPrimaryWellCode(QString());
|
|
|
}
|
|
|
|
|
|
if(m_oNumericalAnalysisCase.getCurrentResultWellCode() == removedWellCode) {
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(QString());
|
|
|
}
|
|
|
|
|
|
// 如果删的是当前井,切换到其他有效井;如果没有,则清空当前井指针。
|
|
|
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(pWellData == nullptr) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 公共删井入口中不允许删除当前井,避免活动井引用失效
|
|
|
if(pWellData == getCurWellData()) {
|
|
|
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;
|
|
|
|
|
|
// 遍历并删除所有井对象,兼容旧逻辑可能遗留的重复指针。
|
|
|
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)
|
|
|
{
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataVerticalWell * >(existingWell)) {
|
|
|
if(vWell->getWellName() == newWell.getWellName()) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateVerticalFracturedWells(const QVector<nmDataVerticalFracturedWell>& wells)
|
|
|
{
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataVerticalFracturedWell * >(existingWell)) {
|
|
|
if(vWell->getWellName() == newWell.getWellName()) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::updateHorizontalFracturedWells(const QVector<nmDataHorizontalFracturedWell>& wells)
|
|
|
{
|
|
|
foreach(const auto& newWell, wells) {
|
|
|
foreach(auto* existingWell, m_vWellData) {
|
|
|
if(auto * vWell = dynamic_cast<nmDataHorizontalFracturedWell * >(existingWell)) {
|
|
|
if(vWell->getWellName() == newWell.getWellName()) {
|
|
|
*vWell = newWell; // 调用赋值运算符
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 获取所有直井数据
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
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_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);
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(sPrimaryWellCode);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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())
|
|
|
{
|
|
|
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;
|
|
|
|
|
|
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();
|
|
|
iSubWndFitting* fitting = nmDataAnalyzeManager::getCurrentFitting();
|
|
|
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.getCurrentResultWellCode().isEmpty()) {
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(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()) {
|
|
|
// 开关只影响手工选择的有产量干扰井。无产量观察井始终有效,
|
|
|
// 因此当前结果井只有在新有效集合中确实不存在时才回退到主井。
|
|
|
const QString sCurrentResultWellCode = getCurrentResultWellCode();
|
|
|
if(!sCurrentResultWellCode.isEmpty() &&
|
|
|
!isWellAvailableForResult(sCurrentResultWellCode)) {
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(
|
|
|
getPrimaryWellCode());
|
|
|
}
|
|
|
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) {
|
|
|
// 第三步:取消当前正在查看的主动井后,结果井回退到主井;自动观察井
|
|
|
// 不在 IncludedWells 中,但仍属于有效计算集合,不能被错误回退。
|
|
|
const QString sCurrentResultWellCode = getCurrentResultWellCode();
|
|
|
if(!sCurrentResultWellCode.isEmpty() &&
|
|
|
!isWellAvailableForResult(sCurrentResultWellCode)) {
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(
|
|
|
getPrimaryWellCode());
|
|
|
}
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isWellAvailableForResult(
|
|
|
const QString& sWellCode) const
|
|
|
{
|
|
|
nmDataWellBase* pWellData = findWellByCode(sWellCode);
|
|
|
if(pWellData == nullptr) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第一步:主分析井是结果界面的固定入口,即使当前处于关井段也必须保留。
|
|
|
if(sWellCode == getPrimaryWellCode()) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 第二步:其他井只有同时具备有效产量并以主动井角色参与当前求解时,
|
|
|
// 才能出现在“结果井”列表。所有无流量观察井一律只在内部保存计算结果。
|
|
|
if(pWellData->getFlowPoints().size() < 2) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QVector<nmCalculationWellRef> vecEffectiveWells =
|
|
|
getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
if(vecEffectiveWells[nIndex].m_sWellCode == sWellCode &&
|
|
|
vecEffectiveWells[nIndex].m_eMode == NM_CaseWell_RateControlled) {
|
|
|
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::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();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::discardPebiResults()
|
|
|
{
|
|
|
m_oNumericalAnalysisCase.discardResults();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isPebiGridValid() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.isGridValid();
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setCurrentResultWellCode(
|
|
|
const QString& sWellCode)
|
|
|
{
|
|
|
const QString sValidatedWellCode = sWellCode.isEmpty()
|
|
|
? getPrimaryWellCode() : sWellCode;
|
|
|
if(!sValidatedWellCode.isEmpty() &&
|
|
|
!isWellAvailableForResult(sValidatedWellCode)) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(sValidatedWellCode);
|
|
|
}
|
|
|
|
|
|
QString nmDataAnalyzeManager::getCurrentResultWellCode() const
|
|
|
{
|
|
|
return m_oNumericalAnalysisCase.getCurrentResultWellCode();
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* nmDataAnalyzeManager::getCurrentResultWellData() const
|
|
|
{
|
|
|
return findWellByCode(getCurrentResultWellCode());
|
|
|
}
|
|
|
|
|
|
// 设置当前查看井
|
|
|
void nmDataAnalyzeManager::setCurWellData(nmDataWellBase * wellData)
|
|
|
{
|
|
|
m_pCurDataWell = wellData;
|
|
|
}
|
|
|
|
|
|
// 获取当前查看井
|
|
|
nmDataWellBase* nmDataAnalyzeManager::getCurWellData()
|
|
|
{
|
|
|
return m_pCurDataWell;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::notifyDataChanged()
|
|
|
{
|
|
|
// 现有属性编辑链路尚未细分“几何变化”和“仅求解输入变化”,
|
|
|
// 为保证正确性先采用保守策略:任一计算数据变化都使网格失效。
|
|
|
m_oNumericalAnalysisCase.invalidateGrid();
|
|
|
|
|
|
// 当前结果井清空产量后会变成观察井,应立即退出结果井列表并回到主井。
|
|
|
const QString sCurrentResultWellCode = getCurrentResultWellCode();
|
|
|
if(!sCurrentResultWellCode.isEmpty() &&
|
|
|
!isWellAvailableForResult(sCurrentResultWellCode)) {
|
|
|
m_oNumericalAnalysisCase.setCurrentResultWellCode(
|
|
|
getPrimaryWellCode());
|
|
|
}
|
|
|
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 = 2;
|
|
|
if(!doc.HasMember("NumericalProjectVersion") ||
|
|
|
!doc["NumericalProjectVersion"].IsInt() ||
|
|
|
doc["NumericalProjectVersion"].GetInt() != nSupportedProjectVersion) {
|
|
|
qWarning() << "Unsupported numerical project version:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString sSavedPrimaryWellCode;
|
|
|
QString sSavedCurrentResultWellCode;
|
|
|
NM_CASE_WELL_MODE eSavedPrimaryWellMode = NM_CaseWell_Observation;
|
|
|
bool bSavedIncludeOtherWells = false;
|
|
|
double dSavedPebiGridControl = 150.0;
|
|
|
QVector<nmCalculationWellRef> vecSavedIncludedWells;
|
|
|
QVector<nmSolverWellRef> vecSavedSolverOrder;
|
|
|
|
|
|
// 第一步:版本 2 必须完整保存分析方案,缺字段时不再按旧项目规则猜测。
|
|
|
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("CurrentResultWellCode") ||
|
|
|
!oCaseJson["CurrentResultWellCode"].IsString() ||
|
|
|
!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());
|
|
|
sSavedCurrentResultWellCode = QString::fromUtf8(
|
|
|
oCaseJson["CurrentResultWellCode"].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() ||
|
|
|
sSavedCurrentResultWellCode.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;
|
|
|
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("WellType") ||
|
|
|
!oWellJson["WellType"].IsInt()) {
|
|
|
qWarning() << "Numerical project contains an invalid well entry:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const QString sWellCode = QString::fromUtf8(
|
|
|
oWellJson["WellCode"].GetString());
|
|
|
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)) {
|
|
|
qWarning() << "Numerical project contains an empty, duplicate or unsupported WellCode:"
|
|
|
<< sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
mapSavedWellTypes.insert(sWellCode, eWellType);
|
|
|
}
|
|
|
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());
|
|
|
const bool bModeValid = eMode == NM_CaseWell_RateControlled ||
|
|
|
eMode == NM_CaseWell_Observation;
|
|
|
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));
|
|
|
}
|
|
|
|
|
|
if(!setEffectiveWellCodes.contains(sSavedCurrentResultWellCode)) {
|
|
|
qWarning() << "Current result WellCode is not effective:"
|
|
|
<< sSavedCurrentResultWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:求解器顺序只允许有效真实井或显式手工裂缝占位。
|
|
|
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(!setEffectiveWellCodes.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() &&
|
|
|
setOrderedWellCodes != setEffectiveWellCodes) {
|
|
|
qWarning() << "SolverWellOrder does not contain every effective well:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 清空现有数据,确保从头加载新数据
|
|
|
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;
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
|
|
|
mapLoadedWellTypes.contains(pWellData->getWellCode())) {
|
|
|
qWarning() << "Loaded numerical project contains an invalid WellCode:";
|
|
|
return false;
|
|
|
}
|
|
|
mapLoadedWellTypes.insert(pWellData->getWellCode(),
|
|
|
pWellData->getWellType());
|
|
|
}
|
|
|
if(mapLoadedWellTypes != mapSavedWellTypes) {
|
|
|
qWarning() << "Loaded well directory differs from validated JSON:"
|
|
|
<< filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 井对象全部加载完成后,严格按 WellCode 恢复数值分析方案。
|
|
|
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);
|
|
|
setIncludedCalculationWells(vecSavedIncludedWells);
|
|
|
m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells);
|
|
|
m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder);
|
|
|
setCurrentResultWellCode(sSavedCurrentResultWellCode.isEmpty()
|
|
|
? sSavedPrimaryWellCode : sSavedCurrentResultWellCode);
|
|
|
|
|
|
|
|
|
// 解析 时间步 对象
|
|
|
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());
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 将 C++ 对象数据写入 JSON 文件
|
|
|
bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath)
|
|
|
{
|
|
|
// 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。
|
|
|
QSet<QString> setWellCodes;
|
|
|
QMap<QString, NM_WELL_MODEL> mapWellTypes;
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
|
|
|
setWellCodes.contains(pWellData->getWellCode())) {
|
|
|
qWarning() << "Cannot save numerical project: empty or duplicate WellCode.";
|
|
|
return false;
|
|
|
}
|
|
|
setWellCodes.insert(pWellData->getWellCode());
|
|
|
mapWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType());
|
|
|
}
|
|
|
|
|
|
const QString sPrimaryWellCode = getPrimaryWellCode();
|
|
|
const QString sCurrentResultWellCode = getCurrentResultWellCode();
|
|
|
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);
|
|
|
}
|
|
|
if(!setEffectiveWellCodes.contains(sCurrentResultWellCode)) {
|
|
|
qWarning() << "Cannot save numerical project: result WellCode is not effective:"
|
|
|
<< sCurrentResultWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
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(); // 获取内存分配器
|
|
|
|
|
|
// 新的数据模型从版本2开始只使用 WellCode 建立跨模块关联。
|
|
|
doc.AddMember("NumericalProjectVersion", 2, 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);
|
|
|
QByteArray baCurrentResultWellCode = getCurrentResultWellCode().toUtf8();
|
|
|
oCaseJson.AddMember("CurrentResultWellCode",
|
|
|
rapidjson::Value(baCurrentResultWellCode.constData(), allocator).Move(),
|
|
|
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);
|
|
|
|
|
|
// 将最终构建好的 Document 写入文件
|
|
|
if(!nmDataJsonTools::WriteDomToFile(doc, filePath)) {
|
|
|
qDebug() << "Error: Failed to write DOM to file:" << filePath;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWndF, bool bClearRoot)
|
|
|
{
|
|
|
Q_ASSERT(nullptr != pSubWndF);
|
|
|
// 通过窗口层上下文获取保存目录
|
|
|
nmDataAnalyzeContextProvider* pContextProvider = nmDataAnalyzeContext::provider();
|
|
|
Q_ASSERT(nullptr != pContextProvider);
|
|
|
QString sDir;
|
|
|
if(pContextProvider == nullptr || !pContextProvider->getSaveResultDir(pSubWndF, sRstCode, sDir)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// bClearRoot为true时清空整个成果目录,清理已删除窗口的残留数据。
|
|
|
// 仅在遍历保存的第一个窗口传入true,后续窗口传false避免误删其他窗口数据。
|
|
|
if(bClearRoot) {
|
|
|
QString sResultRootPath = QFileInfo(sDir).dir().absolutePath();
|
|
|
if(!clearDirectoryContents(sResultRootPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString sResultPath = sDir + "/Results";
|
|
|
if(!this->ensureDirectoryExists(sResultPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
// 将数据写入Json文件
|
|
|
if(!this->WriteProjectData(sResultPath + "/Numerical_Parameters.json")) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// --- 保存所有井的历史数据到独立目录 /WellHistory ---
|
|
|
QString sWellHistoryDataPath = sDir + "/WellHistory";
|
|
|
if(!this->ensureDirectoryExists(sWellHistoryDataPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<nmDataWellBase*> vecAllWells = this->getWellDataList(); // 获取所有井
|
|
|
|
|
|
// 遍历所有井,保存历史数据
|
|
|
for(int wellIdx = 0; wellIdx < vecAllWells.size(); ++wellIdx) {
|
|
|
nmDataWellBase* pWellData = vecAllWells[wellIdx];
|
|
|
if(pWellData) {
|
|
|
if(!this->saveWellHistoryData(sWellHistoryDataPath, pWellData)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第一步:准备网格、场结果和单井结果的固定路径。
|
|
|
QString sGridPath = sDir + "/Grid";
|
|
|
QString sResultGridFilePath = sGridPath + "/ResultGrid.vtu";
|
|
|
QString sResultWellLocationPath =
|
|
|
sGridPath + "/ResultWellLocations.bin";
|
|
|
QString sTimeStepDataDirPath = sDir + "/TimeStepData";
|
|
|
QString sTimeStepDataFilePath =
|
|
|
sTimeStepDataDirPath + "/PressureTimeSteps.bin";
|
|
|
QString sWellRstDataPath = sDir + "/WellRst";
|
|
|
|
|
|
// 第二步:无有效网格时不保存内存中残留的 VTK 对象和结果,并清理同目录旧文件。
|
|
|
if(!isPebiGridValid()) {
|
|
|
QFile::remove(sGridPath + "/CurrentGrid.vtu");
|
|
|
QFile::remove(sResultGridFilePath);
|
|
|
QFile::remove(sResultWellLocationPath);
|
|
|
QFile::remove(sTimeStepDataFilePath);
|
|
|
if(QDir(sWellRstDataPath).exists() &&
|
|
|
!clearDirectoryContents(sWellRstDataPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 方案标记为有效网格时,必须同时持有可写出的 VTK 网格。
|
|
|
if(!m_pVtkUnstructuredGrid) {
|
|
|
qWarning() << "Cannot save a valid PEBI grid: VTK grid is missing.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!this->ensureDirectoryExists(sGridPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:保存与 SolverWellOrder 同版本的实时网格。
|
|
|
if(!this->writeUnstructuredGridToFile(
|
|
|
m_pVtkUnstructuredGrid, sGridPath + "/CurrentGrid.vtu")) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:网格有效但结果无效时只保存网格,并删除上一次求解的残留文件。
|
|
|
if(!m_oNumericalAnalysisCase.areResultsValid()) {
|
|
|
QFile::remove(sResultGridFilePath);
|
|
|
QFile::remove(sResultWellLocationPath);
|
|
|
QFile::remove(sTimeStepDataFilePath);
|
|
|
if(QDir(sWellRstDataPath).exists() &&
|
|
|
!clearDirectoryContents(sWellRstDataPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 结果标记有效时,基础网格和时间步压力必须同时完整存在。
|
|
|
if(!m_pResultBaseGrid || m_mapTimeStepDataP.isEmpty()) {
|
|
|
qWarning() << "Cannot save valid PEBI results: result grid or time steps are missing.";
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第五步:保存场图基础网格、井位置和全部时间步压力。
|
|
|
if(!this->writeUnstructuredGridToFile(
|
|
|
m_pResultBaseGrid, sResultGridFilePath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!this->saveWellLocations(sResultWellLocationPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!this->ensureDirectoryExists(sTimeStepDataDirPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!writeTimeStepDataMapToBinaryFile(sTimeStepDataFilePath)) {
|
|
|
qDebug() << QString("Failed to save all time step data map to binary file: %1").arg(sTimeStepDataFilePath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第六步:先清空目标目录,再按 WellCode 保存每口真实计算井的结果。
|
|
|
if(!this->ensureDirectoryExists(sWellRstDataPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(!clearDirectoryContents(sWellRstDataPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
QVector<nmSolverWellRef> vecWells = this->getSolverWellOrder();
|
|
|
|
|
|
// 遍历每口井,保存里面计算出来的数据
|
|
|
for(int wellIdx = 0; wellIdx < vecWells.size(); ++wellIdx) {
|
|
|
const nmSolverWellRef& oWellRef = vecWells[wellIdx];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData =
|
|
|
this->findWellByCode(oWellRef.m_sWellCode);
|
|
|
|
|
|
if(pWellData) {
|
|
|
if(!this->saveWellCalRstData(sWellRstDataPath, pWellData)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
|
|
|
{
|
|
|
// 将Json文件内容读取出来
|
|
|
QString sResultPath = sLoadAnalDir + "/Results/Numerical_Parameters.json";
|
|
|
if(!this->ReadProjectData(sResultPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第一步:先检查分析方案、网格文件和结果文件是否构成完整的一组。
|
|
|
QString sCurrentGridPath = sLoadAnalDir + "/Grid/CurrentGrid.vtu";
|
|
|
QString sResultGridPath = sLoadAnalDir + "/Grid/ResultGrid.vtu";
|
|
|
QString sResultWellLocationPath =
|
|
|
sLoadAnalDir + "/Grid/ResultWellLocations.bin";
|
|
|
QString sTimeStepDataFilePath =
|
|
|
sLoadAnalDir + "/TimeStepData/PressureTimeSteps.bin";
|
|
|
QString sWellRstDataPath = sLoadAnalDir + "/WellRst";
|
|
|
|
|
|
const bool bHasCurrentGridFile = QFile::exists(sCurrentGridPath);
|
|
|
const bool bHasResultGridFile = QFile::exists(sResultGridPath);
|
|
|
const bool bHasResultWellLocationFile =
|
|
|
QFile::exists(sResultWellLocationPath);
|
|
|
const bool bHasTimeStepDataFile = QFile::exists(sTimeStepDataFilePath);
|
|
|
QStringList listResultFilters;
|
|
|
listResultFilters << "*_Results.bin";
|
|
|
const bool bHasAnyWellResultFile = !QDir(sWellRstDataPath).entryList(
|
|
|
listResultFilters, QDir::Files).isEmpty();
|
|
|
const bool bHasSolverWellOrder = !getSolverWellOrder().isEmpty();
|
|
|
const bool bHasAnyResultFile = bHasResultGridFile ||
|
|
|
bHasResultWellLocationFile || bHasTimeStepDataFile ||
|
|
|
bHasAnyWellResultFile;
|
|
|
const bool bHasCompleteResultFiles = bHasResultGridFile &&
|
|
|
bHasResultWellLocationFile && bHasTimeStepDataFile;
|
|
|
|
|
|
if(bHasCurrentGridFile != bHasSolverWellOrder) {
|
|
|
qWarning() << "CurrentGrid.vtu and SolverWellOrder are inconsistent:"
|
|
|
<< sLoadAnalDir;
|
|
|
return false;
|
|
|
}
|
|
|
if(bHasAnyResultFile &&
|
|
|
(!bHasCurrentGridFile || !bHasCompleteResultFiles)) {
|
|
|
qWarning() << "PEBI result files are incomplete:" << sLoadAnalDir;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:读取当前网格;只有文件和求解器顺序同时存在时才标记有效。
|
|
|
if(bHasCurrentGridFile &&
|
|
|
!this->readUnstructuredGridFromFile(m_pVtkUnstructuredGrid,
|
|
|
sCurrentGridPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
if(bHasCurrentGridFile) {
|
|
|
if(getSolverWellOrder().isEmpty()) {
|
|
|
qWarning() << "Loaded grid has no SolverWellOrder:" << sCurrentGridPath;
|
|
|
return false;
|
|
|
}
|
|
|
m_oNumericalAnalysisCase.markGridBuilt();
|
|
|
}
|
|
|
|
|
|
// 第三步:读取完整求解结果;网格项目允许尚未执行过求解。
|
|
|
if(bHasResultGridFile &&
|
|
|
!this->readUnstructuredGridFromFile(m_pResultBaseGrid,
|
|
|
sResultGridPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(bHasResultWellLocationFile &&
|
|
|
!this->loadWellLocations(sResultWellLocationPath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(bHasTimeStepDataFile &&
|
|
|
!readTimeStepDataMapFromBinaryFile(sTimeStepDataFilePath)) {
|
|
|
qWarning() << QString("Failed to load all time step data map from binary file: %1").arg(sTimeStepDataFilePath);
|
|
|
return false;
|
|
|
}
|
|
|
if(bHasCompleteResultFiles && m_mapTimeStepDataP.isEmpty()) {
|
|
|
qWarning() << "PEBI result contains no pressure time steps:"
|
|
|
<< sLoadAnalDir;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第四步:按 Map 井目录完整恢复历史数据。
|
|
|
// 版本 2 保存时会为每口井写入历史文件,因此缺失或损坏都表示项目不完整。
|
|
|
QString sWellHistoryDataPath = sLoadAnalDir + "/WellHistory";
|
|
|
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
|
|
|
nmDataWellBase* pWellData = m_vWellData[nIndex];
|
|
|
if(!this->loadWellHistoryData(sWellHistoryDataPath, pWellData)) {
|
|
|
qWarning() << "Failed to load well history for WellCode:"
|
|
|
<< (pWellData == nullptr
|
|
|
? QString()
|
|
|
: pWellData->getWellCode());
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第五步:有完整场结果时,必须同时具备每口真实求解井的结果。
|
|
|
// SolverWellOrder 是 DLL 返回槽位与井对象之间的唯一映射;手工裂缝只占槽位,
|
|
|
// 不对应 Map 中的井对象,也没有独立的结果文件。
|
|
|
if(bHasCompleteResultFiles) {
|
|
|
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
|
|
|
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode);
|
|
|
if(pWellData == nullptr ||
|
|
|
!this->loadWellCalRstData(sWellRstDataPath, pWellData)) {
|
|
|
qWarning() << "Failed to load calculation result for WellCode:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第六步:二进制数据完整后,再生成各井用于曲线显示的压力、流量点。
|
|
|
this->loadWellPreAndFlow();
|
|
|
|
|
|
// 主动井必须能够从项目井目录恢复有效产量制度;观察井允许没有流量点。
|
|
|
QVector<nmCalculationWellRef> vecEffectiveWells = getEffectiveCalculationWells();
|
|
|
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
|
|
|
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
|
|
|
nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode);
|
|
|
if(pWellData == nullptr ||
|
|
|
(oWellRef.m_eMode == NM_CaseWell_RateControlled &&
|
|
|
pWellData->getFlowPoints().size() < 2)) {
|
|
|
qWarning() << "Failed to restore rate schedule for WellCode:"
|
|
|
<< oWellRef.m_sWellCode;
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(bHasCurrentGridFile && bHasCompleteResultFiles) {
|
|
|
m_oNumericalAnalysisCase.markResultsAvailable();
|
|
|
}
|
|
|
|
|
|
if (m_pVtkUnstructuredGrid != nullptr)
|
|
|
m_bIsLoadData = true;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> nmDataAnalyzeManager::getUnstructuredGridCopy() const
|
|
|
{
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pCopyGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
|
|
|
if(m_pVtkUnstructuredGrid) {
|
|
|
// 执行深拷贝:复制网格的所有几何和拓扑信息
|
|
|
pCopyGrid->DeepCopy(m_pVtkUnstructuredGrid);
|
|
|
}
|
|
|
|
|
|
return pCopyGrid;
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> nmDataAnalyzeManager::getResultBaseGrid() const
|
|
|
{
|
|
|
return m_pResultBaseGrid;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setResultBaseGrid(vtkSmartPointer<vtkUnstructuredGrid> grid)
|
|
|
{
|
|
|
m_pResultBaseGrid = grid;
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> nmDataAnalyzeManager::getResultBaseGridCopy() const
|
|
|
{
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pCopyGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
|
|
|
if(m_pResultBaseGrid) {
|
|
|
// 执行深拷贝:复制网格的所有几何和拓扑信息
|
|
|
pCopyGrid->DeepCopy(m_pResultBaseGrid);
|
|
|
}
|
|
|
|
|
|
return pCopyGrid;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::writeUnstructuredGridToFile(vtkSmartPointer<vtkUnstructuredGrid> grid, const QString& filePath)
|
|
|
{
|
|
|
// 检查是否存在有效的网格数据
|
|
|
if(!grid) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 根据文件扩展名决定使用哪种格式
|
|
|
if(filePath.endsWith(".vtu", Qt::CaseInsensitive)) {
|
|
|
// 使用XML格式(推荐格式)
|
|
|
vtkNew<vtkXMLUnstructuredGridWriter> writer;
|
|
|
writer->SetInputData(grid); // 设置输入数据
|
|
|
writer->SetFileName(filePath.toStdString().c_str()); // 设置输出文件名
|
|
|
writer->SetDataModeToBinary(); // 设置为二进制模式,也可以用SetDataModeToAscii()设为文本模式
|
|
|
writer->Write(); // 执行写入操作
|
|
|
} else if(filePath.endsWith(".vtk", Qt::CaseInsensitive)) {
|
|
|
// 使用传统的VTK格式
|
|
|
vtkNew<vtkUnstructuredGridWriter> writer;
|
|
|
writer->SetInputData(grid); // 设置输入数据
|
|
|
writer->SetFileName(filePath.toStdString().c_str()); // 设置输出文件名
|
|
|
writer->SetFileTypeToBinary(); // 设置为二进制模式,也可以用SetFileTypeToASCII()设为文本模式
|
|
|
writer->Write(); // 执行写入操作
|
|
|
} else {
|
|
|
return false; // 不支持的文件扩展名
|
|
|
}
|
|
|
|
|
|
return true; // 写入成功
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::readUnstructuredGridFromFile(vtkSmartPointer<vtkUnstructuredGrid>& grid, const QString& filePath)
|
|
|
{
|
|
|
// 检查文件是否存在
|
|
|
if(!QFile::exists(filePath)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 根据文件扩展名选择不同的读取器
|
|
|
if(filePath.endsWith(".vtu", Qt::CaseInsensitive)) {
|
|
|
// 使用XML格式读取器
|
|
|
vtkNew<vtkXMLUnstructuredGridReader> reader;
|
|
|
reader->SetFileName(filePath.toStdString().c_str()); // 设置输入文件名
|
|
|
reader->Update(); // 执行读取操作
|
|
|
grid = reader->GetOutput(); // 获取读取的数据
|
|
|
} else if(filePath.endsWith(".vtk", Qt::CaseInsensitive)) {
|
|
|
// 使用传统VTK格式读取器
|
|
|
vtkNew<vtkUnstructuredGridReader> reader;
|
|
|
reader->SetFileName(filePath.toStdString().c_str()); // 设置输入文件名
|
|
|
reader->Update(); // 执行读取操作
|
|
|
grid = reader->GetOutput(); // 获取读取的数据
|
|
|
} else {
|
|
|
return false; // 不支持的文件扩展名
|
|
|
}
|
|
|
|
|
|
// 检查是否成功读取了有效的网格数据
|
|
|
if(grid && grid->GetNumberOfPoints() > 0) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false; // 读取失败
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::addTimeStep(double time, vtkSmartPointer<vtkDoubleArray> data)
|
|
|
{
|
|
|
if(!data || data->GetNumberOfTuples() == 0) return;
|
|
|
|
|
|
m_mapTimeStepDataP.insert(time, data);
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearTimeSteps()
|
|
|
{
|
|
|
m_mapTimeStepDataP.clear();
|
|
|
m_dScalarRangeP[0] = DBL_MAX;
|
|
|
m_dScalarRangeP[1] = -DBL_MAX;
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkDoubleArray> nmDataAnalyzeManager::getTimeStepData(double time) const
|
|
|
{
|
|
|
if(m_mapTimeStepDataP.contains(time)) {
|
|
|
return m_mapTimeStepDataP.value(time);
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
QList<double> nmDataAnalyzeManager::getTimeStepKeys()
|
|
|
{
|
|
|
return m_mapTimeStepDataP.keys();
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::isTimeStepDataEmpty()
|
|
|
{
|
|
|
return m_mapTimeStepDataP.isEmpty();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 辅助函数:将整个时间步数据QMap保存到单个二进制文件。
|
|
|
* 文件中依次存储:QMap大小、每个时间键、每个vtkDoubleArray的大小、每个vtkDoubleArray的数据。
|
|
|
* @param filePath 文件保存的完整路径。
|
|
|
* @return 写入成功返回true,否则返回false。
|
|
|
*/
|
|
|
bool nmDataAnalyzeManager::writeTimeStepDataMapToBinaryFile(const QString& filePath)
|
|
|
{
|
|
|
QFile file(filePath);
|
|
|
|
|
|
// 使用 Truncate 模式确保如果文件已存在,会被清空并覆盖
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
|
|
qDebug() << QString("Failed to open file for writing (TimeStepDataMap): %1").arg(filePath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream out(&file);
|
|
|
out.setVersion(QDataStream::Qt_4_8); // 当前项目Qt版本
|
|
|
|
|
|
// 1. 写入 QMap 的元素数量
|
|
|
out << (qint32)m_mapTimeStepDataP.size();
|
|
|
|
|
|
// 2. 遍历 QMap,依次写入每个时间键和对应的 vtkDoubleArray 数据
|
|
|
for(auto it = m_mapTimeStepDataP.constBegin(); it != m_mapTimeStepDataP.constEnd(); ++it) {
|
|
|
double time = it.key();
|
|
|
vtkSmartPointer<vtkDoubleArray> data = it.value();
|
|
|
|
|
|
if(!qIsFinite(time) || !data || data->GetNumberOfTuples() <= 0) {
|
|
|
file.close();
|
|
|
QFile::remove(filePath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 写入时间键
|
|
|
out << time;
|
|
|
|
|
|
// 写入 vtkDoubleArray 的元素数量
|
|
|
qint64 numberOfTuples = data ? data->GetNumberOfTuples() : 0;
|
|
|
out << numberOfTuples;
|
|
|
|
|
|
// 写入 vtkDoubleArray 的数据
|
|
|
if(data && numberOfTuples > 0) {
|
|
|
for(qint64 i = 0; i < numberOfTuples; ++i) {
|
|
|
out << data->GetValue(i);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const bool bWriteOk = out.status() == QDataStream::Ok && file.flush();
|
|
|
file.close();
|
|
|
if(!bWriteOk) {
|
|
|
QFile::remove(filePath);
|
|
|
}
|
|
|
return bWriteOk;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 辅助函数:从单个二进制文件加载整个时间步数据QMap。
|
|
|
* @param filePath 文件所在的完整路径。
|
|
|
* @return 加载成功返回true,否则返回false。
|
|
|
*/
|
|
|
bool nmDataAnalyzeManager::readTimeStepDataMapFromBinaryFile(const QString& filePath)
|
|
|
{
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::ReadOnly)) {
|
|
|
qDebug() << QString("Failed to open file for reading (TimeStepDataMap): %1").arg(filePath);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream in(&file);
|
|
|
in.setVersion(QDataStream::Qt_4_8); // 必须与写入时的版本一致
|
|
|
|
|
|
// 1. 读取 QMap 的元素数量
|
|
|
qint32 mapSize;
|
|
|
in >> mapSize;
|
|
|
|
|
|
const qint64 nFileSize = file.size();
|
|
|
if(in.status() != QDataStream::Ok || mapSize <= 0 ||
|
|
|
mapSize > nFileSize / static_cast<qint64>(sizeof(double) + sizeof(qint64))) {
|
|
|
qDebug() << QString("Invalid map size read from binary file: %1").arg(filePath);
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QMap<double, vtkSmartPointer<vtkDoubleArray> > mapLoadedTimeSteps;
|
|
|
double dLoadedMinP = DBL_MAX;
|
|
|
double dLoadedMaxP = -DBL_MAX;
|
|
|
|
|
|
// 2. 循环读取每个时间键和对应的 vtkDoubleArray 数据
|
|
|
for(qint32 k = 0; k < mapSize; ++k) {
|
|
|
double time;
|
|
|
in >> time; // 读取时间键
|
|
|
|
|
|
qint64 numberOfTuples;
|
|
|
in >> numberOfTuples; // 读取 vtkDoubleArray 的元素数量
|
|
|
|
|
|
// 第一步:每一帧必须有有限时间、非空压力数组,且声明长度不能超过文件容量。
|
|
|
if(in.status() != QDataStream::Ok || !qIsFinite(time) ||
|
|
|
mapLoadedTimeSteps.contains(time) || numberOfTuples <= 0 ||
|
|
|
numberOfTuples > nFileSize / static_cast<qint64>(sizeof(double))) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
vtkSmartPointer<vtkDoubleArray> data = vtkSmartPointer<vtkDoubleArray>::New();
|
|
|
data->SetNumberOfComponents(1); // 假设是单分量数组
|
|
|
data->SetNumberOfTuples(numberOfTuples); // 设置数组大小
|
|
|
|
|
|
// 设置数组名称
|
|
|
data->SetName("p");
|
|
|
|
|
|
if(numberOfTuples > 0) {
|
|
|
double currentMin = DBL_MAX;
|
|
|
double currentMax = -DBL_MAX;
|
|
|
|
|
|
for(qint64 i = 0; i < numberOfTuples; ++i) {
|
|
|
double value;
|
|
|
in >> value;
|
|
|
if(in.status() != QDataStream::Ok || !qIsFinite(value)) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
data->SetValue(i, value);
|
|
|
|
|
|
// 更新当前时间步的范围
|
|
|
currentMin = qMin(currentMin, value);
|
|
|
currentMax = qMax(currentMax, value);
|
|
|
}
|
|
|
|
|
|
// 更新全局范围
|
|
|
dLoadedMinP = qMin(dLoadedMinP, currentMin);
|
|
|
dLoadedMaxP = qMax(dLoadedMaxP, currentMax);
|
|
|
}
|
|
|
|
|
|
mapLoadedTimeSteps.insert(time, data);
|
|
|
}
|
|
|
|
|
|
// 第二步:完整消费文件后再一次性提交,损坏文件不会留下部分场结果。
|
|
|
if(in.status() != QDataStream::Ok || !file.atEnd() ||
|
|
|
mapLoadedTimeSteps.size() != mapSize) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_mapTimeStepDataP = mapLoadedTimeSteps;
|
|
|
m_dScalarRangeP[0] = dLoadedMinP;
|
|
|
m_dScalarRangeP[1] = dLoadedMaxP;
|
|
|
file.close();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::saveWellHistoryData(QString sDir, nmDataWellBase* pWellData)
|
|
|
{
|
|
|
Q_ASSERT(nullptr != pWellData);
|
|
|
if(nullptr == pWellData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 文件名使用 WellCode,井改名不会造成历史数据丢失。
|
|
|
QString sWellCode = pWellData->getWellCode();
|
|
|
if(sWellCode.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
QString filePath = sDir + "/" + sWellCode + "_History.bin";
|
|
|
|
|
|
QFile file(filePath);
|
|
|
if(!file.open(QIODevice::WriteOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream out(&file);
|
|
|
out.setVersion(QDataStream::Qt_4_8);
|
|
|
|
|
|
// 只写入历史数据
|
|
|
out << pWellData->getHistoryPressure();
|
|
|
out << pWellData->getHistoryLogLog();
|
|
|
out << pWellData->getHistorySemiLog();
|
|
|
|
|
|
const bool bWriteOk = out.status() == QDataStream::Ok && file.flush();
|
|
|
file.close();
|
|
|
if(!bWriteOk) {
|
|
|
QFile::remove(filePath);
|
|
|
}
|
|
|
return bWriteOk;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadWellHistoryData(QString sDir, nmDataWellBase* pWellData)
|
|
|
{
|
|
|
Q_ASSERT(nullptr != pWellData);
|
|
|
|
|
|
if(nullptr == pWellData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString sWellCode = pWellData->getWellCode();
|
|
|
if(sWellCode.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString filePath = sDir + "/" + sWellCode + "_History.bin";
|
|
|
|
|
|
QFile file(filePath);
|
|
|
if(!file.open(QIODevice::ReadOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream in(&file);
|
|
|
in.setVersion(QDataStream::Qt_4_8);
|
|
|
|
|
|
// 只需要读取历史数据
|
|
|
QVector<QVector<double>> historyPressureData;
|
|
|
QVector<QVector<double>> historyLoglogData;
|
|
|
QVector<QVector<double>> historySemiLogData;
|
|
|
|
|
|
// 只读取历史数据
|
|
|
in >> historyPressureData;
|
|
|
in >> historyLoglogData;
|
|
|
in >> historySemiLogData;
|
|
|
|
|
|
// 第一步:先验证整个数据流,再修改井对象,避免截断文件写入半成品数据。
|
|
|
if(in.status() != QDataStream::Ok || !file.atEnd()) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:文件完整时一次性回填三类历史曲线。
|
|
|
pWellData->setHistoryPressure(historyPressureData);
|
|
|
pWellData->setHistoryLogLog(historyLoglogData);
|
|
|
pWellData->setHistorySemiLog(historySemiLogData);
|
|
|
|
|
|
file.close();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::saveWellCalRstData(QString sDir, nmDataWellBase* pWellData)
|
|
|
{
|
|
|
Q_ASSERT(nullptr != pWellData);
|
|
|
|
|
|
if(nullptr == pWellData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 构建文件路径,为每口井的结果创建一个独立文件
|
|
|
QString sWellCode = pWellData->getWellCode();
|
|
|
|
|
|
if(sWellCode.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString filePath = sDir + "/" + sWellCode + "_Results.bin";
|
|
|
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream out(&file);
|
|
|
out.setVersion(QDataStream::Qt_4_8); // 设置数据流版本,保证未来兼容性
|
|
|
|
|
|
// 写入数据
|
|
|
// QDataStream 可以直接序列化 QVector<QVector<double>>
|
|
|
out << pWellData->getResultPressure();
|
|
|
out << pWellData->getResultLogLog();
|
|
|
out << pWellData->getResultSemiLog();
|
|
|
|
|
|
const bool bWriteOk = out.status() == QDataStream::Ok && file.flush();
|
|
|
file.close();
|
|
|
if(!bWriteOk) {
|
|
|
QFile::remove(filePath);
|
|
|
}
|
|
|
return bWriteOk;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadWellCalRstData(QString sDir, nmDataWellBase* pWellData)
|
|
|
{
|
|
|
Q_ASSERT(nullptr != pWellData);
|
|
|
|
|
|
if(nullptr == pWellData) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString sWellCode = pWellData->getWellCode();
|
|
|
|
|
|
if(sWellCode.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString filePath = sDir + "/" + sWellCode + "_Results.bin";
|
|
|
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::ReadOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream in(&file);
|
|
|
in.setVersion(QDataStream::Qt_4_8); // 确保与写入时版本一致
|
|
|
|
|
|
// 读取数据到 pWellData 的成员变量
|
|
|
QVector<QVector<double>> pressureData;
|
|
|
QVector<QVector<double>> loglogData;
|
|
|
QVector<QVector<double>> semiLogData;
|
|
|
|
|
|
in >> pressureData;
|
|
|
in >> loglogData;
|
|
|
in >> semiLogData;
|
|
|
|
|
|
// 第一步:先验证整个数据流,再修改井对象,避免损坏文件生成部分结果。
|
|
|
if(in.status() != QDataStream::Ok || !file.atEnd()) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:主动井可包含三类曲线;观察井正常情况下只有压力数据。
|
|
|
pWellData->setResultPressure(pressureData);
|
|
|
pWellData->setResultLogLog(loglogData);
|
|
|
pWellData->setResultSemiLog(semiLogData);
|
|
|
|
|
|
//// 存储当前井名称和二维位置到映射
|
|
|
//QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble());
|
|
|
//addWellLocation(sWellName, ptWellCoords);
|
|
|
|
|
|
file.close();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::getScalarRangeP(double range[2]) const
|
|
|
{
|
|
|
range[0] = m_dScalarRangeP[0];
|
|
|
range[1] = m_dScalarRangeP[1];
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::setScalarRangeP(double min, double max)
|
|
|
{
|
|
|
m_dScalarRangeP[0] = min;
|
|
|
m_dScalarRangeP[1] = max;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::addWellLocation(const QString& sWellCode, const QPointF& location)
|
|
|
{
|
|
|
m_mapWellLocations.insert(sWellCode, location);
|
|
|
}
|
|
|
|
|
|
QPointF nmDataAnalyzeManager::getWellLocation(const QString& sWellCode) const
|
|
|
{
|
|
|
return m_mapWellLocations.value(sWellCode, QPointF());
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::removeWellLocation(const QString& sWellCode)
|
|
|
{
|
|
|
return m_mapWellLocations.remove(sWellCode) > 0;
|
|
|
}
|
|
|
|
|
|
void nmDataAnalyzeManager::clearWellLocations()
|
|
|
{
|
|
|
m_mapWellLocations.clear();
|
|
|
}
|
|
|
|
|
|
QMap<QString, QPointF> nmDataAnalyzeManager::getAllWellLocations() const
|
|
|
{
|
|
|
return m_mapWellLocations;
|
|
|
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::saveWellLocations(const QString& filePath)
|
|
|
{
|
|
|
// 第一步:结果位置必须逐口覆盖求解器中的真实井,手工裂缝不对应 Map 井。
|
|
|
QSet<QString> setExpectedWellCodes;
|
|
|
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
|
|
|
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
if(oWellRef.m_sWellCode.isEmpty() ||
|
|
|
setExpectedWellCodes.contains(oWellRef.m_sWellCode)) {
|
|
|
return false;
|
|
|
}
|
|
|
setExpectedWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QSet<QString> setStoredWellCodes;
|
|
|
QMap<QString, QPointF>::const_iterator itLocation =
|
|
|
m_mapWellLocations.constBegin();
|
|
|
for(; itLocation != m_mapWellLocations.constEnd(); ++itLocation) {
|
|
|
if(itLocation.key().isEmpty() ||
|
|
|
!qIsFinite(itLocation.value().x()) ||
|
|
|
!qIsFinite(itLocation.value().y())) {
|
|
|
return false;
|
|
|
}
|
|
|
setStoredWellCodes.insert(itLocation.key());
|
|
|
}
|
|
|
if(setExpectedWellCodes.isEmpty() ||
|
|
|
setStoredWellCodes != setExpectedWellCodes) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:身份和坐标完整后再写文件,写入失败时删除半成品。
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream out(&file);
|
|
|
out.setVersion(QDataStream::Qt_4_8);
|
|
|
|
|
|
qDebug() << "Map size before writting:" << m_mapWellLocations.size();
|
|
|
|
|
|
out << m_mapWellLocations;
|
|
|
|
|
|
const bool bWriteOk = out.status() == QDataStream::Ok && file.flush();
|
|
|
file.close();
|
|
|
if(!bWriteOk) {
|
|
|
QFile::remove(filePath);
|
|
|
}
|
|
|
return bWriteOk;
|
|
|
}
|
|
|
|
|
|
bool nmDataAnalyzeManager::loadWellLocations(const QString& filePath)
|
|
|
{
|
|
|
QFile file(filePath);
|
|
|
|
|
|
if(!file.open(QIODevice::ReadOnly)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QDataStream in(&file);
|
|
|
in.setVersion(QDataStream::Qt_4_8);
|
|
|
|
|
|
// 第一步:先读入局部对象,损坏文件不能覆盖当前内存中的有效位置。
|
|
|
QMap<QString, QPointF> mapLoadedWellLocations;
|
|
|
in >> mapLoadedWellLocations;
|
|
|
|
|
|
if(in.status() != QDataStream::Ok || !file.atEnd()) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第二步:加载结果必须与 SolverWellOrder 中的真实井 WellCode 完整对应。
|
|
|
QSet<QString> setExpectedWellCodes;
|
|
|
QVector<nmSolverWellRef> vecSolverOrder = getSolverWellOrder();
|
|
|
for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) {
|
|
|
const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex];
|
|
|
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
|
|
|
if(oWellRef.m_sWellCode.isEmpty() ||
|
|
|
setExpectedWellCodes.contains(oWellRef.m_sWellCode)) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
setExpectedWellCodes.insert(oWellRef.m_sWellCode);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QSet<QString> setLoadedWellCodes;
|
|
|
QMap<QString, QPointF>::const_iterator itLocation =
|
|
|
mapLoadedWellLocations.constBegin();
|
|
|
for(; itLocation != mapLoadedWellLocations.constEnd(); ++itLocation) {
|
|
|
if(itLocation.key().isEmpty() ||
|
|
|
!qIsFinite(itLocation.value().x()) ||
|
|
|
!qIsFinite(itLocation.value().y())) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
setLoadedWellCodes.insert(itLocation.key());
|
|
|
}
|
|
|
if(setExpectedWellCodes.isEmpty() ||
|
|
|
setLoadedWellCodes != setExpectedWellCodes) {
|
|
|
file.close();
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 第三步:全部校验通过后一次性提交。
|
|
|
m_mapWellLocations = mapLoadedWellLocations;
|
|
|
|
|
|
qDebug() << "Map size after reading:" << m_mapWellLocations.size();
|
|
|
|
|
|
file.close();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
//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;
|
|
|
}
|