#include "nmDataAnalyzeManager.h" #include "nmAttrRegistry.h" #include "nmPebiResultSnapshot.h" #include "nmPebiResultSnapshotSerializer.h" #include "nmNumericalSaveSession.h" #include "nmDataAnalyzeContext.h" #include "nmDataAnalyzeContextProvider.h" #include "nmDataPlotContext.h" #include "nmDataPlotContextProvider.h" #include "nmDataWellBase.h" #include "nmDataVerticalWell.h" #include "nmDataVerticalFracturedWell.h" #include "nmDataHorizontalFracturedWell.h" #include "ZxDataWell.h" #include "ZxBaseUtil.h" #include #include #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 #include #include #include #include "singlePhaseSolver.h" #include "nmDataTimeStepSetting.h" #include #include #include #include #include #include #include #include // 井基参数名 → 数据成员访问器的映射描述 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(w).getDrainAngle(); } static nmDataAttribute* getParaHfNumberOfFractures(nmDataWellBase& w) { return &static_cast(w).getNumberOfFractures(); } static nmDataAttribute* getParaVfFractureHalfLength(nmDataWellBase& w) { return &static_cast(w).getFractureHalfLength(); } static nmDataAttribute* getParaVfFractureAngle(nmDataWellBase& w) { return &static_cast(w).getFractureAngle(); } static nmDataAttribute* getParaVfDfc(nmDataWellBase& w) { return &static_cast(w).getDfc(); } static nmDataAttribute* getParaHfFractureHalfLength(nmDataWellBase& w) { return &static_cast(w).getFractureHalfLength(); } static nmDataAttribute* getParaHfFractureAngle(nmDataWellBase& w) { return &static_cast(w).getFractureAngle(); } static nmDataAttribute* getParaHfDfc(nmDataWellBase& w) { return &static_cast(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& vecX) { if(pPvtPara != nullptr && pPvtPara->getPressure().isEmpty() && !vecX.isEmpty()) { pPvtPara->setPressure(vecX); } } // 从Diffusion右侧结果表中提取指定列数据 bool extractDiffusionColumn(const VVecDouble& vvec, int nColumn, QVector& 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 complementSaturation(const QVector& vecSaturation) { QVector 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 reversedVector(const QVector& vecValues) { QVector 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 vecS; QVector vecKr1; QVector 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& vecPressure) { // 分别获取体积系数、压缩系数、粘度三条曲线 QVector 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 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 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& 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& vecLayers) { // 清空现有分层数据 qDeleteAll(vecLayers); vecLayers.clear(); // 创建一个默认分层 nmDataLayer* pDefaultLayer = new nmDataLayer(); pDefaultLayer->setTop(6000.0); // 默认顶深 pDefaultLayer->setThickness(dThickness); // 使用从界面获取的厚度值 pDefaultLayer->setBottom(6000.0 + dThickness); // 计算底深 pDefaultLayer->setIsChecked(false); // 默认未选中 pDefaultLayer->setColor(QColor(0, 255, 0)); // 设置默认颜色(绿色) // 将默认分层添加到分层列表 vecLayers.append(pDefaultLayer); } rapidjson::Value numericalJsonString( const QString& sValue, rapidjson::Document::AllocatorType& oAllocator) { const QByteArray baValue = sValue.toUtf8(); return rapidjson::Value(baValue.constData(), static_cast(baValue.size()), oAllocator); } void addNumericalFileReference( rapidjson::Value& oParent, const char* pName, const nmNumericalFileReference& oReference, rapidjson::Document::AllocatorType& oAllocator) { rapidjson::Value oJson(rapidjson::kObjectType); oJson.AddMember("Path", numericalJsonString( oReference.m_sRelativePath, oAllocator), oAllocator); oJson.AddMember("Length", oReference.m_nLength, oAllocator); oJson.AddMember("Sha1", numericalJsonString( oReference.m_sSha1, oAllocator), oAllocator); oParent.AddMember(rapidjson::Value(pName, oAllocator).Move(), oJson, oAllocator); } bool readNumericalFileReference( const rapidjson::Value& oParent, const char* pName, nmNumericalFileReference& oReference) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsObject()) { return false; } const rapidjson::Value& oJson = oParent[pName]; if(!oJson.HasMember("Path") || !oJson["Path"].IsString() || !oJson.HasMember("Length") || !oJson["Length"].IsUint64() || !oJson.HasMember("Sha1") || !oJson["Sha1"].IsString()) { return false; } oReference.m_sRelativePath = QString::fromUtf8(oJson["Path"].GetString()); oReference.m_nLength = oJson["Length"].GetUint64(); oReference.m_sSha1 = QString::fromLatin1(oJson["Sha1"].GetString()); return oReference.isValid(); } bool readV3WindowHeader( const QString& sMainJsonPath, nmNumericalWindowPayloadReferences& oReferences, QString& sCurrentResultWellInstanceId, quint64& nGridInputRevision, quint64& nBuiltGridRevision, quint64& nResultInputRevision, QString* pError) { const QFileInfo oInfo(sMainJsonPath); if(!oInfo.isFile() || oInfo.size() <= 0 || oInfo.size() > 64ll * 1024ll * 1024ll) { if(pError != NULL) *pError = "Numerical v3 main JSON is missing or too large."; return false; } rapidjson::Document oDocument; if(!nmDataJsonTools::ReadDomFromFile(sMainJsonPath, oDocument) || !oDocument.IsObject() || !oDocument.HasMember("NumericalProjectVersion") || !oDocument["NumericalProjectVersion"].IsInt()) { if(pError != NULL) *pError = "Numerical project version is missing."; return false; } const int nVersion = oDocument["NumericalProjectVersion"].GetInt(); if(nVersion != nmNumericalResultPersistence::projectVersion()) { if(pError != NULL) { *pError = nVersion == 2 ? "Numerical project version 2 is not supported." : "Numerical project version is not supported."; } return false; } if(!oDocument.HasMember("InputRevisions") || !oDocument["InputRevisions"].IsObject() || !oDocument.HasMember("CurrentResultWellInstanceId") || !oDocument["CurrentResultWellInstanceId"].IsString() || !oDocument.HasMember("PayloadFiles") || !oDocument["PayloadFiles"].IsObject()) { if(pError != NULL) *pError = "Numerical v3 main JSON header is incomplete."; return false; } const rapidjson::Value& oRevisions = oDocument["InputRevisions"]; if(!oRevisions.HasMember("GridInputRevision") || !oRevisions["GridInputRevision"].IsUint64() || !oRevisions.HasMember("BuiltGridRevision") || !oRevisions["BuiltGridRevision"].IsUint64() || !oRevisions.HasMember("ResultInputRevision") || !oRevisions["ResultInputRevision"].IsUint64()) { if(pError != NULL) *pError = "Numerical input revisions are invalid."; return false; } nGridInputRevision = oRevisions["GridInputRevision"].GetUint64(); nBuiltGridRevision = oRevisions["BuiltGridRevision"].GetUint64(); nResultInputRevision = oRevisions["ResultInputRevision"].GetUint64(); if(nGridInputRevision == 0 || nResultInputRevision == 0 || (nBuiltGridRevision != 0 && nBuiltGridRevision != nGridInputRevision)) { if(pError != NULL) *pError = "Numerical input revision values are invalid."; return false; } sCurrentResultWellInstanceId = QString::fromUtf8( oDocument["CurrentResultWellInstanceId"].GetString()); const rapidjson::Value& oPayload = oDocument["PayloadFiles"]; if(!oPayload.HasMember("CurrentGrid") || !oPayload.HasMember("WellHistories") || !oPayload["WellHistories"].IsArray() || oPayload["WellHistories"].Size() > 10000u || !oPayload.HasMember("Snapshot")) { if(pError != NULL) *pError = "Numerical payload reference list is invalid."; return false; } QSet setPaths; oReferences = nmNumericalWindowPayloadReferences(); if(!oPayload["CurrentGrid"].IsNull()) { if(!readNumericalFileReference(oPayload, "CurrentGrid", oReferences.m_oCurrentGrid)) { if(pError != NULL) *pError = "Current grid reference is invalid."; return false; } oReferences.m_bHasCurrentGrid = true; setPaths.insert(oReferences.m_oCurrentGrid.m_sRelativePath); } const rapidjson::Value& oHistories = oPayload["WellHistories"]; for(rapidjson::SizeType nIndex = 0; nIndex < oHistories.Size(); ++nIndex) { const rapidjson::Value& oJson = oHistories[nIndex]; nmNumericalFileReference oReference; if(!oJson.IsObject() || !oJson.HasMember("WellInstanceId") || !oJson["WellInstanceId"].IsString() || !readNumericalFileReference(oJson, "File", oReference)) { if(pError != NULL) *pError = "Well history reference is invalid."; return false; } const QString sWellInstanceId = QString::fromUtf8( oJson["WellInstanceId"].GetString()); if(sWellInstanceId.isEmpty() || oReferences.m_mapWellHistories.contains(sWellInstanceId) || setPaths.contains(oReference.m_sRelativePath)) { if(pError != NULL) *pError = "Well history references are duplicated."; return false; } oReferences.m_mapWellHistories.insert(sWellInstanceId, oReference); setPaths.insert(oReference.m_sRelativePath); } if(!oPayload["Snapshot"].IsNull()) { if(!readNumericalFileReference(oPayload, "Snapshot", oReferences.m_oSnapshot) || setPaths.contains(oReferences.m_oSnapshot.m_sRelativePath)) { if(pError != NULL) *pError = "Snapshot reference is invalid or duplicated."; return false; } oReferences.m_bHasSnapshot = true; } return true; } } ZX_DEFINE_DYNAMIC(DataAnalyzeManager, nmDataAnalyzeManager) nmDataAnalyzeManager::nmDataAnalyzeManager(): ZxDataObjectBin(0) { m_pOwnerFitting = nullptr; m_nBackgroundUseCount = 0; m_bPebiGridGenerationActive = false; m_nPebiGridGenerationRevision = 0; m_reservoirData = nullptr; m_outlineData = nullptr; m_pMeasuringScaleData = nullptr; m_pebiPvtPara = nullptr; m_pMixedResults = nullptr; m_pLayerData = nullptr; m_pCurDataWell = nullptr; m_pMeasureData = nullptr; m_axisData = nullptr; m_pNmGuiPlot = nullptr; m_pGeoRefData = nullptr; m_pTimeStep = nullptr; //m_pPerCloData = nullptr; m_pAutomaticFittingData = nullptr; m_pDiagnosticData = nullptr; //m_pSkinVsRateData = nullptr; //m_pFlowSegmentData = nullptr; m_pForecastData = nullptr; m_pSensitiveData = nullptr; // 属性注册表 m_pAttrRegistry = new nmAttrRegistry(); m_backgroundImageInfo.bIsVisible = false; // 初始化混合参数数据(Temp) //m_pMixedResults = new nmDataMixedResults; // 初始化储层数据 m_pLayerData = new nmDataLayer; m_eGridType = NM_Grid_Type::NM_Grid_PEBI; //默认网格类型为PEBI m_eSolverModelType = NM_SOLVER_MODEL_TYPE::SMT_Oil_ConstPvt; m_nPebiSolverType = PebiSolverCpuAccelerated; m_nPebiOmpThreads = 4; m_nPebiIluReuseSteps = 5; this->initDefaultDisplaySettings(); // 初始化中英文翻译映射 nmTranslationManager::initTranslations(); m_bIsLoadData = false; // 获取完整路径 //QString appPath = QCoreApplication::applicationFilePath(); //qDebug() << "完整应用路径:" << appPath; // 许可证路径 //m_licensePath = appPath + "/../../3rd/Pebi/license/HXNWTM_license.dat"; // 获取完整路径 QString appPath = QCoreApplication::applicationFilePath(); // 获取应用程序所在的目录 QString appDir = QFileInfo(appPath).absolutePath(); // 定义从应用程序目录到许可证目录的相对路径 QString relativeJump = "/../Res/license/HXNWTM_license.dat"; // 将目录和相对跳转路径拼接 m_licensePath = appDir + relativeJump; } // 初始化静态成员 nmDataAnalyzeManager::~nmDataAnalyzeManager() { // 第一步:任何窗口销毁顺序下,都必须先等后台网格/求解任务停止访问。 // wait() 会暂时释放互斥量,因此任务仍能调用 endBackgroundUse() 正常退出。 { QMutexLocker oLocker(&m_oBackgroundUseMutex); while(m_nBackgroundUseCount > 0) { m_oNoBackgroundUseCondition.wait(&m_oBackgroundUseMutex); } } // 图元只引用数据,数据统一由DataManager释放。 // 关闭成果或流动段分析后,清理该分析窗口对应的全部数据。 // 这两个成员是借用引用或容器内对象的别名,不单独delete。 m_pCurDataWell = nullptr; m_pNmGuiPlot = nullptr; // 先释放容器中的数据对象,再释放其余独占的单对象数据。 qDeleteAll(m_vWellData); m_vWellData.clear(); qDeleteAll(m_vFaultData); m_vFaultData.clear(); qDeleteAll(m_vFractureData); m_vFractureData.clear(); qDeleteAll(m_vRegionData); m_vRegionData.clear(); qDeleteAll(m_vRegionMarkData); m_vRegionMarkData.clear(); qDeleteAll(m_vecLayers); m_vecLayers.clear(); delete m_outlineData; m_outlineData = nullptr; delete m_axisData; m_axisData = nullptr; delete m_pAutomaticFittingData; m_pAutomaticFittingData = nullptr; delete m_reservoirData; m_reservoirData = nullptr; delete m_pMeasuringScaleData; m_pMeasuringScaleData = nullptr; delete m_pMeasureData; m_pMeasureData = nullptr; delete m_pGeoRefData; m_pGeoRefData = nullptr; delete m_pForecastData; m_pForecastData = nullptr; delete m_pSensitiveData; m_pSensitiveData = nullptr; delete m_pDiagnosticData; m_pDiagnosticData = nullptr; delete m_pebiPvtPara; m_pebiPvtPara = nullptr; delete m_pMixedResults; m_pMixedResults = nullptr; delete m_pLayerData; m_pLayerData = nullptr; delete m_pTimeStep; m_pTimeStep = nullptr; // 最后 delete 属性注册表(确保数据对象先销毁,nmAttrRegistry 能接收 destroyed 信号清理映射) delete m_pAttrRegistry; m_pAttrRegistry = nullptr; } QMap nmDataAnalyzeManager::s_mapDataAnalManager; iSubWndFitting* nmDataAnalyzeManager::s_pCurSubWndFitting = nullptr; nmDataAnalyzeManager* nmDataAnalyzeManager::getInstanceByFitting(iSubWndFitting* pSubWndF) { if(s_mapDataAnalManager.contains(pSubWndF)) { return s_mapDataAnalManager[pSubWndF]; } nmDataAnalyzeManager* pInstance = new nmDataAnalyzeManager(); // DataManager 与创建它的成果窗口一一绑定。后续后台任务读取 PVT、拟压力等 // 上下文时必须使用该窗口,不能随界面当前页签切换到另一份成果。 pInstance->m_pOwnerFitting = pSubWndF; s_mapDataAnalManager[pSubWndF] = pInstance; return pInstance; } void nmDataAnalyzeManager::removeInstanceByFitting(iSubWndFitting* pSubWndF) { if(pSubWndF == nullptr) { return; } // take()先解除窗口和manager的映射,避免析构期间再次找到待释放对象。 nmDataAnalyzeManager* pInstance = s_mapDataAnalManager.take(pSubWndF); if(s_pCurSubWndFitting == pSubWndF) { s_pCurSubWndFitting = nullptr; } delete pInstance; } nmDataAnalyzeManager* nmDataAnalyzeManager::getCurrentInstance() { if(s_mapDataAnalManager.contains(s_pCurSubWndFitting)) { return s_mapDataAnalManager[s_pCurSubWndFitting]; } else { return nullptr; } } void nmDataAnalyzeManager::beginBackgroundUse() { QMutexLocker oLocker(&m_oBackgroundUseMutex); ++m_nBackgroundUseCount; } void nmDataAnalyzeManager::endBackgroundUse() { QMutexLocker oLocker(&m_oBackgroundUseMutex); if(m_nBackgroundUseCount <= 0) { Q_ASSERT(false); return; } --m_nBackgroundUseCount; if(m_nBackgroundUseCount == 0) { m_oNoBackgroundUseCondition.wakeAll(); } } nmDataWellBase *nmDataAnalyzeManager::createWell(NM_WELL_MODEL eWellType) { // 根据当前Fitting窗口来获取对应的井相关数据 iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting(); nmDataAnalyzeContextProvider* pContextProvider = nmDataAnalyzeContext::provider(); Q_ASSERT(nullptr != pSubWndFitting); Q_ASSERT(nullptr != pContextProvider); nmDataWellBase* pWellData = nullptr; // 新建一口井,默认直井 if(eWellType == NM_WELL_MODEL::Vertical_Well) { // 直接使用子类指针创建对象 nmDataVerticalWell* verticalWell = new nmDataVerticalWell; pWellData = static_cast(verticalWell); } else if(eWellType == NM_WELL_MODEL::Vertical_Fractured_Well) { // 直接使用子类指针创建对象 nmDataVerticalFracturedWell* vFracturedWell = new nmDataVerticalFracturedWell; pWellData = static_cast(vFracturedWell); } else if(eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { // 直接使用子类指针创建对象 nmDataHorizontalFracturedWell* hFracturedWell = new nmDataHorizontalFracturedWell; pWellData = static_cast(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(pNewWellData); if(pVerticalFracturedWell != nullptr) { pVerticalFracturedWell->setFracs(); } nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast(pNewWellData); if(pHorizontalFracturedWell != nullptr) { pHorizontalFracturedWell->setFracs(); } // 第四步:原位提交替换,WellCode 不变,因此主井、包含井和结果井关联自然保留。 if(!replaceWellData(pOldWellData, pNewWellData)) { delete pNewWellData; return nullptr; } return pNewWellData; } bool nmDataAnalyzeManager::isPrimaryWell( const nmDataWellBase* pWellData) const { if(pWellData == nullptr) { return false; } const QString sPrimaryWellCode = m_oNumericalAnalysisCase.getPrimaryWellCode(); return !sPrimaryWellCode.isEmpty() && pWellData->getWellCode() == sPrimaryWellCode; } bool nmDataAnalyzeManager::canRemoveWell( const nmDataWellBase* pWellData) const { if(pWellData == nullptr || isPrimaryWell(pWellData)) { return false; } // 不通过 WellCode 代替所有权判断,避免同编码外部对象误删管理器内的井。 for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { if(m_vWellData[nIndex] == pWellData) { return true; } } return false; } bool nmDataAnalyzeManager::removeWell(nmDataWellBase* pWellData) { // 固定主井及非法指针必须在任何版本递增、图元解绑和对象删除前拒绝。 // 该检查是数据层最终兜底,不能只依赖 Map 界面的提前拦截。 if(!canRemoveWell(pWellData)) { return false; } const QString removedWellName = pWellData->getWellName(); const QString removedWellCode = pWellData->getWellCode(); const bool isCurrentWell = (m_pCurDataWell == pWellData); // 第二步:Map 中删除任意真实井都会改变网格井集合。即使它是自动参与的 // 无产量观察井,也必须让旧网格、旧求解器顺序和旧结果一起失效。 m_oNumericalAnalysisCase.invalidateGrid(); // 第三步:删除井对象前,先从“包含其他井”中移除对应 WellCode。 QVector vecIncludedWells = m_oNumericalAnalysisCase.getIncludedWells(); for(int nIndex = vecIncludedWells.size() - 1; nIndex >= 0; --nIndex) { if(vecIncludedWells[nIndex].m_sWellCode == removedWellCode) { vecIncludedWells.remove(nIndex); } } m_oNumericalAnalysisCase.setIncludedWells(vecIncludedWells); // 如果删的是当前井,切换到其他有效井;如果没有,则清空当前井指针。 if(isCurrentWell) { m_pCurDataWell = nullptr; for(int i = 0; i < m_vWellData.size(); ++i) { nmDataWellBase* pCandidate = m_vWellData[i]; if(pCandidate != nullptr && pCandidate != pWellData) { m_pCurDataWell = pCandidate; break; } } } QString removedCode = pWellData->getWellCode(); QString removedName = pWellData->getWellName(); // 先从 registry 清理该井的所有属性,避免野指针 if (m_pAttrRegistry) { for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) { if (!isWellParaOf(WELL_PARA_DESCS[i], pWellData->getWellType())) { continue; } QString name = removedCode + "_" + WELL_PARA_DESCS[i].sName; m_pAttrRegistry->unregAttr(name); } } // 倒序移除全部匹配项,兼容旧逻辑可能遗留的重复指针。 for (int i = m_vWellData.size() - 1; i >= 0; --i) { if (m_vWellData[i] == pWellData) { m_vWellData.remove(i); } } delete pWellData; pWellData = nullptr; emit sigWellRemoved(removedCode, removedName); emit dataChanged(); return true; } void nmDataAnalyzeManager::removeWellDataAndPlot(nmDataWellBase* pWellData) { // 删除图元之前先执行同一业务预检,避免主井数据被拒绝后图元已经消失。 if(!canRemoveWell(pWellData)) { return; } nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider(); if (m_pNmGuiPlot != nullptr && pPlotContextProvider != nullptr) { // 移除所有井图元 pPlotContextProvider->removeWellPlotByData(m_pNmGuiPlot, pWellData); removeWell(pWellData); } } void nmDataAnalyzeManager::clearAllWellData() { typedef QPair WellIdentity; QList removedWells; // 全量清空表示当前分析上下文被丢弃;先发出快照清除通知, // 此时实时井对象仍然有效,直接连接的 UI 刷新不会遇到悬空井指针。 clearPebiResultSnapshot(); // 遍历并删除所有井对象,兼容旧逻辑可能遗留的重复指针。 QSet 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 nmDataAnalyzeManager::getWellDataList() const { return m_vWellData; } //QVector nmDataAnalyzeManager::getWellDataListCopy() const //{ // QVector result; // result.reserve(m_vWellData.size()); // // foreach(const nmDataWellBase* well, m_vWellData) { // if(well) { // result.append(*well); // 调用拷贝构造函数 // } // } // // return result; //} //void nmDataAnalyzeManager::updateWellData(const QVector& 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(existingWell)) { // if (auto newVFractured = dynamic_cast(&newWell)) { // *existingVFractured = *newVFractured; // } // } // else if (auto existingHFractured = dynamic_cast(existingWell)) { // if (auto newHFractured = dynamic_cast(&newWell)) { // *existingHFractured = *newHFractured; // } // } else if (auto existingVertical = dynamic_cast(existingWell)) { // if (auto newVertical = dynamic_cast(&newWell)) { // *existingVertical = *newVertical; // 调用赋值运算符 // } // } // } // } // //} void nmDataAnalyzeManager::updateVerticalWells(const QVector& wells) { bool bWellUpdated = false; foreach(const auto& newWell, wells) { const QString sWellInstanceId = newWell.getWellInstanceId(); if(sWellInstanceId.isEmpty()) { qWarning() << "Cannot update vertical well without UUID:" << newWell.getWellName(); continue; } foreach(auto* existingWell, m_vWellData) { if(auto * vWell = dynamic_cast(existingWell)) { // UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。 if(vWell->getWellInstanceId() == sWellInstanceId) { *vWell = newWell; // 调用赋值运算符 bWellUpdated = true; break; } } } } // 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。 if(bWellUpdated) { rebindPebiResultSnapshotToWells(); } } void nmDataAnalyzeManager::updateVerticalFracturedWells(const QVector& wells) { bool bWellUpdated = false; foreach(const auto& newWell, wells) { const QString sWellInstanceId = newWell.getWellInstanceId(); if(sWellInstanceId.isEmpty()) { qWarning() << "Cannot update vertical fractured well without UUID:" << newWell.getWellName(); continue; } foreach(auto* existingWell, m_vWellData) { if(auto * vWell = dynamic_cast(existingWell)) { // UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。 if(vWell->getWellInstanceId() == sWellInstanceId) { *vWell = newWell; // 调用赋值运算符 bWellUpdated = true; break; } } } } // 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。 if(bWellUpdated) { rebindPebiResultSnapshotToWells(); } } void nmDataAnalyzeManager::updateHorizontalFracturedWells(const QVector& wells) { bool bWellUpdated = false; foreach(const auto& newWell, wells) { const QString sWellInstanceId = newWell.getWellInstanceId(); if(sWellInstanceId.isEmpty()) { qWarning() << "Cannot update horizontal fractured well without UUID:" << newWell.getWellName(); continue; } foreach(auto* existingWell, m_vWellData) { if(auto * vWell = dynamic_cast(existingWell)) { // UUID 是井实例的唯一身份,禁止同名新井覆盖旧井。 if(vWell->getWellInstanceId() == sWellInstanceId) { *vWell = newWell; // 调用赋值运算符 bWellUpdated = true; break; } } } } // 井赋值不复制快照绑定,并会解除目标井的弱引用,回写后必须由 Manager 统一恢复。 if(bWellUpdated) { rebindPebiResultSnapshotToWells(); } } // 获取所有直井数据 QVector nmDataAnalyzeManager::getVerticalWellData() const { QVector verticalWells; foreach(nmDataWellBase* well, m_vWellData) { nmDataVerticalWell* vWell = dynamic_cast(well); if(vWell && !dynamic_cast(well)) { // 确保不是垂直裂缝井 verticalWells.append(vWell); } } return verticalWells; } // 获取所有垂直裂缝井数据 QVector nmDataAnalyzeManager::getVerticalFracturedWellData() const { QVector vFracturedWells; foreach(nmDataWellBase* well, m_vWellData) { nmDataVerticalFracturedWell* vFracturedWell = dynamic_cast(well); if(vFracturedWell) { vFracturedWells.append(vFracturedWell); } } return vFracturedWells; } // 获取所有多段压裂水平井数据 QVector nmDataAnalyzeManager::getHorizontalFracturedWellData() const { QVector hFracturedWells; foreach(nmDataWellBase* well, m_vWellData) { nmDataHorizontalFracturedWell* hFracturedWell = dynamic_cast(well); if(hFracturedWell) { hFracturedWells.append(hFracturedWell); } } return hFracturedWells; } nmDataWellBase* nmDataAnalyzeManager::findWellByName(QString wellName) const { foreach(nmDataWellBase* pWell, m_vWellData) { if(pWell && pWell->getWellName() == wellName) { return pWell; // 找到匹配的井,返回指针 } } return nullptr; // 未找到匹配的井,返回 nullptr } nmDataWellBase* nmDataAnalyzeManager::findWellByCode( const QString& sWellCode) const { if(sWellCode.isEmpty()) { return nullptr; } foreach(nmDataWellBase* pWell, m_vWellData) { if(pWell != nullptr && pWell->getWellCode() == sWellCode) { return pWell; } } return nullptr; } nmDataWellBase* nmDataAnalyzeManager::findWellByInstanceId( const QString& sWellInstanceId) const { if(sWellInstanceId.isEmpty()) { return nullptr; } foreach(nmDataWellBase* pWell, m_vWellData) { if(pWell != nullptr && pWell->getWellInstanceId() == sWellInstanceId) { return pWell; } } return nullptr; } void nmDataAnalyzeManager::initCurWellData() { // 获取当前默认井数据 ZxDataWell* pWellData = zxCurWell; if(pWellData == nullptr) { return; } // 判断是哪一种井类型,初始化对应的参数 QString wellClass = pWellData->getWellClassEn(); // 获取当前井的压力、流量数据 ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP); ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF); ZxDataGaugeP* pGaugeP = nullptr; ZxDataGaugeF* pGaugeF = nullptr; // 遍历压力数据列表 for(int i = 0; i < m_listGaugeP.size(); ++i) { if(pGaugeP = dynamic_cast(m_listGaugeP[i])) { // 拿到第一条压力数据 break; } } // 遍历流量数据列表 for(int i = 0; i < m_listGaugeF.size(); ++i) { if(pGaugeF = dynamic_cast(m_listGaugeF[i])) { // 拿到第一条流量数据 break; } } // 获取的压力、流量数据 QVector 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(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> vvecHistoryPressureData; //压力历史数据 QVector> vvecHistoryLogData; // 历史双对数曲线数据 QVector> 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(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> vvecHistoryPressureData; //压力历史数据 QVector> vvecHistoryLogData; // 历史双对数曲线数据 QVector> 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(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> vvecHistoryPressureData; //压力历史数据 QVector> vvecHistoryLogData; // 历史双对数曲线数据 QVector> vvecHistorySemiLogData; // 历史半对数曲线数据 this->calculationLogData(pHFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); //使用setter方法存储历史数据到井对象中 pHFracturedWell->setHistoryPressure(vvecHistoryPressureData); pHFracturedWell->setHistoryLogLog(vvecHistoryLogData); pHFracturedWell->setHistorySemiLog(vvecHistorySemiLogData); // 计算裂缝数据 pHFracturedWell->setFracs(); // 设置为当前查看的井 this->setCurWellData(pHFracturedWell); } // 第一次导入的当前流动段井固定为数值分析入口井。后续切换查看井或 // 重复初始化不得改变主井身份及其求解角色。 if(m_oNumericalAnalysisCase.getPrimaryWellCode().isEmpty() && m_pCurDataWell != nullptr && !m_pCurDataWell->getWellCode().isEmpty()) { const QString sPrimaryWellCode = m_pCurDataWell->getWellCode(); m_oNumericalAnalysisCase.setPrimaryWellCode(sPrimaryWellCode); m_oNumericalAnalysisCase.setPrimaryWellMode( m_pCurDataWell->getFlowPoints().size() >= 2 ? NM_CaseWell_RateControlled : NM_CaseWell_Observation); } } void nmDataAnalyzeManager::calculationLogData( nmDataWellBase* pWellData, QVector>& vvecHistoryData, QVector>& vvecLogPreData, QVector>& 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 vecPressure = pWellData->getPressurePoints(); std::vector 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 vecTimeQ = pWellData->getFlowPoints(); if(vecPressure.isEmpty() || vecTimeQ.size() < 2) { return; } int nTimeNumQ = vecTimeQ.size() - 1; if (nTimeNumQ <= 0) { // 没有流量段数据,无法计算双对数/半对数曲线 return; } std::vector timeQ(nTimeNumQ); std::vector q(nTimeNumQ); for(int i = 0; i < nTimeNumQ; ++i) { timeQ[i] = vecTimeQ[i + 1].x(); q[i] = vecTimeQ[i + 1].y(); } // 调用 DLL 计算双对数曲线 std::vector logPre; int iSectionFlowIndex = pWellData->getIndexF(); HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll"); if(hMod_solver) { typedef bool (*PreLog)(const std::vector&, const int&, double*, double*, int, std::vector&); 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::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(m_listGaugeP[i])) { // 拿到第一条压力数据 break; } } // 遍历流量数据列表 for(int i = 0; i < m_listGaugeF.size(); ++i) { if(pGaugeF = dynamic_cast(m_listGaugeF[i])) { // 拿到第一条流量数据 break; } } // 获取的压力、流量数据 QVector 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(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(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(pWell); if(pHFracturedWell == nullptr) { return nullptr; } pAddedWell = pHFracturedWell; pHFracturedWell->setWellName(pWellData->getName()); pHFracturedWell->setWellCode(pWellData->getCode()); nmDataAttribute tempAttr = pHFracturedWell->getX(); tempAttr.setValue(pWellData->getLocationX()); pHFracturedWell->setX(tempAttr); tempAttr = pHFracturedWell->getY(); tempAttr.setValue(pWellData->getLocationY()); pHFracturedWell->setY(tempAttr); tempAttr = pHFracturedWell->getRadius(); tempAttr.setValue(pWellData->getWellRadius()); pHFracturedWell->setRadius(tempAttr); // 设置井的压力数据、流量数据 pHFracturedWell->setPressurePoints(vecPtsP); pHFracturedWell->setFlowPoints(vecPtsF); // 计算裂缝数据 pHFracturedWell->setFracs(); // 设置流量段索引 pHFracturedWell->setIndexF(nIndexF); } // 第二步:不支持的项目井型不进入 Map,也不发送伪造的新增井通知。 if(pAddedWell == nullptr) { return nullptr; } // 第三步:井数据完整建立后,再通知 Map 和参数面板新增井分组。 { syncWellAttrs(); QString code = pWellData->getCode(); QString name = pWellData->getName(); QStringList paras; for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) { if (isWellParaOf(WELL_PARA_DESCS[i], pAddedWell->getWellType())) { paras << WELL_PARA_DESCS[i].sName; } } emit sigWellAdded(code, name, paras); } return pAddedWell; } void nmDataAnalyzeManager::appendNmWellData(nmDataWellBase* pWellData) { if (pWellData == nullptr) return; bool bWellAdded = false; if (!m_vWellData.contains(pWellData)) { m_vWellData.append(pWellData); bWellAdded = true; } // 外部先构造、再加入 Map 的井没有经过 createWell(),需要在这里统一 // 使旧网格失效。已由 createWell() 登记的井不重复增加输入版本号。 if(bWellAdded) { m_oNumericalAnalysisCase.invalidateGrid(); } // 通知面板新增井分组 { syncWellAttrs(); QString code = pWellData->getWellCode(); QString name = pWellData->getWellName(); QStringList paras; for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) { if (isWellParaOf(WELL_PARA_DESCS[i], pWellData->getWellType())) { paras << WELL_PARA_DESCS[i].sName; } } emit sigWellAdded(code, name, paras); } } bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell, nmDataWellBase* pNewWell) { // 先完成所有无副作用的前置校验,失败时保持双方所有权不变。 if (pOldWell == nullptr || pNewWell == nullptr || pOldWell == pNewWell || m_vWellData.contains(pNewWell) || pOldWell->getWellCode().isEmpty() || pOldWell->getWellCode() != pNewWell->getWellCode() || pOldWell->getWellInstanceId().isEmpty() || pOldWell->getWellInstanceId() != pNewWell->getWellInstanceId()) { return false; } int nOldIndex = m_vWellData.indexOf(pOldWell); if (nOldIndex < 0) { return false; } // 删除旧对象前保存观察者通知所需信息,后续不得再解引用旧指针。 QString sOldCode = pOldWell->getWellCode(); QString sOldName = pOldWell->getWellName(); bool bWasCurrentWell = m_pCurDataWell == pOldWell; // 第一步:在原位置替换全部旧指针;同一 WellCode 的分析方案关联无需迁移。 for (int nIndex = m_vWellData.size() - 1; nIndex >= 0; --nIndex) { if (m_vWellData[nIndex] == pOldWell) { m_vWellData.remove(nIndex); } } m_vWellData.insert(qMin(nOldIndex, m_vWellData.size()), pNewWell); // 井型改变会改变 PEBI 几何输入,旧求解器顺序和网格必须整体失效。 m_oNumericalAnalysisCase.invalidateGrid(); if (bWasCurrentWell) { m_pCurDataWell = pNewWell; } // 第二步:先让注册表指向新对象,再销毁旧对象并统一通知观察者。 syncWellAttrs(); delete pOldWell; // 同 UUID 井型替换后,新对象必须重新获得最后结果的弱引用。 rebindPebiResultSnapshotToWells(); QStringList listParameters; for (int nIndex = 0; nIndex < WELL_PARA_DESC_COUNT; ++nIndex) { if (isWellParaOf(WELL_PARA_DESCS[nIndex], pNewWell->getWellType())) { listParameters << WELL_PARA_DESCS[nIndex].sName; } } // 沿用既有移除和添加信号,保证工程树及属性页无需感知替换细节。 emit sigWellRemoved(sOldCode, sOldName); emit sigWellAdded(pNewWell->getWellCode(), pNewWell->getWellName(), listParameters); emit dataChanged(); return true; } /// @brief 创建油藏数据对象,从界面读取PVT单值和分层数据,构建reservoir对象 void nmDataAnalyzeManager::createReservoir() { // 1. 获取上下文 iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting(); nmDataAnalyzeContextProvider* pCtx = nmDataAnalyzeContext::provider(); Q_ASSERT(nullptr != pSubWndFitting); Q_ASSERT(nullptr != pCtx); PvtFluidType eType = WFT_Null; pCtx->getBasicPft(pSubWndFitting, eType); // 2. 读取PVT单值参数 QMap 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 activeAttrNames; QSet 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 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 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> 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 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 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 nmDataAnalyzeManager::getRegionMarkDataListCopy() const { QVector result; result.reserve(m_vRegionMarkData.size()); foreach(const nmDataRegionMark* regionMark, m_vRegionMarkData) { if(regionMark) { result.append(*regionMark); // 调用拷贝构造函数 } } return result; } void nmDataAnalyzeManager::updateRegionMarkData(const QVector& 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 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 nmDataAnalyzeManager::getRegionDataListCopy() const { QVector result; result.reserve(m_vRegionData.size()); foreach(const nmDataRegion* region, m_vRegionData) { if(region) { result.append(*region); // 调用拷贝构造函数 } } return result; } void nmDataAnalyzeManager::updateRegionData(const QVector& 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 nmDataAnalyzeManager::getFractureDataList() const { return m_vFractureData; } QVector nmDataAnalyzeManager::getDFNFractureDataList() const { QVector 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 nmDataAnalyzeManager::getFractureDataListCopy() const { QVector result; result.reserve(m_vFractureData.size()); foreach(const nmDataFracture* fracture, m_vFractureData) { if(fracture) { result.append(*fracture); // 调用拷贝构造函数 } } return result; } void nmDataAnalyzeManager::updateFractureData(const QVector& 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 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 nmDataAnalyzeManager::getFaultDataListCopy() const { QVector result; result.reserve(m_vFaultData.size()); foreach(const nmDataFault* fault, m_vFaultData) { if(fault) { result.append(*fault); // 调用拷贝构造函数 } } return result; } void nmDataAnalyzeManager::updateFaultData(const QVector& 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 nmDataAnalyzeManager::getValueAllConnectablePoints() { QVector vecAllPoints; // 遍历所有断层数据 foreach(nmDataFault* fault, m_vFaultData) { if(fault) { QVector faultPoints = fault->getFaultPoints(); foreach(const QPointF& point, faultPoints) { vecAllPoints.append(point); } } } // 遍历所有裂缝数据 foreach(nmDataFracture* fracture, m_vFractureData) { if(fracture) { QVector fracturePoints = fracture->getFracturePoints(); foreach(const QPointF& point, fracturePoints) { vecAllPoints.append(point); } } } // 遍历所有复合区数据 foreach(nmDataRegion* region, m_vRegionData) { if(region) { QVector regionPoints = region->getVecPts(); foreach(const QPointF& point, regionPoints) { vecAllPoints.append(point); } } } // 处理边界数据 if(m_outlineData) { QVector 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(well); if(pVFwell == nullptr) { continue; } QVector fracsPoints = pVFwell->getFracs(); foreach(const QPointF& point, fracsPoints) { vecAllPoints.append(point); } } } return vecAllPoints; } QVector nmDataAnalyzeManager::getPosAllConnectablePoints() { // 将绘图坐标系转为qt坐标系 QVector vecValues = this->getValueAllConnectablePoints(); QVector 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 nmDataAnalyzeManager::removePosByObject(QObject * obj) { // 获取所有可连接的点(屏幕坐标) QVector vecAllPoints = this->getPosAllConnectablePoints(); // 创建一个临时容器用于存储要移除的点 QVector pointsToRemove; // 检查传入的对象类型并收集相关点 if(nmDataFault * fault = dynamic_cast(obj)) { QVector faultPoints = fault->getFaultPoints(); foreach(const QPointF& point, faultPoints) { pointsToRemove.append(point); } } else if(nmDataFracture * fracture = dynamic_cast(obj)) { QVector fracturePoints = fracture->getFracturePoints(); foreach(const QPointF& point, fracturePoints) { pointsToRemove.append(point); } } else if(nmDataRegion * region = dynamic_cast(obj)) { QVector regionPoints = region->getVecPts(); foreach(const QPointF& point, regionPoints) { pointsToRemove.append(point); } } else if(nmDataVerticalFracturedWell * well = dynamic_cast(obj)) { QVector fracsPoints = well->getFracs(); foreach(const QPointF& point, fracsPoints) { pointsToRemove.append(point); } } else if(m_outlineData == obj) { QVector 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 vecPosRemove; nmDataPlotContextProvider* pPlotContextProvider = nmDataPlotContext::provider(); if(m_pNmGuiPlot && pPlotContextProvider) { pPlotContextProvider->getPosForValue(m_pNmGuiPlot, pointsToRemove, vecPosRemove); } // 移除与特定对象相关的点 QVector::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> nmDataAnalyzeManager::getPebiSolverHistoryDataByName(const QString & wellName) { QVector> vvecHisotryData; QVector vX; QVector 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> nmDataAnalyzeManager::getPebiSolverLogPreDataByName(const QString & wellName) { QVector> vvecLogPreData; QVector vX; QVector vY; QVector 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> nmDataAnalyzeManager::getPebiSolverSemiLogPreDataByName(const QString & wellName) { QVector> vvecSemiLogPreData; QVector vX; QVector 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& pressure, std::vector& pseudoPressure) { pressure.clear(); pseudoPressure.clear(); nmDataAnalyzeContextProvider* context = nmDataAnalyzeContext::provider(); // 使用本 DataManager 创建时绑定的成果窗口;切换当前页签不能改变数据来源。 iSubWndFitting* fitting = m_pOwnerFitting; VVecDouble pseudoResult; if(context == nullptr || fitting == nullptr || !context->getPseuRstOf(fitting, pseudoResult) || pseudoResult.size() < 2 || pseudoResult[0].size() < 2 || pseudoResult[0].size() != pseudoResult[1].size()) { return false; } pressure = pseudoResult[0].toStdVector(); pseudoPressure = pseudoResult[1].toStdVector(); return true; } nmDataMixedResults* nmDataAnalyzeManager::getMixedResults() { return m_pMixedResults; } nmDataMixedResults* nmDataAnalyzeManager::createMixedResult() { if(m_pMixedResults != nullptr) { delete m_pMixedResults; m_pMixedResults = nullptr; } m_pMixedResults = new nmDataMixedResults; return m_pMixedResults; } nmDataLayer* nmDataAnalyzeManager::getLayerData() { // 1. 删除 m_vecLayers 中当前存储的所有 nmDataLayer 对象,释放内存 qDeleteAll(m_vecLayers); // 2. 清空 m_vecLayers 自身,移除所有指针 m_vecLayers.clear(); return m_pLayerData; } void nmDataAnalyzeManager::setLayers(QVector vecLayers) { m_vecLayers = vecLayers; // 几何分层会改变储层离散输入,旧网格和旧结果不再有效。 m_oNumericalAnalysisCase.invalidateGrid(); emit dataChanged(); } QVector 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& 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& 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> nmDataAnalyzeManager::getCalculationWells() const { QVector > vecLegacyOrder; QVector vecSolverOrder = getSolverWellOrder(); for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) { vecLegacyOrder.append(qMakePair(NM_WELL_MODEL::Unknow_Well, QString())); continue; } nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode); if(pWellData != nullptr) { vecLegacyOrder.append(qMakePair(oWellRef.m_eWellType, pWellData->getWellName())); } } return vecLegacyOrder; } bool nmDataAnalyzeManager::isContainsWellName(const QString & wellName) const { nmDataWellBase* pWellData = findWellByName(wellName); return pWellData != nullptr && isWellSelectedForCalculation(pWellData->getWellCode()); } nmDataNumericalAnalysisCase* nmDataAnalyzeManager::getNumericalAnalysisCase() { return &m_oNumericalAnalysisCase; } const nmDataNumericalAnalysisCase* nmDataAnalyzeManager::getNumericalAnalysisCase() const { return &m_oNumericalAnalysisCase; } void nmDataAnalyzeManager::setPrimaryWellCode(const QString& sWellCode) { if(!sWellCode.isEmpty() && findWellByCode(sWellCode) == nullptr) { return; } const quint64 nOldRevision = m_oNumericalAnalysisCase.getGridInputRevision(); m_oNumericalAnalysisCase.setPrimaryWellCode(sWellCode); if(m_oNumericalAnalysisCase.getGridInputRevision() != nOldRevision) { emit dataChanged(); } } QString nmDataAnalyzeManager::getPrimaryWellCode() const { return m_oNumericalAnalysisCase.getPrimaryWellCode(); } void nmDataAnalyzeManager::setIncludeOtherWells(bool bInclude) { const bool bOldInclude = m_oNumericalAnalysisCase.getIncludeOtherWells(); m_oNumericalAnalysisCase.setIncludeOtherWells(bInclude); if(bOldInclude != m_oNumericalAnalysisCase.getIncludeOtherWells()) { // 开关只改变当前求解输入。最后结果下拉框继续使用快照冻结的 UUID 列表。 emit dataChanged(); } } bool nmDataAnalyzeManager::getIncludeOtherWells() const { return m_oNumericalAnalysisCase.getIncludeOtherWells(); } void nmDataAnalyzeManager::setPebiGridControl(double dGridControl) { m_oNumericalAnalysisCase.setPebiGridControl(dGridControl); } double nmDataAnalyzeManager::getPebiGridControl() const { return m_oNumericalAnalysisCase.getPebiGridControl(); } void nmDataAnalyzeManager::setIncludedCalculationWells( const QVector& vecWells) { const QVector vecOldWells = m_oNumericalAnalysisCase.getIncludedWells(); QVector 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 vecNewWells = m_oNumericalAnalysisCase.getIncludedWells(); bool bChanged = vecOldWells.size() != vecNewWells.size(); for(int nIndex = 0; !bChanged && nIndex < vecNewWells.size(); ++nIndex) { bChanged = vecOldWells[nIndex].m_sWellCode != vecNewWells[nIndex].m_sWellCode || vecOldWells[nIndex].m_eMode != vecNewWells[nIndex].m_eMode; } if(bChanged) { // 第三步:这里只通知当前输入变化,不修改独立快照中的结果井选择。 emit dataChanged(); } } QVector nmDataAnalyzeManager::getIncludedCalculationWells() const { return m_oNumericalAnalysisCase.getIncludedWells(); } QVector nmDataAnalyzeManager::getEffectiveCalculationWells() const { QVector vecEffectiveWells; QSet 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 setIncludedRateWellCodes; if(getIncludeOtherWells()) { const QVector 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 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 vecEffectiveWells = getEffectiveCalculationWells(); for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) { if(vecEffectiveWells[nIndex].m_sWellCode == sWellCode) { return true; } } return false; } void nmDataAnalyzeManager::clearSolverWellOrder() { m_oNumericalAnalysisCase.clearSolverWellOrder(); } void nmDataAnalyzeManager::appendSolverWell(const nmSolverWellRef& oWellRef) { m_oNumericalAnalysisCase.appendSolverWell(oWellRef); } void nmDataAnalyzeManager::insertSolverWell( int nIndex, const nmSolverWellRef& oWellRef) { m_oNumericalAnalysisCase.insertSolverWell(nIndex, oWellRef); } QVector nmDataAnalyzeManager::getSolverWellOrder() const { return m_oNumericalAnalysisCase.getSolverWellOrder(); } void nmDataAnalyzeManager::setSolverWellOrder( const QVector& vecSolverWellOrder) { m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder); } bool nmDataAnalyzeManager::commitPebiGridResult( quint64 nGridInputRevision, const QVector& vecSolverWellOrder, vtkSmartPointer pGrid) { // 第一步:空井顺序或空网格都不是可求解成果,不能破坏当前有效网格。 if(vecSolverWellOrder.isEmpty() || pGrid == nullptr || pGrid->GetNumberOfCells() <= 0) { return false; } // 第二步:先检查后台任务捕获的输入版本。该函数只在主线程调用,检查和 // 后续写入之间不会处理界面事件,因此可以作为一次不可分割的成果提交。 if(m_oNumericalAnalysisCase.getGridInputRevision() != nGridInputRevision) { return false; } // 第三步:整体替换 DLL 槽位顺序,再登记同一输入版本并替换 VTK 网格。 // setSolverWellOrder() 会统一重编号,避免逐井追加期间暴露半成品顺序。 m_oNumericalAnalysisCase.setSolverWellOrder(vecSolverWellOrder); if(!m_oNumericalAnalysisCase.markGridBuiltIfCurrent( nGridInputRevision)) { return false; } m_pVtkUnstructuredGrid = pGrid; return true; } void nmDataAnalyzeManager::beginPebiGridGeneration( quint64 nGridInputRevision) { if(nGridInputRevision == 0) { return; } // 该状态只协调同一主线程上的窗口流程;后台任务不得直接读写 DataManager。 m_bPebiGridGenerationActive = true; m_nPebiGridGenerationRevision = nGridInputRevision; } void nmDataAnalyzeManager::endPebiGridGeneration(bool bSucceeded) { if(!m_bPebiGridGenerationActive) { return; } const quint64 nGridInputRevision = m_nPebiGridGenerationRevision; m_bPebiGridGenerationActive = false; m_nPebiGridGenerationRevision = 0; emit sigPebiGridGenerationFinished(nGridInputRevision, bSucceeded); } bool nmDataAnalyzeManager::isPebiGridGenerationActive() const { return m_bPebiGridGenerationActive; } void nmDataAnalyzeManager::markPebiGridBuilt() { m_oNumericalAnalysisCase.markGridBuilt(); } bool nmDataAnalyzeManager::markPebiGridBuiltIfCurrent( quint64 nGridInputRevision) { return m_oNumericalAnalysisCase.markGridBuiltIfCurrent( nGridInputRevision); } void nmDataAnalyzeManager::invalidatePebiGrid() { m_oNumericalAnalysisCase.invalidateGrid(); } void nmDataAnalyzeManager::invalidatePebiResults() { m_oNumericalAnalysisCase.invalidateResults(); } bool nmDataAnalyzeManager::isPebiGridValid() const { return m_oNumericalAnalysisCase.isGridValid(); } bool nmDataAnalyzeManager::hasPebiResults() const { // 结果存在性只有一个事实来源:Manager 是否持有完整只读快照。 return !m_pPebiResultSnapshot.isNull(); } QSharedPointer nmDataAnalyzeManager::getPebiResultSnapshot() const { return m_pPebiResultSnapshot; } bool nmDataAnalyzeManager::hasPebiResultSnapshot() const { return !m_pPebiResultSnapshot.isNull(); } bool nmDataAnalyzeManager::isPebiResultSnapshotCurrent() const { return !m_pPebiResultSnapshot.isNull() && m_pPebiResultSnapshot->getGridInputRevision() == m_oNumericalAnalysisCase.getGridInputRevision() && m_pPebiResultSnapshot->getResultInputRevision() == m_oNumericalAnalysisCase.getResultInputRevision(); } QString nmDataAnalyzeManager::getCurrentResultWellInstanceId() const { return m_sCurrentResultWellInstanceId; } bool nmDataAnalyzeManager::setCurrentResultWellInstanceId( const QString& sWellInstanceId) { // 结果井选择属于快照查看状态,不能反向修改当前分析主井。 if(m_pPebiResultSnapshot.isNull()) { if(!sWellInstanceId.isEmpty()) { return false; } m_sCurrentResultWellInstanceId.clear(); return true; } QString sValidatedWellInstanceId = sWellInstanceId; if(sValidatedWellInstanceId.isEmpty()) { const QStringList& listDisplayWellIds = m_pPebiResultSnapshot->getDisplayWellInstanceIds(); sValidatedWellInstanceId = listDisplayWellIds.isEmpty() ? QString() : listDisplayWellIds.first(); } if(!sValidatedWellInstanceId.isEmpty() && !m_pPebiResultSnapshot->isDisplayWell(sValidatedWellInstanceId)) { return false; } if(m_sCurrentResultWellInstanceId == sValidatedWellInstanceId) { return true; } m_sCurrentResultWellInstanceId = sValidatedWellInstanceId; emit sigPebiResultSnapshotChanged(); return true; } bool nmDataAnalyzeManager::commitPebiResultSnapshot( QSharedPointer& pCandidate, QString* pError) { if(pError != NULL) { pError->clear(); } QString sError; QString sNextWellInstanceId; QSharedPointer pPublishedCandidate; try { // 发布只能发生在 Manager 所属线程,避免工作线程直接替换 UI 正在读取的引用。 if(QThread::currentThread() != thread()) { sError = "PEBI result snapshot must be committed on its owner thread."; } else if(pCandidate.isNull() || !pCandidate->isComplete()) { sError = "PEBI result snapshot candidate is incomplete."; } else if(pCandidate->getGridInputRevision() != m_oNumericalAnalysisCase.getGridInputRevision() || pCandidate->getResultInputRevision() != m_oNumericalAnalysisCase.getResultInputRevision() || !m_oNumericalAnalysisCase.isGridValid()) { sError = "PEBI result snapshot input revisions are stale."; } // 即使外部遗漏输入版本递增,也不允许已删除井或同编码新井接收旧任务结果。 QSet setWellInstanceIds; for(int nIndex = 0; sError.isEmpty() && nIndex < pCandidate->getWellCount(); ++nIndex) { const nmPebiResultWellSnapshot* pWell = pCandidate->getWellAt(nIndex); nmDataWellBase* pLiveWell = pWell == NULL ? NULL : findWellByCode(pWell->m_sWellCode); if(pWell == NULL || pLiveWell == NULL || pLiveWell->getWellInstanceId() != pWell->m_sWellInstanceId || setWellInstanceIds.contains(pWell->m_sWellInstanceId)) { sError = "PEBI result well identity is stale."; break; } setWellInstanceIds.insert(pWell->m_sWellInstanceId); } // 当前网格登记的槽位必须与候选完全相同,防止相同版本下的非法外部改写。 const QVector vecCurrentOrder = m_oNumericalAnalysisCase.getSolverWellOrder(); if(sError.isEmpty() && vecCurrentOrder.size() != pCandidate->getSolverSlotCount()) { sError = "PEBI solver slot order has changed."; } for(int nIndex = 0; sError.isEmpty() && nIndex < vecCurrentOrder.size(); ++nIndex) { const nmSolverWellRef& oCurrent = vecCurrentOrder[nIndex]; const nmPebiResultSolverSlot* pSaved = pCandidate->getSolverSlotAt(nIndex); if(pSaved == NULL || oCurrent.m_nSolverIndex != pSaved->m_nSolverIndex || oCurrent.m_eEntryKind != pSaved->m_eEntryKind || oCurrent.m_sWellCode != pSaved->m_sWellCode || oCurrent.m_eWellType != pSaved->m_eWellType) { sError = "PEBI solver slot order has changed."; } } if(sError.isEmpty()) { pPublishedCandidate = QSharedPointer(pCandidate); // 新结果仍包含旧选择时保持用户上下文,否则退到第一口可显示井。 sNextWellInstanceId = m_sCurrentResultWellInstanceId; if(!pPublishedCandidate->isDisplayWell(sNextWellInstanceId)) { const QStringList& listDisplayWellIds = pPublishedCandidate->getDisplayWellInstanceIds(); sNextWellInstanceId = listDisplayWellIds.isEmpty() ? QString() : listDisplayWellIds.first(); } } } catch(const std::bad_alloc&) { // 校验期间内存不足时尚未触碰发布点,旧快照和旧结果井选择保持不变。 pCandidate.clear(); if(pError != NULL) { *pError = "Not enough memory to validate PEBI result snapshot."; } return false; } if(!sError.isEmpty()) { // 提交失败也要消除可写别名,但旧快照和旧结果井选择保持不变。 pCandidate.clear(); if(pError != NULL) { *pError = sError; } return false; } // 发布点只替换只读引用;先前快照由仍在查看它的窗口延迟释放。 m_pPebiResultSnapshot = pPublishedCandidate; m_sCurrentResultWellInstanceId = sNextWellInstanceId; pCandidate.clear(); rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); return true; } void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells() { QWeakPointer pWeakSnapshot; if(!m_pPebiResultSnapshot.isNull()) { pWeakSnapshot = QWeakPointer( m_pPebiResultSnapshot); } for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = m_vWellData[nIndex]; if(pWell == NULL) { continue; } const bool bBelongsToSnapshot = !m_pPebiResultSnapshot.isNull() && m_pPebiResultSnapshot->findWell( pWell->getWellInstanceId()) != NULL; pWell->bindPebiResultSnapshot(bBelongsToSnapshot ? pWeakSnapshot : QWeakPointer()); } } void nmDataAnalyzeManager::clearPebiResultSnapshot() { if(m_pPebiResultSnapshot.isNull() && m_sCurrentResultWellInstanceId.isEmpty()) { return; } m_pPebiResultSnapshot.clear(); m_sCurrentResultWellInstanceId.clear(); rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); } // 设置当前查看井 void nmDataAnalyzeManager::setCurWellData(nmDataWellBase * wellData) { m_pCurDataWell = wellData; } // 获取当前查看井 nmDataWellBase* nmDataAnalyzeManager::getCurWellData() { return m_pCurDataWell; } void nmDataAnalyzeManager::notifyDataChanged() { // 现有属性编辑链路尚未细分“几何变化”和“仅求解输入变化”, // 为保证正确性先采用保守策略:任一计算数据变化都使网格失效。 m_oNumericalAnalysisCase.invalidateGrid(); emit dataChanged(); } void nmDataAnalyzeManager::notifyGeometryListChanged() { m_oNumericalAnalysisCase.invalidateGrid(); emit sigGeometryListChanged(); emit dataChanged(); } void nmDataAnalyzeManager::notifyParameterObjectNameChanged() { emit sigGeometryListChanged(); } void nmDataAnalyzeManager::setDisplaySettings(const QVector& displaySettings) { m_vecDisplaySettings = displaySettings; } QVector 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 nmDataAnalyzeManager::getPropertyInterpolationDataSets() const { return m_vecPropertyInterpolationDataSets; } void nmDataAnalyzeManager::setPropertyInterpolationDataSets( const QVector& dataSets) { m_vecPropertyInterpolationDataSets = dataSets; // 属性插值只覆盖求解器单元属性,不改变二维 PEBI 拓扑。 m_oNumericalAnalysisCase.invalidateResults(); } // 从 JSON 文件读取数据到 C++ 对象 bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) { rapidjson::Document doc; // 调用 nmDataJsonTools 读取 JSON 文件到 Document if(!nmDataJsonTools::ReadDomFromFile(filePath, doc)) { qDebug() << "Error: Failed to read DOM from file:" << filePath; return false; } // 新数据模型不兼容旧项目。明确校验版本,避免按井名或对象顺序猜测关联关系。 const int nSupportedProjectVersion = 3; if(!doc.HasMember("NumericalProjectVersion") || !doc["NumericalProjectVersion"].IsInt() || doc["NumericalProjectVersion"].GetInt() != nSupportedProjectVersion) { qWarning() << "Unsupported numerical project version:" << filePath; return false; } quint64 nSavedGridInputRevision = 0; quint64 nSavedBuiltGridRevision = 0; quint64 nSavedResultInputRevision = 0; if(!doc.HasMember("InputRevisions") || !doc["InputRevisions"].IsObject()) { qWarning() << "Numerical v3 project has no input revisions:" << filePath; return false; } const rapidjson::Value& oRevisionJson = doc["InputRevisions"]; if(!oRevisionJson.HasMember("GridInputRevision") || !oRevisionJson["GridInputRevision"].IsUint64() || !oRevisionJson.HasMember("BuiltGridRevision") || !oRevisionJson["BuiltGridRevision"].IsUint64() || !oRevisionJson.HasMember("ResultInputRevision") || !oRevisionJson["ResultInputRevision"].IsUint64()) { qWarning() << "Numerical v3 project contains invalid input revisions:" << filePath; return false; } nSavedGridInputRevision = oRevisionJson["GridInputRevision"].GetUint64(); nSavedBuiltGridRevision = oRevisionJson["BuiltGridRevision"].GetUint64(); nSavedResultInputRevision = oRevisionJson["ResultInputRevision"].GetUint64(); if(nSavedGridInputRevision == 0 || nSavedResultInputRevision == 0 || (nSavedBuiltGridRevision != 0 && nSavedBuiltGridRevision != nSavedGridInputRevision)) { qWarning() << "Numerical v3 project input revision values are invalid:" << filePath; return false; } QString sSavedPrimaryWellCode; NM_CASE_WELL_MODE eSavedPrimaryWellMode = NM_CaseWell_Observation; bool bSavedIncludeOtherWells = false; double dSavedPebiGridControl = 150.0; QVector vecSavedIncludedWells; QVector vecSavedSolverOrder; // 第一步:v3 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。 if(!doc.HasMember("NumericalAnalysisCase") || !doc["NumericalAnalysisCase"].IsObject() || !doc.HasMember("Wells") || !doc["Wells"].IsArray()) { qWarning() << "Numerical project has no complete analysis case:" << filePath; return false; } const rapidjson::Value& oCaseJson = doc["NumericalAnalysisCase"]; if(!oCaseJson.HasMember("PrimaryWellCode") || !oCaseJson["PrimaryWellCode"].IsString() || !oCaseJson.HasMember("PrimaryWellMode") || !oCaseJson["PrimaryWellMode"].IsInt() || !oCaseJson.HasMember("IncludeOtherWells") || !oCaseJson["IncludeOtherWells"].IsBool() || !oCaseJson.HasMember("PebiGridControl") || !oCaseJson["PebiGridControl"].IsNumber() || !oCaseJson.HasMember("IncludedWells") || !oCaseJson["IncludedWells"].IsArray() || !oCaseJson.HasMember("SolverWellOrder") || !oCaseJson["SolverWellOrder"].IsArray()) { qWarning() << "Numerical analysis case contains invalid fields:" << filePath; return false; } sSavedPrimaryWellCode = QString::fromUtf8( oCaseJson["PrimaryWellCode"].GetString()); eSavedPrimaryWellMode = static_cast( oCaseJson["PrimaryWellMode"].GetInt()); bSavedIncludeOtherWells = oCaseJson["IncludeOtherWells"].GetBool(); dSavedPebiGridControl = oCaseJson["PebiGridControl"].GetDouble(); const bool bPrimaryModeValid = eSavedPrimaryWellMode == NM_CaseWell_RateControlled || eSavedPrimaryWellMode == NM_CaseWell_Observation; if(sSavedPrimaryWellCode.isEmpty() || !bPrimaryModeValid || !qIsFinite(dSavedPebiGridControl) || dSavedPebiGridControl <= 0.0) { qWarning() << "Numerical analysis case contains invalid base values:" << filePath; return false; } // 第二步:先从 JSON 井数组建立唯一 WellCode 到井型的索引,尚不修改当前内存数据。 QMap mapSavedWellTypes; QSet setSavedWellInstanceIds; const rapidjson::Value& vecWellsJson = doc["Wells"]; for(rapidjson::SizeType nIndex = 0; nIndex < vecWellsJson.Size(); ++nIndex) { const rapidjson::Value& oWellJson = vecWellsJson[nIndex]; if(!oWellJson.IsObject() || !oWellJson.HasMember("WellCode") || !oWellJson["WellCode"].IsString() || !oWellJson.HasMember("WellInstanceId") || !oWellJson["WellInstanceId"].IsString() || !oWellJson.HasMember("WellType") || !oWellJson["WellType"].IsInt()) { qWarning() << "Numerical project contains an invalid well entry:" << filePath; return false; } const QString sWellCode = QString::fromUtf8( oWellJson["WellCode"].GetString()); const QString sWellInstanceId = QString::fromUtf8( oWellJson["WellInstanceId"].GetString()); const QUuid oWellInstanceUuid(sWellInstanceId); const QString sNormalizedWellInstanceId = oWellInstanceUuid.toString().remove('{').remove('}'); const NM_WELL_MODEL eWellType = static_cast( oWellJson["WellType"].GetInt()); const bool bSupportedWellType = eWellType == NM_WELL_MODEL::Vertical_Well || eWellType == NM_WELL_MODEL::Vertical_Fractured_Well || eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well; if(sWellCode.isEmpty() || !bSupportedWellType || mapSavedWellTypes.contains(sWellCode) || oWellInstanceUuid.isNull() || sNormalizedWellInstanceId != sWellInstanceId || setSavedWellInstanceIds.contains(sWellInstanceId)) { qWarning() << "Numerical project contains an invalid well identity:" << sWellCode << sWellInstanceId; return false; } mapSavedWellTypes.insert(sWellCode, eWellType); setSavedWellInstanceIds.insert(sWellInstanceId); } if(!mapSavedWellTypes.contains(sSavedPrimaryWellCode)) { qWarning() << "Primary WellCode is absent from Wells:" << sSavedPrimaryWellCode; return false; } // 第三步:校验包含井,禁止空编码、重复编码、主井重复和非法角色。 QSet setEffectiveWellCodes; setEffectiveWellCodes.insert(sSavedPrimaryWellCode); QSet 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( oWellJson["Mode"].GetInt()); // Include Other Wells 只保存主动生产/注入井;无产量观察井由 Map // 自动推导,不允许以观察角色混入这个持久化列表。 const bool bModeValid = eMode == NM_CaseWell_RateControlled; if(sWellCode == sSavedPrimaryWellCode || !mapSavedWellTypes.contains(sWellCode) || setIncludedWellCodes.contains(sWellCode) || !bModeValid) { qWarning() << "IncludedWells contains an invalid WellCode or role:" << sWellCode; return false; } setIncludedWellCodes.insert(sWellCode); if(bSavedIncludeOtherWells) { setEffectiveWellCodes.insert(sWellCode); } vecSavedIncludedWells.append(nmCalculationWellRef(sWellCode, eMode)); } // 第四步:此时井的历史流量尚未恢复,无法判断一口 Map 井最终属于 // 主动井还是自动观察井。因此这里只校验真实井存在、井型一致且不重复; // 历史数据加载完成后,再与最终有效计算井集合做严格一致性校验。 QSet 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( oOrderJson["WellType"].GetInt()); const NM_SOLVER_ENTRY_KIND eEntryKind = static_cast( oOrderJson["EntryKind"].GetInt()); if(eEntryKind == NM_SolverEntry_Well) { if(!mapSavedWellTypes.contains(sWellCode) || setOrderedWellCodes.contains(sWellCode) || mapSavedWellTypes.value(sWellCode, NM_WELL_MODEL::Unknow_Well) != eWellType) { qWarning() << "SolverWellOrder contains an invalid real well:" << sWellCode; return false; } setOrderedWellCodes.insert(sWellCode); } else if(eEntryKind == NM_SolverEntry_ManualFracture) { if(!sWellCode.isEmpty() || eWellType != NM_WELL_MODEL::Unknow_Well) { qWarning() << "Manual fracture solver entry is malformed:" << filePath; return false; } } else { qWarning() << "SolverWellOrder contains an unknown entry kind:" << filePath; return false; } vecSavedSolverOrder.append(nmSolverWellRef( static_cast(nIndex), eWellType, sWellCode, eEntryKind)); } if(!vecSavedSolverOrder.isEmpty()) { // 主井和已启用的显式包含井无需依赖历史数据即可确定,必须已经在顺序中。 // 顺序中的其他 Map 井可能是稍后才能识别的无产量观察井,当前不能拒绝。 QSet::const_iterator oEffectiveIt = setEffectiveWellCodes.constBegin(); for(; oEffectiveIt != setEffectiveWellCodes.constEnd(); ++oEffectiveIt) { if(!setOrderedWellCodes.contains(*oEffectiveIt)) { qWarning() << "SolverWellOrder does not contain a configured well:" << *oEffectiveIt; return false; } } } // 前置校验已完成,从这里开始替换当前输入。旧快照要到全部 v3 载荷 // 成功后才替换,加载失败或内存不足时仍保留上一套已发布快照。 // 清空现有数据,确保从头加载新数据 foreach(nmDataFracture* pFractureData, m_vFractureData) { delete pFractureData; // 释放堆上分配的 nmDataFracture 对象 } m_vFractureData.clear(); foreach(nmDataFault* pFaultData, m_vFaultData) { delete pFaultData; // 释放堆上分配的 nmDataFault 对象 } m_vFaultData.clear(); foreach(nmDataRegion* pRegionData, m_vRegionData) { delete pRegionData; // 释放堆上分配的 nmDataRegion 对象 } m_vRegionData.clear(); foreach(nmDataRegionMark* pRegionMarkData, m_vRegionMarkData) { delete pRegionMarkData; // 释放堆上分配的 nmDataRegionMark 对象 } m_vRegionMarkData.clear(); foreach(nmDataLayer* pLayerData, m_vecLayers) { delete pLayerData; // 释放堆上分配的 nmDataLayer 对象 } m_vecLayers.clear(); m_vecPropertyInterpolationDataSets.clear(); if(m_reservoirData) { delete m_reservoirData; m_reservoirData = nullptr; } if(m_axisData) { delete m_axisData; m_axisData = nullptr; } if(m_outlineData) { delete m_outlineData; m_outlineData = nullptr; } if(m_pMixedResults) { delete m_pMixedResults; m_pMixedResults = nullptr; } if(m_pGeoRefData) { delete m_pGeoRefData; m_pGeoRefData = nullptr; } if(m_pTimeStep) { delete m_pTimeStep; m_pTimeStep = nullptr; } if(m_pSensitiveData) { delete m_pSensitiveData; m_pSensitiveData = nullptr; } //if(m_pPerCloData) { // delete m_pPerCloData; // m_pPerCloData = nullptr; //} if(m_pAutomaticFittingData) { delete m_pAutomaticFittingData; m_pAutomaticFittingData = nullptr; } //if(m_pebiPvtPara) { // delete m_pebiPvtPara; // m_pebiPvtPara = nullptr; //} foreach(nmDataWellBase* pWellData, m_vWellData) { delete pWellData; // 释放堆上分配的 nmDataWellBase 对象 } m_vWellData.clear(); m_oNumericalAnalysisCase.clear(); // 解析 "Fractures" 数组 if(doc.HasMember("Fractures") && doc["Fractures"].IsArray()) { const rapidjson::Value& fracturesJson = doc["Fractures"]; for(rapidjson::SizeType i = 0; i < fracturesJson.Size(); ++i) { // 动态创建 nmDataFracture 对象,并获取其指针 nmDataFracture* fracture = new nmDataFracture(); fracture->FromJsonValue(fracturesJson[i]); // 调用 nmDataFracture 自身的反序列化方法 m_vFractureData.append(fracture); // 将指针添加到 QVector } } // 解析 "Faults" 数组 if(doc.HasMember("Faults") && doc["Faults"].IsArray()) { const rapidjson::Value& faultsJson = doc["Faults"]; for(rapidjson::SizeType i = 0; i < faultsJson.Size(); ++i) { // 动态创建 nmDataFault 对象,并获取其指针 nmDataFault* fault = new nmDataFault(); fault->FromJsonValue(faultsJson[i]); // 调用 nmDataFault 自身的反序列化方法 m_vFaultData.append(fault); // 将指针添加到 QVector } } // 解析 "Regions" 数组 if(doc.HasMember("Regions") && doc["Regions"].IsArray()) { const rapidjson::Value& regionsJson = doc["Regions"]; for(rapidjson::SizeType i = 0; i < regionsJson.Size(); ++i) { // 动态创建 nmDataRegion 对象,并获取其指针 nmDataRegion* region = new nmDataRegion(); // 传入this作为父对象 region->FromJsonValue(regionsJson[i]); // 调用 nmDataRegion 自身的反序列化方法 m_vRegionData.append(region); // 将指针添加到 QVector } } // 解析 "RegionMarks" 数组 if(doc.HasMember("RegionMarks") && doc["RegionMarks"].IsArray()) { const rapidjson::Value& regionMarksJson = doc["RegionMarks"]; for(rapidjson::SizeType i = 0; i < regionMarksJson.Size(); ++i) { // 动态创建 nmDataRegionMark 对象,并获取其指针 nmDataRegionMark* regionMark = new nmDataRegionMark(); // 传入this作为父对象 regionMark->FromJsonValue(regionMarksJson[i]); // 调用 nmDataRegion 自身的反序列化方法 m_vRegionMarkData.append(regionMark); // 将指针添加到 QVector } } // 解析 "Layers" 数组 if(doc.HasMember("Layers") && doc["Layers"].IsArray()) { const rapidjson::Value& layersJson = doc["Layers"]; for(rapidjson::SizeType i = 0; i < layersJson.Size(); ++i) { // 动态创建 nmDataRegion 对象,并获取其指针 nmDataLayer* layer = new nmDataLayer(); // 传入this作为父对象 layer->FromJsonValue(layersJson[i]); // 调用 nmDataLayer 自身的反序列化方法 m_vecLayers.append(layer); // 将指针添加到 QVector } } /* 读取属性插值数据组 */ if(doc.HasMember("PropertyInterpolationDataSets") && doc["PropertyInterpolationDataSets"].IsArray()) { const rapidjson::Value& dataSetsJson = doc["PropertyInterpolationDataSets"]; for(rapidjson::SizeType i = 0; i < dataSetsJson.Size(); ++i) { const rapidjson::Value& dataSetJson = dataSetsJson[i]; if(!dataSetJson.IsObject()) { continue; } nmPropertyInterpolationDataSet dataSet; if(dataSetJson.HasMember("Name") && dataSetJson["Name"].IsString()) { dataSet.name = QString::fromUtf8(dataSetJson["Name"].GetString()); } if(dataSetJson.HasMember("Property") && dataSetJson["Property"].IsString()) { dataSet.property = QString::fromUtf8(dataSetJson["Property"].GetString()); } if(dataSet.property == "h") { dataSet.valueDisplayUnit = "m"; } else if(dataSet.property == "phi") { dataSet.valueDisplayUnit.clear(); } if(dataSetJson.HasMember("XDisplayUnit") && dataSetJson["XDisplayUnit"].IsString()) { dataSet.xDisplayUnit = QString::fromUtf8( dataSetJson["XDisplayUnit"].GetString()); } if(dataSetJson.HasMember("YDisplayUnit") && dataSetJson["YDisplayUnit"].IsString()) { dataSet.yDisplayUnit = QString::fromUtf8( dataSetJson["YDisplayUnit"].GetString()); } if(dataSetJson.HasMember("ValueDisplayUnit") && dataSetJson["ValueDisplayUnit"].IsString()) { dataSet.valueDisplayUnit = QString::fromUtf8( dataSetJson["ValueDisplayUnit"].GetString()); } if(dataSetJson.HasMember("RangeDisplayUnit") && dataSetJson["RangeDisplayUnit"].IsString()) { dataSet.rangeDisplayUnit = QString::fromUtf8( dataSetJson["RangeDisplayUnit"].GetString()); } if(dataSetJson.HasMember("UseForCalculation") && dataSetJson["UseForCalculation"].IsBool()) { dataSet.useForCalculation = dataSetJson["UseForCalculation"].GetBool(); } if(dataSetJson.HasMember("ShowPoints") && dataSetJson["ShowPoints"].IsBool()) { dataSet.showPoints = dataSetJson["ShowPoints"].GetBool(); } if(dataSetJson.HasMember("ShowLabels") && dataSetJson["ShowLabels"].IsBool()) { dataSet.showLabels = dataSetJson["ShowLabels"].GetBool(); } if(dataSetJson.HasMember("Nugget") && dataSetJson["Nugget"].IsNumber()) { dataSet.nugget = dataSetJson["Nugget"].GetDouble(); } if(dataSetJson.HasMember("Sill") && dataSetJson["Sill"].IsNumber()) { dataSet.sill = dataSetJson["Sill"].GetDouble(); } if(dataSetJson.HasMember("Range") && dataSetJson["Range"].IsNumber()) { dataSet.range = dataSetJson["Range"].GetDouble(); } if(dataSetJson.HasMember("Model") && dataSetJson["Model"].IsInt()) { dataSet.model = dataSetJson["Model"].GetInt(); } if(dataSetJson.HasMember("Points") && dataSetJson["Points"].IsArray()) { const rapidjson::Value& pointsJson = dataSetJson["Points"]; for(rapidjson::SizeType pointIndex = 0; pointIndex < pointsJson.Size(); ++pointIndex) { const rapidjson::Value& pointJson = pointsJson[pointIndex]; if(!pointJson.IsObject() || !pointJson.HasMember("X") || !pointJson["X"].IsNumber() || !pointJson.HasMember("Y") || !pointJson["Y"].IsNumber() || !pointJson.HasMember("Value") || !pointJson["Value"].IsNumber()) { continue; } dataSet.points.append(nmPropertyInterpolationPointData( pointJson["X"].GetDouble(), pointJson["Y"].GetDouble(), pointJson["Value"].GetDouble())); } } m_vecPropertyInterpolationDataSets.append(dataSet); } } /* 解析 "Reservoir" 对象 */ if(doc.HasMember("Reservoir") && doc["Reservoir"].IsObject()) { m_reservoirData = new nmDataReservoir; m_reservoirData->FromJsonValue(doc["Reservoir"]); // 注册储层属性到 nmAttrRegistry(与 createReservoir 保持一致) if (m_pAttrRegistry) { m_pAttrRegistry->clear(); m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness()); m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure()); m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability()); m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity()); m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt()); m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf()); m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi()); m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi()); } } // 解析 "Axis" 对象 if(doc.HasMember("Axis") && doc["Axis"].IsObject()) { m_axisData = new nmDataAxis; m_axisData->FromJsonValue(doc["Axis"]); } // 解析 "Outline" 对象 if(doc.HasMember("Outline") && doc["Outline"].IsObject()) { m_outlineData = new nmDataOutline; m_outlineData->FromJsonValue(doc["Outline"]); } // 解析 "GeoRef" 对象 if(doc.HasMember("GeoReference") && doc["GeoReference"].IsObject()) { m_pGeoRefData = new nmDataGeoRef; m_pGeoRefData->FromJsonValue(doc["GeoReference"]); } // 解析 "AutomaticFitting" 对象 if(doc.HasMember("AutomaticFitting") && doc["AutomaticFitting"].IsObject()) { m_pAutomaticFittingData = new nmDataAutomaticFitting; m_pAutomaticFittingData->FromJsonValue(doc["AutomaticFitting"]); } // 解析 "Sensitive" 对象 if(doc.HasMember("Sensitive") && doc["Sensitive"].IsObject()) { m_pSensitiveData = new nmDataSensitive; m_pSensitiveData->FromJsonValue(doc["Sensitive"]); } // 解析 "PVT" 对象 //if(doc.HasMember("PVT") && doc["PVT"].IsObject()) { // m_pebiPvtPara = new nmDataPvtParaForPebi; // m_pebiPvtPara->FromJsonValue(doc["PVT"]); //} // 解析 "MixResult" 对象 if(doc.HasMember("MixResult") && doc["MixResult"].IsObject()) { m_pMixedResults = new nmDataMixedResults; m_pMixedResults->FromJsonValue(doc["MixResult"]); } // 解析 "Wells" 数组 if(doc.HasMember("Wells") && doc["Wells"].IsArray()) { const rapidjson::Value& wellsJson = doc["Wells"]; for(rapidjson::SizeType i = 0; i < wellsJson.Size(); ++i) { //// 动态创建 nmDataFault 对象,并获取其指针 //nmDataWellBase* well = new nmDataWellBase(); // 传入this作为父对象 //well->FromJsonValue(wellsJson[i]); // 调用 nmDataFault 自身的反序列化方法 //m_vWellData.append(well); // 将指针添加到 QVector // 获取当前井的JSON对象 const rapidjson::Value& wellItemJson = wellsJson[i]; // 临时指针,用于指向新创建的井对象 nmDataWellBase* pWell = nullptr; // 井类型 NM_WELL_MODEL eWellType = NM_WELL_MODEL::Unknow_Well; // 1. 读取 wellType if(wellItemJson.HasMember("WellType") && wellItemJson["WellType"].IsInt()) { eWellType = static_cast(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 mapLoadedWellTypes; QSet setLoadedWellInstanceIds; for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWellData = m_vWellData[nIndex]; if(pWellData == nullptr || pWellData->getWellCode().isEmpty() || mapLoadedWellTypes.contains(pWellData->getWellCode()) || !setSavedWellInstanceIds.contains( pWellData->getWellInstanceId()) || setLoadedWellInstanceIds.contains( pWellData->getWellInstanceId())) { qWarning() << "Loaded numerical project contains an invalid WellCode:"; return false; } mapLoadedWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType()); setLoadedWellInstanceIds.insert(pWellData->getWellInstanceId()); } if(mapLoadedWellTypes != mapSavedWellTypes || setLoadedWellInstanceIds != setSavedWellInstanceIds) { qWarning() << "Loaded well directory differs from validated JSON:" << filePath; return false; } // 井对象全部加载完成后,先按 WellCode 原样恢复已经通过结构校验的分析方案。 // 这里不能调用会检查流量点的公开 setter,因为历史流量要到 loadNmResult() // 的二进制恢复阶段才可用;业务有效性在历史数据恢复后统一确认。 nmDataWellBase* pPrimaryWell = findWellByCode(sSavedPrimaryWellCode); if(pPrimaryWell == nullptr) { qWarning() << "PrimaryWellCode is missing from numerical project:" << sSavedPrimaryWellCode; return false; } m_pCurDataWell = pPrimaryWell; m_oNumericalAnalysisCase.setPrimaryWellCode(sSavedPrimaryWellCode); m_oNumericalAnalysisCase.setPrimaryWellMode(eSavedPrimaryWellMode); m_oNumericalAnalysisCase.setPebiGridControl(dSavedPebiGridControl); m_oNumericalAnalysisCase.setIncludedWells(vecSavedIncludedWells); m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells); m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder); // 解析 时间步 对象 if(doc.HasMember("TimeStep") && doc["TimeStep"].IsObject()) { m_pTimeStep = new nmDataTimeStepSetting; m_pTimeStep->FromJsonValue(doc["TimeStep"]); } // 恢复当前求解器模型类型 if(doc.HasMember("SolverModelType") && doc["SolverModelType"].IsInt()) { m_eSolverModelType = static_cast(doc["SolverModelType"].GetInt()); } if(doc.HasMember("PebiSolverType") && doc["PebiSolverType"].IsInt()) { setPebiSolverType(doc["PebiSolverType"].GetInt()); } if(doc.HasMember("PebiOmpThreads") && doc["PebiOmpThreads"].IsInt()) { setPebiOmpThreads(doc["PebiOmpThreads"].GetInt()); } if(doc.HasMember("PebiIluReuseSteps") && doc["PebiIluReuseSteps"].IsInt()) { setPebiIluReuseSteps(doc["PebiIluReuseSteps"].GetInt()); } if(!m_oNumericalAnalysisCase.restoreInputRevisions( nSavedGridInputRevision, nSavedBuiltGridRevision, nSavedResultInputRevision)) { qWarning() << "Failed to restore numerical v3 input revisions:" << filePath; return false; } return true; } // 将 C++ 对象数据写入 JSON 文件 bool nmDataAnalyzeManager::WriteProjectData( const QString & filePath, const nmNumericalWindowPayloadReferences* pPayloadReferences) { if(pPayloadReferences == NULL) { qWarning() << "Numerical v3 project requires explicit payload references."; return false; } // 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。 QSet setWellCodes; QSet setWellInstanceIds; QMap mapWellTypes; for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWellData = m_vWellData[nIndex]; const QString sWellInstanceId = pWellData == nullptr ? QString() : pWellData->getWellInstanceId(); const QUuid oWellInstanceUuid(sWellInstanceId); const QString sNormalizedWellInstanceId = oWellInstanceUuid.toString().remove('{').remove('}'); if(pWellData == nullptr || pWellData->getWellCode().isEmpty() || setWellCodes.contains(pWellData->getWellCode()) || oWellInstanceUuid.isNull() || sNormalizedWellInstanceId != sWellInstanceId || setWellInstanceIds.contains(sWellInstanceId)) { qWarning() << "Cannot save numerical project: invalid well identity."; return false; } setWellCodes.insert(pWellData->getWellCode()); setWellInstanceIds.insert(sWellInstanceId); mapWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType()); } const QString sPrimaryWellCode = getPrimaryWellCode(); if(!setWellCodes.contains(sPrimaryWellCode) || !qIsFinite(getPebiGridControl()) || getPebiGridControl() <= 0.0) { qWarning() << "Cannot save numerical project: invalid primary well or GridControl."; return false; } // 第二步:校验有效计算井和当前网格的求解器顺序使用同一组 WellCode。 QSet setEffectiveWellCodes; QVector vecEffectiveWells = getEffectiveCalculationWells(); for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) { const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex]; if(!setWellCodes.contains(oWellRef.m_sWellCode) || setEffectiveWellCodes.contains(oWellRef.m_sWellCode)) { qWarning() << "Cannot save numerical project: invalid effective WellCode:" << oWellRef.m_sWellCode; return false; } setEffectiveWellCodes.insert(oWellRef.m_sWellCode); } QSet setOrderedWellCodes; QVector vecValidatedSolverOrder = getSolverWellOrder(); // 求解器顺序只属于有效网格。网格已经失效时保存空顺序, // 防止旧顺序与未保存的旧网格组成一个自相矛盾的项目。 if(!isPebiGridValid()) { vecValidatedSolverOrder.clear(); } for(int nIndex = 0; nIndex < vecValidatedSolverOrder.size(); ++nIndex) { const nmSolverWellRef& oWellRef = vecValidatedSolverOrder[nIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { if(!setEffectiveWellCodes.contains(oWellRef.m_sWellCode) || setOrderedWellCodes.contains(oWellRef.m_sWellCode) || mapWellTypes.value(oWellRef.m_sWellCode, NM_WELL_MODEL::Unknow_Well) != oWellRef.m_eWellType) { qWarning() << "Cannot save numerical project: invalid solver WellCode:" << oWellRef.m_sWellCode; return false; } setOrderedWellCodes.insert(oWellRef.m_sWellCode); } else if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) { if(!oWellRef.m_sWellCode.isEmpty() || oWellRef.m_eWellType != NM_WELL_MODEL::Unknow_Well) { qWarning() << "Cannot save numerical project: malformed manual fracture entry."; return false; } } else { qWarning() << "Cannot save numerical project: unknown solver entry kind."; return false; } } if(!vecValidatedSolverOrder.isEmpty() && setOrderedWellCodes != setEffectiveWellCodes) { qWarning() << "Cannot save numerical project: solver order is incomplete."; return false; } // 第三步:基础身份校验通过后再构造 JSON,避免生成无法重新加载的半成品文件。 rapidjson::Document doc; doc.SetObject(); // 根节点是对象 rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); // 获取内存分配器 doc.AddMember("NumericalProjectVersion", 3, allocator); rapidjson::Value oRevisionJson(rapidjson::kObjectType); oRevisionJson.AddMember("GridInputRevision", m_oNumericalAnalysisCase.getGridInputRevision(), allocator); oRevisionJson.AddMember("BuiltGridRevision", m_oNumericalAnalysisCase.getBuiltGridRevision(), allocator); oRevisionJson.AddMember("ResultInputRevision", m_oNumericalAnalysisCase.getResultInputRevision(), allocator); doc.AddMember("InputRevisions", oRevisionJson, allocator); doc.AddMember("CurrentResultWellInstanceId", numericalJsonString( m_sCurrentResultWellInstanceId, allocator), allocator); // 构建 "Fractures" 数组的 JSON Value rapidjson::Value fracturesJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataFracture 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataFracture* pFractureData, m_vFractureData) { if(pFractureData) { // 检查指针是否有效 fracturesJsonArray.PushBack(pFractureData->ToJsonValue(allocator), allocator); } } doc.AddMember("Fractures", fracturesJsonArray, allocator); // 构建 "Faults" 数组的 JSON Value rapidjson::Value faultsJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataFault 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataFault* pFaultData, m_vFaultData) { if(pFaultData) { // 检查指针是否有效 faultsJsonArray.PushBack(pFaultData->ToJsonValue(allocator), allocator); } } doc.AddMember("Faults", faultsJsonArray, allocator); // 构建 "Regions" 数组的 JSON Value rapidjson::Value regionsJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataRegion 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataRegion* pRegionData, m_vRegionData) { if(pRegionData) { // 检查指针是否有效 regionsJsonArray.PushBack(pRegionData->ToJsonValue(allocator), allocator); } } doc.AddMember("Regions", regionsJsonArray, allocator); // 构建 "RegionMarks" 数组的 JSON Value rapidjson::Value regionMarksJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataRegionMark 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataRegionMark* pRegionMarkData, m_vRegionMarkData) { if(pRegionMarkData) { // 检查指针是否有效 regionMarksJsonArray.PushBack(pRegionMarkData->ToJsonValue(allocator), allocator); } } doc.AddMember("RegionMarks", regionMarksJsonArray, allocator); // 构建 "Layers" 数组的 JSON Value rapidjson::Value layersJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataLayer 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataLayer* pLayerData, m_vecLayers) { if(pLayerData) { // 检查指针是否有效 layersJsonArray.PushBack(pLayerData->ToJsonValue(allocator), allocator); } } doc.AddMember("Layers", layersJsonArray, allocator); /* 保存属性插值数据组;数值统一使用m、D等求解器基准单位 */ rapidjson::Value interpolationDataSetsJson(rapidjson::kArrayType); for(int dataSetIndex = 0; dataSetIndex < m_vecPropertyInterpolationDataSets.size(); ++dataSetIndex) { const nmPropertyInterpolationDataSet& dataSet = m_vecPropertyInterpolationDataSets[dataSetIndex]; rapidjson::Value dataSetJson(rapidjson::kObjectType); QByteArray nameUtf8 = dataSet.name.toUtf8(); QByteArray propertyUtf8 = dataSet.property.toUtf8(); dataSetJson.AddMember("Name", rapidjson::Value(nameUtf8.constData(), allocator).Move(), allocator); dataSetJson.AddMember("Property", rapidjson::Value(propertyUtf8.constData(), allocator).Move(), allocator); QByteArray xDisplayUnitUtf8 = dataSet.xDisplayUnit.toUtf8(); QByteArray yDisplayUnitUtf8 = dataSet.yDisplayUnit.toUtf8(); QByteArray valueDisplayUnitUtf8 = dataSet.valueDisplayUnit.toUtf8(); QByteArray rangeDisplayUnitUtf8 = dataSet.rangeDisplayUnit.toUtf8(); dataSetJson.AddMember("XDisplayUnit", rapidjson::Value(xDisplayUnitUtf8.constData(), allocator).Move(), allocator); dataSetJson.AddMember("YDisplayUnit", rapidjson::Value(yDisplayUnitUtf8.constData(), allocator).Move(), allocator); dataSetJson.AddMember("ValueDisplayUnit", rapidjson::Value(valueDisplayUnitUtf8.constData(), allocator).Move(), allocator); dataSetJson.AddMember("RangeDisplayUnit", rapidjson::Value(rangeDisplayUnitUtf8.constData(), allocator).Move(), allocator); dataSetJson.AddMember("UseForCalculation", dataSet.useForCalculation, allocator); dataSetJson.AddMember("ShowPoints", dataSet.showPoints, allocator); dataSetJson.AddMember("ShowLabels", dataSet.showLabels, allocator); dataSetJson.AddMember("Nugget", dataSet.nugget, allocator); dataSetJson.AddMember("Sill", dataSet.sill, allocator); dataSetJson.AddMember("Range", dataSet.range, allocator); dataSetJson.AddMember("Model", dataSet.model, allocator); rapidjson::Value pointsJson(rapidjson::kArrayType); for(int pointIndex = 0; pointIndex < dataSet.points.size(); ++pointIndex) { const nmPropertyInterpolationPointData& point = dataSet.points[pointIndex]; rapidjson::Value pointJson(rapidjson::kObjectType); pointJson.AddMember("X", point.x, allocator); pointJson.AddMember("Y", point.y, allocator); pointJson.AddMember("Value", point.value, allocator); pointsJson.PushBack(pointJson, allocator); } dataSetJson.AddMember("Points", pointsJson, allocator); interpolationDataSetsJson.PushBack(dataSetJson, allocator); } doc.AddMember("PropertyInterpolationDataSets", interpolationDataSetsJson, allocator); // 序列化 "Reservoir" if(m_reservoirData) { rapidjson::Value reservoirJson(rapidjson::kObjectType); reservoirJson = m_reservoirData->ToJsonValue(allocator); doc.AddMember("Reservoir", reservoirJson, allocator); } // 序列化 "Axis" if(m_axisData) { rapidjson::Value axisJson(rapidjson::kObjectType); axisJson = m_axisData->ToJsonValue(allocator); doc.AddMember("Axis", axisJson, allocator); } // 序列化 "Outline" if(m_outlineData) { rapidjson::Value outlineJson(rapidjson::kObjectType); outlineJson = m_outlineData->ToJsonValue(allocator); doc.AddMember("Outline", outlineJson, allocator); } // 序列化 "Reservoir" if(m_pGeoRefData) { rapidjson::Value geoRefJson(rapidjson::kObjectType); geoRefJson = m_pGeoRefData->ToJsonValue(allocator); doc.AddMember("GeoReference", geoRefJson, allocator); } //// 序列化 "PerforationClosing" //if(m_pPerCloData) { // rapidjson::Value perCloJson(rapidjson::kObjectType); // perCloJson = m_pPerCloData->ToJsonValue(allocator); // doc.AddMember("PerforationClosing", perCloJson, allocator); //} //// 序列化 "SkinVsRate" //if(m_pSkinVsRateData) { // rapidjson::Value skinJson(rapidjson::kObjectType); // skinJson = m_pSkinVsRateData->ToJsonValue(allocator); // doc.AddMember("SkinVsRate", skinJson, allocator); //} // 序列化 "AutomaticFitting" if(m_pAutomaticFittingData) { rapidjson::Value autofitJson(rapidjson::kObjectType); autofitJson = m_pAutomaticFittingData->ToJsonValue(allocator); doc.AddMember("AutomaticFitting", autofitJson, allocator); } // 序列化 "PVT" //if(m_pebiPvtPara) { // rapidjson::Value pvtJson(rapidjson::kObjectType); // pvtJson = m_pebiPvtPara->ToJsonValue(allocator); // doc.AddMember("PVT", pvtJson, allocator); //} // 序列化 "MixResult" if(m_pMixedResults) { rapidjson::Value mixResultsJson(rapidjson::kObjectType); mixResultsJson = m_pMixedResults->ToJsonValue(allocator); doc.AddMember("MixResult", mixResultsJson, allocator); } // 序列化 "Sensitive" if(m_pSensitiveData) { rapidjson::Value sensitiveJson(rapidjson::kObjectType); sensitiveJson = m_pSensitiveData->ToJsonValue(allocator); doc.AddMember("Sensitive", sensitiveJson, allocator); } // 构建 "Wells" 数组的 JSON Value rapidjson::Value wellsJsonArray(rapidjson::kArrayType); // 遍历存储 nmDataWellBase 指针的 QVector,并解引用指针来调用 ToJsonValue 方法 foreach(nmDataWellBase* pWellData, m_vWellData) { if(nmDataVerticalFracturedWell * pVerticalFracturedWell = dynamic_cast(pWellData)) { // 检查指针是否有效 wellsJsonArray.PushBack(pVerticalFracturedWell->ToJsonValue(allocator), allocator); } else if(nmDataHorizontalFracturedWell * pHorizontalFracturedWell = dynamic_cast(pWellData)) { // 检查指针是否有效 wellsJsonArray.PushBack(pHorizontalFracturedWell->ToJsonValue(allocator), allocator); } else if(nmDataHorizontalWell * pHorizontalWell = dynamic_cast(pWellData)) { // 检查指针是否有效 wellsJsonArray.PushBack(pHorizontalWell->ToJsonValue(allocator), allocator); } else if(nmDataVerticalWell * pVerticalWell = dynamic_cast(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(m_oNumericalAnalysisCase.getPrimaryWellMode()), allocator); oCaseJson.AddMember("IncludeOtherWells", m_oNumericalAnalysisCase.getIncludeOtherWells(), allocator); oCaseJson.AddMember("PebiGridControl", m_oNumericalAnalysisCase.getPebiGridControl(), allocator); rapidjson::Value vecIncludedJson(rapidjson::kArrayType); QVector 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(oWellRef.m_eMode), allocator); vecIncludedJson.PushBack(oWellJson, allocator); } oCaseJson.AddMember("IncludedWells", vecIncludedJson, allocator); rapidjson::Value vecSolverOrderJson(rapidjson::kArrayType); QVector 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(oWellRef.m_eWellType), allocator); oOrderJson.AddMember("EntryKind", static_cast(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(m_eSolverModelType), allocator); doc.AddMember("PebiSolverType", m_nPebiSolverType, allocator); doc.AddMember("PebiOmpThreads", m_nPebiOmpThreads, allocator); doc.AddMember("PebiIluReuseSteps", m_nPebiIluReuseSteps, allocator); // 主 JSON 只保存引用,不通过目录扫描推断网格、历史或快照载荷。 rapidjson::Value oPayloadJson(rapidjson::kObjectType); if(pPayloadReferences->m_bHasCurrentGrid) { addNumericalFileReference(oPayloadJson, "CurrentGrid", pPayloadReferences->m_oCurrentGrid, allocator); } else { oPayloadJson.AddMember("CurrentGrid", rapidjson::Value(rapidjson::kNullType).Move(), allocator); } rapidjson::Value oHistoryJson(rapidjson::kArrayType); QMap::const_iterator oHistoryIt = pPayloadReferences->m_mapWellHistories.constBegin(); for(; oHistoryIt != pPayloadReferences->m_mapWellHistories.constEnd(); ++oHistoryIt) { rapidjson::Value oWellJson(rapidjson::kObjectType); oWellJson.AddMember("WellInstanceId", numericalJsonString( oHistoryIt.key(), allocator), allocator); addNumericalFileReference(oWellJson, "File", oHistoryIt.value(), allocator); oHistoryJson.PushBack(oWellJson, allocator); } oPayloadJson.AddMember("WellHistories", oHistoryJson, allocator); if(pPayloadReferences->m_bHasSnapshot) { addNumericalFileReference(oPayloadJson, "Snapshot", pPayloadReferences->m_oSnapshot, allocator); } else { oPayloadJson.AddMember("Snapshot", rapidjson::Value(rapidjson::kNullType).Move(), allocator); } doc.AddMember("PayloadFiles", oPayloadJson, allocator); // 将最终构建好的 Document 写入文件 if(!nmDataJsonTools::WriteDomToFile(doc, filePath)) { qDebug() << "Error: Failed to write DOM to file:" << filePath; return false; } return true; } bool nmDataAnalyzeManager::saveNmResultV3( const QString& sWindowDirectory, nmNumericalFileReference& oMainJsonReference, QString* pError) { if(pError != NULL) { pError->clear(); } oMainJsonReference = nmNumericalFileReference(); try { if(sWindowDirectory.isEmpty() || !ensureDirectoryExists(sWindowDirectory)) { if(pError != NULL) *pError = "Cannot create numerical v3 window directory."; return false; } nmNumericalWindowPayloadReferences oReferences; if(isPebiGridValid()) { if(m_pVtkUnstructuredGrid == NULL || !ensureDirectoryExists(QDir(sWindowDirectory).filePath("Grid"))) { if(pError != NULL) *pError = "Current PEBI grid is unavailable."; return false; } const QString sCurrentGridPath = QDir(sWindowDirectory).filePath( "Grid/CurrentGrid.vtu"); if(!nmNumericalResultPersistence::writeGrid( m_pVtkUnstructuredGrid, sCurrentGridPath, pError) || !nmNumericalResultPersistence::buildFileReference( sCurrentGridPath, "Grid/CurrentGrid.vtu", oReferences.m_oCurrentGrid)) { return false; } oReferences.m_bHasCurrentGrid = true; } const QString sHistoryDirectory = QDir(sWindowDirectory).filePath( "WellHistory"); if(!ensureDirectoryExists(sHistoryDirectory)) { if(pError != NULL) *pError = "Cannot create v3 well history directory."; return false; } QSet setWellInstanceIds; for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = m_vWellData[nIndex]; if(pWell == NULL || pWell->getWellInstanceId().isEmpty() || setWellInstanceIds.contains(pWell->getWellInstanceId())) { if(pError != NULL) *pError = "Live well UUID directory is invalid."; return false; } setWellInstanceIds.insert(pWell->getWellInstanceId()); nmNumericalWellHistoryData oHistory; oHistory.m_vecPressure = pWell->getHistoryPressure(); oHistory.m_vecLogLog = pWell->getHistoryLogLog(); oHistory.m_vecSemiLog = pWell->getHistorySemiLog(); const QString sRelativePath = QString("WellHistory/%1.bin") .arg(pWell->getWellInstanceId()); const QString sHistoryPath = QDir(sWindowDirectory).filePath( sRelativePath); nmNumericalFileReference oReference; if(!nmNumericalResultPersistence::writeWellHistory( sHistoryPath, oHistory, pError) || !nmNumericalResultPersistence::buildFileReference( sHistoryPath, sRelativePath, oReference)) { return false; } oReferences.m_mapWellHistories.insert( pWell->getWellInstanceId(), oReference); } if(!m_pPebiResultSnapshot.isNull()) { if(!nmPebiResultSnapshotSerializer::save( m_pPebiResultSnapshot, sWindowDirectory, oReferences.m_oSnapshot, pError)) { return false; } oReferences.m_bHasSnapshot = true; } // 所有外部载荷成功后才写主 JSON,使引用链不会指向半成品文件。 const QString sMainJsonPath = QDir(sWindowDirectory).filePath( "Numerical_Parameters.json"); if(!WriteProjectData(sMainJsonPath, &oReferences) || !nmNumericalResultPersistence::buildFileReference( sMainJsonPath, "Numerical_Parameters.json", oMainJsonReference)) { if(pError != NULL && pError->isEmpty()) *pError = "Cannot write numerical v3 main JSON."; return false; } // 返回保存会话前沿主 JSON 的明确引用完整复核本窗口载荷。 return validateNmResultV3Window( sWindowDirectory, sMainJsonPath, pError); } catch(const std::bad_alloc&) { if(pError != NULL) *pError = "Not enough memory to save numerical v3 result."; return false; } } bool nmDataAnalyzeManager::validateNmResultV3Window( const QString& sWindowDirectory, const QString& sMainJsonPath, QString* pError) { if(pError != NULL) { pError->clear(); } try { const QString sExpectedDirectory = QDir::cleanPath( QFileInfo(sWindowDirectory).absoluteFilePath()); const QFileInfo oMainInfo(sMainJsonPath); if(!oMainInfo.isFile() || QDir::cleanPath(oMainInfo.absolutePath()).compare( sExpectedDirectory, Qt::CaseInsensitive) != 0) { if(pError != NULL) *pError = "Numerical v3 main JSON leaves its window directory."; return false; } nmNumericalWindowPayloadReferences oReferences; QString sCurrentResultWellInstanceId; quint64 nGridInputRevision = 0; quint64 nBuiltGridRevision = 0; quint64 nResultInputRevision = 0; if(!readV3WindowHeader(sMainJsonPath, oReferences, sCurrentResultWellInstanceId, nGridInputRevision, nBuiltGridRevision, nResultInputRevision, pError)) { return false; } Q_UNUSED(nGridInputRevision); Q_UNUSED(nBuiltGridRevision); Q_UNUSED(nResultInputRevision); if(!oReferences.m_bHasSnapshot && !sCurrentResultWellInstanceId.isEmpty()) { if(pError != NULL) *pError = "Numerical v3 result well is selected without a snapshot."; return false; } if(oReferences.m_bHasCurrentGrid && !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oReferences.m_oCurrentGrid, NULL, pError)) { return false; } QMap::const_iterator oIt = oReferences.m_mapWellHistories.constBegin(); for(; oIt != oReferences.m_mapWellHistories.constEnd(); ++oIt) { QString sHistoryPath; nmNumericalWellHistoryData oValidatedHistory; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oIt.value(), &sHistoryPath, pError) || !nmNumericalResultPersistence::readWellHistory( sHistoryPath, oValidatedHistory, pError)) { return false; } } if(oReferences.m_bHasSnapshot && !nmPebiResultSnapshotSerializer::validate( sWindowDirectory, oReferences.m_oSnapshot, pError)) { return false; } return true; } catch(const std::bad_alloc&) { if(pError != NULL) *pError = "Not enough memory to validate numerical v3 result."; return false; } } bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) { try { // v3 先在独立候选对象上完成全部解析和业务校验。 // 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。 nmDataAnalyzeManager oLoadedManager; oLoadedManager.m_pOwnerFitting = m_pOwnerFitting; oLoadedManager.initPvtParaFromSubFit(); if(!oLoadedManager.loadNmResultV3InPlace(sLoadAnalDir)) { return false; } // 井构造时不能依赖尚未发布的候选 Manager,完整读取后统一绑定储层。 for(int nIndex = 0; nIndex < oLoadedManager.m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = oLoadedManager.m_vWellData[nIndex]; if(pWell != NULL) { pWell->m_pReservoir = oLoadedManager.m_reservoirData; } } swapLoadedProjectState(oLoadedManager); rebuildLoadedAttributeRegistry(); rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); return true; } catch(const std::bad_alloc&) { qWarning() << "Not enough memory to stage numerical v3 result."; return false; } } bool nmDataAnalyzeManager::loadNmResultV3InPlace( const QString& sLoadAnalDir) { try { // 外部框架仍传入旧的“成果根/窗口标识”逻辑路径;v3 只从根清单解析真实代次。 const QFileInfo oLogicalWindowInfo(QDir::cleanPath(sLoadAnalDir)); const QString sWindowId = oLogicalWindowInfo.fileName(); const QString sResultRootDirectory = oLogicalWindowInfo.dir().absolutePath(); const QString sManifestPath = QDir(sResultRootDirectory).filePath( "NumericalSaveManifest.json"); if(!QFileInfo(sManifestPath).isFile()) { const QString sLegacyJsonPath = QDir(sLoadAnalDir).filePath( "Results/Numerical_Parameters.json"); if(QFileInfo(sLegacyJsonPath).isFile()) { qWarning() << "Numerical project version 2 is not supported:" << sLegacyJsonPath; } else { qWarning() << "Numerical v3 root manifest is missing:" << sManifestPath; } return false; } QString sWindowDirectory; QString sMainJsonPath; QString sError; if(!nmNumericalSaveSession::resolveWindowMainJson( sResultRootDirectory, sWindowId, sWindowDirectory, sMainJsonPath, NULL, &sError)) { qWarning() << sError; return false; } nmNumericalWindowPayloadReferences oReferences; QString sSavedResultWellInstanceId; quint64 nGridInputRevision = 0; quint64 nBuiltGridRevision = 0; quint64 nResultInputRevision = 0; if(!readV3WindowHeader(sMainJsonPath, oReferences, sSavedResultWellInstanceId, nGridInputRevision, nBuiltGridRevision, nResultInputRevision, &sError)) { qWarning() << sError << sMainJsonPath; return false; } // 所有引用文件先完成哈希和结构读取,任何失败都不能替换已发布快照。 vtkSmartPointer pLoadedCurrentGrid; if(oReferences.m_bHasCurrentGrid) { QString sCurrentGridPath; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oReferences.m_oCurrentGrid, &sCurrentGridPath, &sError) || !nmNumericalResultPersistence::readGrid( sCurrentGridPath, pLoadedCurrentGrid, &sError)) { qWarning() << sError; return false; } } QMap mapLoadedHistories; QMap::const_iterator oHistoryIt = oReferences.m_mapWellHistories.constBegin(); for(; oHistoryIt != oReferences.m_mapWellHistories.constEnd(); ++oHistoryIt) { QString sHistoryPath; nmNumericalWellHistoryData oHistory; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oHistoryIt.value(), &sHistoryPath, &sError) || !nmNumericalResultPersistence::readWellHistory( sHistoryPath, oHistory, &sError)) { qWarning() << sError; return false; } mapLoadedHistories.insert(oHistoryIt.key(), oHistory); } QSharedPointer pLoadedCandidate; if(oReferences.m_bHasSnapshot && !nmPebiResultSnapshotSerializer::load( sWindowDirectory, oReferences.m_oSnapshot, pLoadedCandidate, &sError)) { qWarning() << sError; return false; } // 主 JSON 在外部载荷全部可用后才恢复当前输入;v2 会在版本检查处直接拒绝。 if(!ReadProjectData(sMainJsonPath)) { return false; } if(m_oNumericalAnalysisCase.getGridInputRevision() != nGridInputRevision || m_oNumericalAnalysisCase.getBuiltGridRevision() != nBuiltGridRevision || m_oNumericalAnalysisCase.getResultInputRevision() != nResultInputRevision || oReferences.m_bHasCurrentGrid != m_oNumericalAnalysisCase.isGridValid()) { qWarning() << "Loaded numerical input revisions and current grid are inconsistent."; return false; } // 实时井历史按 UUID 一一匹配;井名、井编码和数组顺序都不能替代身份。 if(mapLoadedHistories.size() != m_vWellData.size()) { qWarning() << "Loaded numerical well history directory is incomplete."; return false; } for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = m_vWellData[nIndex]; if(pWell == NULL || !mapLoadedHistories.contains(pWell->getWellInstanceId())) { qWarning() << "Loaded numerical well UUID has no history payload."; return false; } const nmNumericalWellHistoryData oHistory = mapLoadedHistories.take(pWell->getWellInstanceId()); pWell->setHistoryPressure(oHistory.m_vecPressure); pWell->setHistoryLogLog(oHistory.m_vecLogLog); pWell->setHistorySemiLog(oHistory.m_vecSemiLog); } if(!mapLoadedHistories.isEmpty()) { qWarning() << "Numerical v3 project contains unreferenced well histories."; return false; } loadWellPreAndFlow(); const QVector vecIncludedWells = getIncludedCalculationWells(); for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) { nmDataWellBase* pWell = findWellByCode( vecIncludedWells[nIndex].m_sWellCode); if(!isSupportedNumericalWell(pWell) || pWell->getFlowPoints().size() < 2) { qWarning() << "Failed to restore included numerical well:" << vecIncludedWells[nIndex].m_sWellCode; return false; } } if(!pLoadedCandidate.isNull() && !sSavedResultWellInstanceId.isEmpty() && !pLoadedCandidate->isDisplayWell(sSavedResultWellInstanceId)) { qWarning() << "Saved result well UUID is absent from the PEBI snapshot."; return false; } if(pLoadedCandidate.isNull() && !sSavedResultWellInstanceId.isEmpty()) { qWarning() << "Numerical project selects a result well without a snapshot."; return false; } // 所有可能失败的读取和映射已完成,再一次替换当前网格与规范快照。 m_pVtkUnstructuredGrid = pLoadedCurrentGrid; if(pLoadedCandidate.isNull()) { clearPebiResultSnapshot(); } else { QSharedPointer pPublished( pLoadedCandidate); QString sNextWellInstanceId = sSavedResultWellInstanceId; if(sNextWellInstanceId.isEmpty()) { const QStringList& listDisplayIds = pPublished->getDisplayWellInstanceIds(); sNextWellInstanceId = listDisplayIds.isEmpty() ? QString() : listDisplayIds.first(); } m_pPebiResultSnapshot = pPublished; m_sCurrentResultWellInstanceId = sNextWellInstanceId; pLoadedCandidate.clear(); rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); } m_bIsLoadData = m_pVtkUnstructuredGrid != NULL || !m_pPebiResultSnapshot.isNull(); return true; } catch(const std::bad_alloc&) { qWarning() << "Not enough memory to load numerical v3 result."; return false; } } void nmDataAnalyzeManager::swapLoadedProjectState( nmDataAnalyzeManager& oLoadedManager) { // 这里只交换项目数据所有权。窗口、Plot、后台任务和同步原语仍属于当前 Manager。 qSwap(m_vWellData, oLoadedManager.m_vWellData); qSwap(m_vFaultData, oLoadedManager.m_vFaultData); qSwap(m_vFractureData, oLoadedManager.m_vFractureData); qSwap(m_vRegionData, oLoadedManager.m_vRegionData); qSwap(m_vRegionMarkData, oLoadedManager.m_vRegionMarkData); qSwap(m_vecLayers, oLoadedManager.m_vecLayers); qSwap(m_vecPropertyInterpolationDataSets, oLoadedManager.m_vecPropertyInterpolationDataSets); qSwap(m_outlineData, oLoadedManager.m_outlineData); qSwap(m_axisData, oLoadedManager.m_axisData); qSwap(m_pAutomaticFittingData, oLoadedManager.m_pAutomaticFittingData); qSwap(m_reservoirData, oLoadedManager.m_reservoirData); qSwap(m_pGeoRefData, oLoadedManager.m_pGeoRefData); qSwap(m_pSensitiveData, oLoadedManager.m_pSensitiveData); qSwap(m_pebiPvtPara, oLoadedManager.m_pebiPvtPara); qSwap(m_pMixedResults, oLoadedManager.m_pMixedResults); qSwap(m_pTimeStep, oLoadedManager.m_pTimeStep); qSwap(m_pCurDataWell, oLoadedManager.m_pCurDataWell); qSwap(m_eSolverModelType, oLoadedManager.m_eSolverModelType); qSwap(m_nPebiSolverType, oLoadedManager.m_nPebiSolverType); qSwap(m_nPebiOmpThreads, oLoadedManager.m_nPebiOmpThreads); qSwap(m_nPebiIluReuseSteps, oLoadedManager.m_nPebiIluReuseSteps); qSwap(m_oNumericalAnalysisCase, oLoadedManager.m_oNumericalAnalysisCase); qSwap(m_pPebiResultSnapshot, oLoadedManager.m_pPebiResultSnapshot); qSwap(m_sCurrentResultWellInstanceId, oLoadedManager.m_sCurrentResultWellInstanceId); vtkSmartPointer pGrid = m_pVtkUnstructuredGrid; m_pVtkUnstructuredGrid = oLoadedManager.m_pVtkUnstructuredGrid; oLoadedManager.m_pVtkUnstructuredGrid = pGrid; qSwap(m_bIsLoadData, oLoadedManager.m_bIsLoadData); } void nmDataAnalyzeManager::rebuildLoadedAttributeRegistry() { if(m_pAttrRegistry == NULL) { return; } // 注册表对象本身不替换,避免破坏已经建立的 UI 信号连接。 m_pAttrRegistry->clear(); if(m_reservoirData != NULL) { m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness()); m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure()); m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability()); m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity()); m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt()); m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf()); m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi()); m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi()); } syncWellAttrs(); syncGeometryAttrs(); } bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath) { QDir dir(dirPath); if(!dir.exists()) { qDebug() << QString("Directory does not exist, attempting to create: %1").arg(dirPath); if(!dir.mkpath(dirPath)) { qDebug() << QString("Failed to create directory: %1").arg(dirPath); return false; } qDebug() << QString("Directory created successfully: %1").arg(dirPath); } else { qDebug() << QString("Directory already exists: %1").arg(dirPath); } return true; } void nmDataAnalyzeManager::loadWellPreAndFlow() { // 获取当前工区里所有的ZxDataWell井 if(zxCurProject == nullptr) { return; } ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell); foreach(nmDataWellBase* pWell, m_vWellData) { if(pWell == nullptr) { continue; } QString sWellCode = pWell->getWellCode(); ZxDataWell* pWellData = nullptr; for(int i = 0; i < wellList.size(); i++) { ZxDataWell* pCandidateWell = dynamic_cast(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(m_listGaugeP[i])) { // 拿到第一条压力数据 break; } } // 遍历流量数据列表 for(int i = 0; i < m_listGaugeF.size(); ++i) { if(pGaugeF = dynamic_cast(m_listGaugeF[i])) { // 拿到第一条流量数据 break; } } // 获取的压力、流量数据 QVector 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 nmDataAnalyzeManager::getUnstructuredGrid() const { return m_pVtkUnstructuredGrid; } void nmDataAnalyzeManager::setUnstructuredGrid(vtkSmartPointer grid) { m_pVtkUnstructuredGrid = grid; } void nmDataAnalyzeManager::clearUnstructuredGrid() { // 释放当前分析持有的实时VTK网格对象,关闭网格窗口后不再保留旧网格。 m_pVtkUnstructuredGrid = nullptr; } //void nmDataAnalyzeManager::createTimeStep() //{ // if (m_pTimeStep != nullptr) // { // delete m_pTimeStep; // m_pTimeStep = nullptr; // } // // // 获取当前井下流量的时间范围 // QVector 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; }