diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h index d9f9dbc..ce669d1 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeManager.h +++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h @@ -487,6 +487,8 @@ public: void insertSolverWell(int nIndex, const nmSolverWellRef& oWellRef); /** @brief 返回网格确定的 DLL 井槽位顺序,与包含井选择相互独立。 */ QVector getSolverWellOrder() const; + /** @brief 返回最后一次成功求解使用的 DLL 井槽位顺序。 */ + QVector getResultSolverWellOrder() const; /** @brief 整体替换 DLL 井槽位顺序,并由分析方案统一重编号。 */ void setSolverWellOrder(const QVector& vecSolverWellOrder); /** @@ -515,6 +517,8 @@ public: void discardPebiResults(); /** @brief 判断当前 PEBI 网格是否与最新输入一致。 */ bool isPebiGridValid() const; + /** @brief 判断是否存在一套可保存和查看的 PEBI 结果。 */ + bool hasPebiResults() const; /** @brief 设置结果查看井;不改变主分析井和参数编辑井。 */ void setCurrentResultWellCode(const QString& sWellCode); diff --git a/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h b/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h index a83b1fe..ede8b3d 100644 --- a/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h +++ b/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h @@ -127,6 +127,13 @@ public: /** @brief 整体替换求解器顺序,并按容器顺序重新编号。 */ void setSolverWellOrder(const QVector& vecOrder); + /** @brief 返回最后一次成功求解所使用的 DLL 井槽位顺序。 */ + QVector getResultSolverWellOrder() const; + /** @brief 从成果文件恢复结果存在状态、当前性和独立井顺序。 */ + void restoreResultState(const QVector& vecOrder, + bool bAvailable, + bool bCurrent); + /** @brief 选井或几何输入改变后,使当前网格和结果失效。 */ void invalidateGrid(); /** @brief 产量、PVT 等只影响求解的输入改变后,仅使结果失效。 */ @@ -145,6 +152,10 @@ public: /** @brief 当前网格版本与几何输入一致且井顺序非空时返回 true。 */ bool isGridValid() const; + /** @brief 内存中存在一套可保存和查看的完整求解结果时返回 true。 */ + bool hasResults() const; + /** @brief 已有结果仍对应当前输入时返回 true。 */ + bool areResultsCurrent() const; /** @brief 当前结果已提交且所依赖网格仍有效时返回 true。 */ bool areResultsValid() const; /** @brief 返回最新几何/选井输入版本,供后台任务启动时捕获。 */ @@ -160,6 +171,8 @@ public: private: /** @brief 规范化有产量包含井,移除空编码、主井和重复项。 */ void normalizeIncludedWells(); + /** @brief 保证当前结果井属于最后结果顺序,否则回退到第一口真实井。 */ + void normalizeCurrentResultWellCode(); private: QString m_sPrimaryWellCode; ///< 主分析井的项目唯一 WellCode。 @@ -170,11 +183,13 @@ private: QString m_sCurrentResultWellCode; ///< 当前结果查看井,仅属于展示状态。 QVector m_vecSolverWellOrder; ///< 网格确定的 DLL 数组下标映射。 + QVector m_vecResultSolverWellOrder; ///< 最后结果使用的独立槽位映射。 quint64 m_nGridInputRevision; ///< 几何、选井等网格输入的最新版本。 quint64 m_nBuiltGridRevision; ///< 当前缓存网格对应版本,0 表示无有效网格。 quint64 m_nResultInputRevision; ///< 产量、角色、PVT 等求解输入版本。 - bool m_bResultsValid; ///< 当前结果是否已成功映射且未过期。 + bool m_bResultsAvailable; ///< 是否存在一套可保存和查看的结果载荷。 + bool m_bResultsValid; ///< 已有结果是否仍对应当前输入和网格。 }; #endif // NMDATANUMERICALANALYSISCASE_H diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 93b9731..38a7392 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -3541,6 +3541,21 @@ bool nmDataAnalyzeManager::isWellAvailableForResult( return false; } + // 已有结果使用求解完成时冻结的井顺序。当前参数、选井或网格变化 + // 不能把最后一套结果中的井从结果界面移除。 + if(m_oNumericalAnalysisCase.hasResults()) { + const QVector vecResultOrder = + m_oNumericalAnalysisCase.getResultSolverWellOrder(); + for(int nIndex = 0; nIndex < vecResultOrder.size(); ++nIndex) { + const nmSolverWellRef& oWellRef = vecResultOrder[nIndex]; + if(oWellRef.m_eEntryKind == NM_SolverEntry_Well && + oWellRef.m_sWellCode == sWellCode) { + return true; + } + } + return false; + } + // 第一步:主分析井是结果界面的固定入口,即使当前处于关井段也必须保留。 if(sWellCode == getPrimaryWellCode()) { return true; @@ -3586,6 +3601,12 @@ QVector nmDataAnalyzeManager::getSolverWellOrder() const return m_oNumericalAnalysisCase.getSolverWellOrder(); } +QVector +nmDataAnalyzeManager::getResultSolverWellOrder() const +{ + return m_oNumericalAnalysisCase.getResultSolverWellOrder(); +} + void nmDataAnalyzeManager::setSolverWellOrder( const QVector& vecSolverWellOrder) { @@ -3682,6 +3703,11 @@ bool nmDataAnalyzeManager::isPebiGridValid() const return m_oNumericalAnalysisCase.isGridValid(); } +bool nmDataAnalyzeManager::hasPebiResults() const +{ + return m_oNumericalAnalysisCase.hasResults(); +} + void nmDataAnalyzeManager::setCurrentResultWellCode( const QString& sWellCode) { @@ -4078,8 +4104,11 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) NM_CASE_WELL_MODE eSavedPrimaryWellMode = NM_CaseWell_Observation; bool bSavedIncludeOtherWells = false; double dSavedPebiGridControl = 150.0; + bool bSavedResultsAvailable = false; + bool bSavedResultsCurrent = false; QVector vecSavedIncludedWells; QVector vecSavedSolverOrder; + QVector vecSavedResultSolverOrder; // 第一步:版本 2 必须完整保存分析方案,缺字段时不再按旧项目规则猜测。 if(!doc.HasMember("NumericalAnalysisCase") || @@ -4206,8 +4235,8 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) vecSavedIncludedWells.append(nmCalculationWellRef(sWellCode, eMode)); } - if(!setEffectiveWellCodes.contains(sSavedCurrentResultWellCode)) { - qWarning() << "Current result WellCode is not effective:" + if(!mapSavedWellTypes.contains(sSavedCurrentResultWellCode)) { + qWarning() << "Current result WellCode is absent from Wells:" << sSavedCurrentResultWellCode; return false; } @@ -4275,6 +4304,113 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) } } + // 第五步:新成果独立保存最后一次结果的井顺序和当前性。旧版本 2 文件 + // 没有这些可选字段时,先以当前 SolverWellOrder 作为候选,最终由结果 + // 二进制文件是否完整决定是否确实存在结果。 + const bool bHasAnyResultStateField = + oCaseJson.HasMember("ResultsAvailable") || + oCaseJson.HasMember("ResultsCurrent") || + oCaseJson.HasMember("ResultSolverWellOrder"); + const bool bHasCompleteResultState = + oCaseJson.HasMember("ResultsAvailable") && + oCaseJson["ResultsAvailable"].IsBool() && + oCaseJson.HasMember("ResultsCurrent") && + oCaseJson["ResultsCurrent"].IsBool() && + oCaseJson.HasMember("ResultSolverWellOrder") && + oCaseJson["ResultSolverWellOrder"].IsArray(); + if(bHasAnyResultStateField && !bHasCompleteResultState) { + qWarning() << "Numerical result state contains invalid fields:" + << filePath; + return false; + } + + if(bHasCompleteResultState) { + bSavedResultsAvailable = + oCaseJson["ResultsAvailable"].GetBool(); + bSavedResultsCurrent = oCaseJson["ResultsCurrent"].GetBool(); + if(bSavedResultsCurrent && !bSavedResultsAvailable) { + qWarning() << "Current result state has no available result:" + << filePath; + return false; + } + + QSet setResultWellCodes; + const rapidjson::Value& vecResultOrderJson = + oCaseJson["ResultSolverWellOrder"]; + for(rapidjson::SizeType nIndex = 0; + nIndex < vecResultOrderJson.Size(); + ++nIndex) { + const rapidjson::Value& oOrderJson = + vecResultOrderJson[nIndex]; + if(!oOrderJson.IsObject() || + !oOrderJson.HasMember("WellCode") || + !oOrderJson["WellCode"].IsString() || + !oOrderJson.HasMember("WellType") || + !oOrderJson["WellType"].IsInt() || + !oOrderJson.HasMember("EntryKind") || + !oOrderJson["EntryKind"].IsInt()) { + qWarning() << "ResultSolverWellOrder 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) { + const bool bSupportedResultWellType = + eWellType == NM_WELL_MODEL::Vertical_Well || + eWellType == NM_WELL_MODEL::Vertical_Fractured_Well || + eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well; + if(!mapSavedWellTypes.contains(sWellCode) || + setResultWellCodes.contains(sWellCode) || + !bSupportedResultWellType) { + qWarning() << "ResultSolverWellOrder contains an invalid real well:" + << sWellCode; + return false; + } + setResultWellCodes.insert(sWellCode); + } else if(eEntryKind == NM_SolverEntry_ManualFracture) { + if(!sWellCode.isEmpty() || + eWellType != NM_WELL_MODEL::Unknow_Well) { + qWarning() << "Result manual fracture entry is malformed:" + << filePath; + return false; + } + } else { + qWarning() << "ResultSolverWellOrder contains an unknown entry kind:" + << filePath; + return false; + } + + vecSavedResultSolverOrder.append(nmSolverWellRef( + static_cast(nIndex), eWellType, + sWellCode, eEntryKind)); + } + + if(bSavedResultsAvailable && + (vecSavedResultSolverOrder.isEmpty() || + !setResultWellCodes.contains(sSavedCurrentResultWellCode))) { + qWarning() << "Available result has no complete result well order:" + << filePath; + return false; + } + } else { + vecSavedResultSolverOrder = vecSavedSolverOrder; + } + + if(!bSavedResultsAvailable && + !setEffectiveWellCodes.contains(sSavedCurrentResultWellCode)) { + qWarning() << "Current result WellCode is not effective:" + << sSavedCurrentResultWellCode; + return false; + } + // 清空现有数据,确保从头加载新数据 foreach(nmDataFracture* pFractureData, m_vFractureData) { delete pFractureData; // 释放堆上分配的 nmDataFracture 对象 @@ -4662,6 +4798,10 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) m_oNumericalAnalysisCase.setIncludedWells(vecSavedIncludedWells); m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells); m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder); + m_oNumericalAnalysisCase.restoreResultState( + vecSavedResultSolverOrder, + bSavedResultsAvailable, + bSavedResultsCurrent); m_oNumericalAnalysisCase.setCurrentResultWellCode( sSavedCurrentResultWellCode.isEmpty() ? sSavedPrimaryWellCode : sSavedCurrentResultWellCode); @@ -4710,9 +4850,10 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) const QString sPrimaryWellCode = getPrimaryWellCode(); const QString sCurrentResultWellCode = getCurrentResultWellCode(); if(!setWellCodes.contains(sPrimaryWellCode) || + !setWellCodes.contains(sCurrentResultWellCode) || !qIsFinite(getPebiGridControl()) || getPebiGridControl() <= 0.0) { - qWarning() << "Cannot save numerical project: invalid primary well or GridControl."; + qWarning() << "Cannot save numerical project: invalid primary/result well or GridControl."; return false; } @@ -4730,12 +4871,6 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) } setEffectiveWellCodes.insert(oWellRef.m_sWellCode); } - if(!setEffectiveWellCodes.contains(sCurrentResultWellCode)) { - qWarning() << "Cannot save numerical project: result WellCode is not effective:" - << sCurrentResultWellCode; - return false; - } - QSet setOrderedWellCodes; QVector vecValidatedSolverOrder = getSolverWellOrder(); // 求解器顺序只属于有效网格。网格已经失效时保存空顺序, @@ -4772,6 +4907,56 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) return false; } + // 最后结果使用独立井顺序,不要求继续属于当前有效计算井,也不要求井型 + // 仍与当前模型一致;但对应 WellCode 必须仍存在,才能保存单井曲线数据。 + const bool bHasResults = m_oNumericalAnalysisCase.hasResults(); + QVector vecValidatedResultOrder; + if(bHasResults) { + vecValidatedResultOrder = + m_oNumericalAnalysisCase.getResultSolverWellOrder(); + } + QSet setResultWellCodes; + for(int nIndex = 0; + nIndex < vecValidatedResultOrder.size(); + ++nIndex) { + const nmSolverWellRef& oWellRef = vecValidatedResultOrder[nIndex]; + if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { + const bool bSupportedResultWellType = + oWellRef.m_eWellType == NM_WELL_MODEL::Vertical_Well || + oWellRef.m_eWellType == NM_WELL_MODEL::Vertical_Fractured_Well || + oWellRef.m_eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well; + if(!setWellCodes.contains(oWellRef.m_sWellCode) || + setResultWellCodes.contains(oWellRef.m_sWellCode) || + !bSupportedResultWellType) { + qWarning() << "Cannot save numerical project: invalid result WellCode:" + << oWellRef.m_sWellCode; + return false; + } + setResultWellCodes.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 result fracture entry."; + return false; + } + } else { + qWarning() << "Cannot save numerical project: unknown result entry kind."; + return false; + } + } + if(bHasResults && + (vecValidatedResultOrder.isEmpty() || + !setResultWellCodes.contains(sCurrentResultWellCode))) { + qWarning() << "Cannot save numerical project: result order is incomplete."; + return false; + } + if(!bHasResults && + !setEffectiveWellCodes.contains(sCurrentResultWellCode)) { + qWarning() << "Cannot save numerical project: result WellCode is not effective:" + << sCurrentResultWellCode; + return false; + } + // 第三步:基础身份校验通过后再构造 JSON,避免生成无法重新加载的半成品文件。 rapidjson::Document doc; doc.SetObject(); // 根节点是对象 @@ -5030,6 +5215,28 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) vecSolverOrderJson.PushBack(oOrderJson, allocator); } oCaseJson.AddMember("SolverWellOrder", vecSolverOrderJson, allocator); + oCaseJson.AddMember("ResultsAvailable", bHasResults, allocator); + oCaseJson.AddMember("ResultsCurrent", + m_oNumericalAnalysisCase.areResultsCurrent(), allocator); + + rapidjson::Value vecResultOrderJson(rapidjson::kArrayType); + for(int nIndex = 0; + nIndex < vecValidatedResultOrder.size(); + ++nIndex) { + const nmSolverWellRef& oWellRef = vecValidatedResultOrder[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); + vecResultOrderJson.PushBack(oOrderJson, allocator); + } + oCaseJson.AddMember("ResultSolverWellOrder", + vecResultOrderJson, allocator); doc.AddMember("NumericalAnalysisCase", oCaseJson, allocator); // 序列化 时间步数据 @@ -5111,37 +5318,30 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn sTimeStepDataDirPath + "/PressureTimeSteps.bin"; QString sWellRstDataPath = sDir + "/WellRst"; - // 第二步:无有效网格时不保存内存中残留的 VTK 对象和结果,并清理同目录旧文件。 - if(!isPebiGridValid()) { - QFile::remove(sGridPath + "/CurrentGrid.vtu"); - QFile::remove(sResultGridFilePath); - QFile::remove(sResultWellLocationPath); - QFile::remove(sTimeStepDataFilePath); - if(QDir(sWellRstDataPath).exists() && - !clearDirectoryContents(sWellRstDataPath)) { - return false; - } - return true; - } - - // 方案标记为有效网格时,必须同时持有可写出的 VTK 网格。 - if(!m_pVtkUnstructuredGrid) { - qWarning() << "Cannot save a valid PEBI grid: VTK grid is missing."; + // 第二步:当前网格和最后结果是两套独立载荷。当前网格失效时只删除 + // CurrentGrid.vtu,不能连带删除已经成功生成的结果。 + const bool bGridValid = isPebiGridValid(); + const bool bHasResults = m_oNumericalAnalysisCase.hasResults(); + if((bGridValid || bHasResults) && + !this->ensureDirectoryExists(sGridPath)) { return false; } - if(!this->ensureDirectoryExists(sGridPath)) { - return false; - } - - // 第三步:保存与 SolverWellOrder 同版本的实时网格。 - if(!this->writeUnstructuredGridToFile( - m_pVtkUnstructuredGrid, sGridPath + "/CurrentGrid.vtu")) { - return false; + if(bGridValid) { + if(!m_pVtkUnstructuredGrid) { + qWarning() << "Cannot save a valid PEBI grid: VTK grid is missing."; + return false; + } + if(!this->writeUnstructuredGridToFile( + m_pVtkUnstructuredGrid, sGridPath + "/CurrentGrid.vtu")) { + return false; + } + } else { + QFile::remove(sGridPath + "/CurrentGrid.vtu"); } - // 第四步:网格有效但结果无效时只保存网格,并删除上一次求解的残留文件。 - if(!m_oNumericalAnalysisCase.areResultsValid()) { + // 第三步:确实没有任何已生成结果时,才清理同目录中的旧结果文件。 + if(!bHasResults) { QFile::remove(sResultGridFilePath); QFile::remove(sResultWellLocationPath); QFile::remove(sTimeStepDataFilePath); @@ -5152,13 +5352,14 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn return true; } - // 结果标记有效时,基础网格和时间步压力必须同时完整存在。 + // 结果存在时,基础网格和时间步压力必须同时完整存在;是否仍对应当前 + // 输入不影响保存和查看。 if(!m_pResultBaseGrid || m_mapTimeStepDataP.isEmpty()) { - qWarning() << "Cannot save valid PEBI results: result grid or time steps are missing."; + qWarning() << "Cannot save available PEBI results: result grid or time steps are missing."; return false; } - // 第五步:保存场图基础网格、井位置和全部时间步压力。 + // 第四步:保存场图基础网格、井位置和全部时间步压力。 if(!this->writeUnstructuredGridToFile( m_pResultBaseGrid, sResultGridFilePath)) { return false; @@ -5177,14 +5378,15 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn return false; } - // 第六步:先清空目标目录,再按 WellCode 保存每口真实计算井的结果。 + // 第五步:先清空目标目录,再按最后结果的 WellCode 顺序保存单井结果。 if(!this->ensureDirectoryExists(sWellRstDataPath)) { return false; } if(!clearDirectoryContents(sWellRstDataPath)) { return false; } - QVector vecWells = this->getSolverWellOrder(); + QVector vecWells = + this->getResultSolverWellOrder(); // 遍历每口井,保存里面计算出来的数据 for(int wellIdx = 0; wellIdx < vecWells.size(); ++wellIdx) { @@ -5196,10 +5398,9 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn nmDataWellBase* pWellData = this->findWellByCode(oWellRef.m_sWellCode); - if(pWellData) { - if(!this->saveWellCalRstData(sWellRstDataPath, pWellData)) { - return false; - } + if(pWellData == nullptr || + !this->saveWellCalRstData(sWellRstDataPath, pWellData)) { + return false; } } @@ -5214,6 +5415,15 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) return false; } + // JSON 中的当前性在读取当前网格前先保存下来;markGridBuilt() 会按运行时 + // 语义把已有结果标为非当前,完整载荷读完后再恢复保存时的状态。 + const bool bSavedResultsCurrent = + m_oNumericalAnalysisCase.areResultsCurrent(); + const bool bDeclaredResultsAvailable = + m_oNumericalAnalysisCase.hasResults(); + const QVector vecResultSolverOrder = + getResultSolverWellOrder(); + // 第一步:先检查分析方案、网格文件和结果文件是否构成完整的一组。 QString sCurrentGridPath = sLoadAnalDir + "/Grid/CurrentGrid.vtu"; QString sResultGridPath = sLoadAnalDir + "/Grid/ResultGrid.vtu"; @@ -5233,6 +5443,7 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) const bool bHasAnyWellResultFile = !QDir(sWellRstDataPath).entryList( listResultFilters, QDir::Files).isEmpty(); const bool bHasSolverWellOrder = !getSolverWellOrder().isEmpty(); + const bool bHasResultSolverOrder = !vecResultSolverOrder.isEmpty(); const bool bHasAnyResultFile = bHasResultGridFile || bHasResultWellLocationFile || bHasTimeStepDataFile || bHasAnyWellResultFile; @@ -5245,10 +5456,15 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) return false; } if(bHasAnyResultFile && - (!bHasCurrentGridFile || !bHasCompleteResultFiles)) { + (!bHasCompleteResultFiles || !bHasResultSolverOrder)) { qWarning() << "PEBI result files are incomplete:" << sLoadAnalDir; return false; } + if(bDeclaredResultsAvailable && !bHasCompleteResultFiles) { + qWarning() << "Declared PEBI results are missing from disk:" + << sLoadAnalDir; + return false; + } // 第二步:读取当前网格;只有文件和求解器顺序同时存在时才标记有效。 if(bHasCurrentGridFile && @@ -5264,7 +5480,8 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) m_oNumericalAnalysisCase.markGridBuilt(); } - // 第三步:读取完整求解结果;网格项目允许尚未执行过求解。 + // 第三步:读取完整求解结果。结果使用自己的网格和井顺序,不要求 + // CurrentGrid.vtu 仍然有效或仍然存在。 if(bHasResultGridFile && !this->readUnstructuredGridFromFile(m_pResultBaseGrid, sResultGridPath)) { @@ -5302,12 +5519,13 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) } // 第五步:有完整场结果时,必须同时具备每口真实求解井的结果。 - // SolverWellOrder 是 DLL 返回槽位与井对象之间的唯一映射;手工裂缝只占槽位, - // 不对应 Map 中的井对象,也没有独立的结果文件。 + // ResultSolverWellOrder 是最后结果槽位与井对象之间的映射;手工裂缝只占 + // 槽位,不对应 Map 中的井对象,也没有独立的结果文件。 if(bHasCompleteResultFiles) { - QVector vecSolverOrder = getSolverWellOrder(); - for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) { - const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex]; + for(int nIndex = 0; + nIndex < vecResultSolverOrder.size(); + ++nIndex) { + const nmSolverWellRef& oWellRef = vecResultSolverOrder[nIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) { continue; } @@ -5374,18 +5592,23 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) } } - // 结果井属于显示状态,但也必须在流量恢复后满足当前结果井规则。 + // 二进制载荷完整后再登记结果存在。旧版本 2 项目没有独立结果状态时 + // 也会在这里按实际文件恢复,当前输入状态则保守标为不一致。 + m_oNumericalAnalysisCase.restoreResultState( + bHasCompleteResultFiles + ? vecResultSolverOrder : QVector(), + bHasCompleteResultFiles, + bHasCompleteResultFiles && bHasCurrentGridFile && + bSavedResultsCurrent); + + // 结果井属于显示状态;有历史结果时按结果井顺序校验,无结果时按当前方案。 if(!isWellAvailableForResult(getCurrentResultWellCode())) { qWarning() << "Loaded result WellCode is not available:" << getCurrentResultWellCode(); return false; } - if(bHasCurrentGridFile && bHasCompleteResultFiles) { - m_oNumericalAnalysisCase.markResultsAvailable(); - } - - if (m_pVtkUnstructuredGrid != nullptr) + if (m_pVtkUnstructuredGrid != nullptr || m_pResultBaseGrid != nullptr) m_bIsLoadData = true; return true; @@ -6017,9 +6240,10 @@ QMap nmDataAnalyzeManager::getAllWellLocations() const bool nmDataAnalyzeManager::saveWellLocations(const QString& filePath) { - // 第一步:结果位置必须逐口覆盖求解器中的真实井,手工裂缝不对应 Map 井。 + // 第一步:结果位置必须逐口覆盖最后结果中的真实井,手工裂缝不对应 Map 井。 QSet setExpectedWellCodes; - QVector vecSolverOrder = getSolverWellOrder(); + QVector vecSolverOrder = + getResultSolverWellOrder(); for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { @@ -6089,9 +6313,10 @@ bool nmDataAnalyzeManager::loadWellLocations(const QString& filePath) return false; } - // 第二步:加载结果必须与 SolverWellOrder 中的真实井 WellCode 完整对应。 + // 第二步:加载结果必须与最后结果井顺序中的真实井 WellCode 完整对应。 QSet setExpectedWellCodes; - QVector vecSolverOrder = getSolverWellOrder(); + QVector vecSolverOrder = + getResultSolverWellOrder(); for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { diff --git a/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp b/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp index 264adeb..f3c144c 100644 --- a/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp +++ b/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp @@ -66,8 +66,9 @@ void nmDataNumericalAnalysisCase::setPrimaryWellCode(const QString& sWellCode) const QString sOldPrimaryWellCode = m_sPrimaryWellCode; m_sPrimaryWellCode = sWellCode; normalizeIncludedWells(); - if(m_sCurrentResultWellCode.isEmpty() || - m_sCurrentResultWellCode == sOldPrimaryWellCode) { + if(!m_bResultsAvailable && + (m_sCurrentResultWellCode.isEmpty() || + m_sCurrentResultWellCode == sOldPrimaryWellCode)) { m_sCurrentResultWellCode = m_sPrimaryWellCode; } invalidateGrid(); @@ -251,6 +252,7 @@ void nmDataNumericalAnalysisCase::appendSolverWell( nmSolverWellRef oIndexedRef = oWellRef; oIndexedRef.m_nSolverIndex = m_vecSolverWellOrder.size(); m_vecSolverWellOrder.append(oIndexedRef); + m_bResultsValid = false; } void nmDataNumericalAnalysisCase::insertSolverWell( @@ -267,6 +269,7 @@ void nmDataNumericalAnalysisCase::insertSolverWell( ++nOrderIndex) { m_vecSolverWellOrder[nOrderIndex].m_nSolverIndex = nOrderIndex; } + m_bResultsValid = false; } bool nmDataNumericalAnalysisCase::removeSolverWell(int nIndex) @@ -295,6 +298,32 @@ void nmDataNumericalAnalysisCase::setSolverWellOrder( m_bResultsValid = false; } +QVector +nmDataNumericalAnalysisCase::getResultSolverWellOrder() const +{ + return m_vecResultSolverWellOrder; +} + +void nmDataNumericalAnalysisCase::restoreResultState( + const QVector& vecOrder, + bool bAvailable, + bool bCurrent) +{ + m_vecResultSolverWellOrder = vecOrder; + for(int nIndex = 0; + nIndex < m_vecResultSolverWellOrder.size(); + ++nIndex) { + m_vecResultSolverWellOrder[nIndex].m_nSolverIndex = nIndex; + } + + m_bResultsAvailable = bAvailable && + !m_vecResultSolverWellOrder.isEmpty(); + m_bResultsValid = m_bResultsAvailable && bCurrent; + if(m_bResultsAvailable) { + normalizeCurrentResultWellCode(); + } +} + // ==================== 网格与结果版本管理 ==================== void nmDataNumericalAnalysisCase::invalidateGrid() @@ -341,7 +370,14 @@ void nmDataNumericalAnalysisCase::discardResults() void nmDataNumericalAnalysisCase::markResultsAvailable() { - m_bResultsValid = isGridValid(); + if(!isGridValid()) { + return; + } + + m_vecResultSolverWellOrder = m_vecSolverWellOrder; + m_bResultsAvailable = true; + m_bResultsValid = true; + normalizeCurrentResultWellCode(); } bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent( @@ -354,8 +390,16 @@ bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent( nResultInputRevision == m_nResultInputRevision; // 第二步:只有输入未变化且网格仍有效时,才允许旧线程提交结果。 - m_bResultsValid = bInputCurrent && isGridValid(); - return m_bResultsValid; + // 校验失败不能破坏此前已经存在的结果。 + if(!bInputCurrent || !isGridValid()) { + return false; + } + + m_vecResultSolverWellOrder = m_vecSolverWellOrder; + m_bResultsAvailable = true; + m_bResultsValid = true; + normalizeCurrentResultWellCode(); + return true; } bool nmDataNumericalAnalysisCase::isGridValid() const @@ -367,7 +411,18 @@ bool nmDataNumericalAnalysisCase::isGridValid() const bool nmDataNumericalAnalysisCase::areResultsValid() const { - return m_bResultsValid && isGridValid(); + return areResultsCurrent() && isGridValid(); +} + +bool nmDataNumericalAnalysisCase::hasResults() const +{ + return m_bResultsAvailable && + !m_vecResultSolverWellOrder.isEmpty(); +} + +bool nmDataNumericalAnalysisCase::areResultsCurrent() const +{ + return hasResults() && m_bResultsValid; } quint64 nmDataNumericalAnalysisCase::getGridInputRevision() const @@ -395,9 +450,11 @@ void nmDataNumericalAnalysisCase::clear() m_vecIncludedWells.clear(); m_sCurrentResultWellCode.clear(); m_vecSolverWellOrder.clear(); + m_vecResultSolverWellOrder.clear(); m_nGridInputRevision = 1; m_nBuiltGridRevision = 0; m_nResultInputRevision = 1; + m_bResultsAvailable = false; m_bResultsValid = false; } @@ -422,3 +479,25 @@ void nmDataNumericalAnalysisCase::normalizeIncludedWells() m_vecIncludedWells = vecNormalized; } + +void nmDataNumericalAnalysisCase::normalizeCurrentResultWellCode() +{ + QString sFirstResultWellCode; + for(int nIndex = 0; + nIndex < m_vecResultSolverWellOrder.size(); + ++nIndex) { + const nmSolverWellRef& oWellRef = + m_vecResultSolverWellOrder[nIndex]; + if(oWellRef.m_eEntryKind != NM_SolverEntry_Well) { + continue; + } + if(sFirstResultWellCode.isEmpty()) { + sFirstResultWellCode = oWellRef.m_sWellCode; + } + if(oWellRef.m_sWellCode == m_sCurrentResultWellCode) { + return; + } + } + + m_sCurrentResultWellCode = sFirstResultWellCode; +} diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index e8767ce..7233aa7 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -1983,10 +1983,13 @@ bool nmSubWndMain::mergeAnaResultToFitting() m_nPendingResultPebiCount = -1; m_nPendingResultSolveTimeMs = -1; - QVector vecWellsOrder = pInstance->getSolverWellOrder(); + QVector vecWellsOrder = + pInstance->hasPebiResults() + ? pInstance->getResultSolverWellOrder() + : pInstance->getSolverWellOrder(); QString sCurrentResultWellCode = pInstance->getCurrentResultWellCode(); - // 当前结果井必须同时位于本次求解顺序并具备结果展示资格。 + // 当前结果井必须同时位于最后一次结果顺序并具备结果展示资格。 // 无流量观察井虽然有内部压力结果,但不能成为曲线窗口的当前结果井。 const bool bResultWellAvailable = pInstance->isWellAvailableForResult(sCurrentResultWellCode); diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index a9104af..f9c9c73 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -372,9 +372,12 @@ void nmWxNumericalDesign::fillResultWellCombo(const QString& sSelectedWellCode) } QVector vecResultWellCodes; - QVector vecSolverOrder = pManager->getSolverWellOrder(); + QVector vecSolverOrder = + pManager->hasPebiResults() + ? pManager->getResultSolverWellOrder() + : pManager->getSolverWellOrder(); - // 第一步:已有网格时按 DLL 的固定下标顺序收集可展示结果井。 + // 第一步:有结果时优先按最后结果顺序,否则按当前网格顺序收集结果井。 // 无流量观察井仍在求解器顺序中,但不能出现在本下拉框。 for(int nOrderIndex = 0; nOrderIndex < vecSolverOrder.size(); ++nOrderIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nOrderIndex];