diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h index 88939f8..f3d13be 100644 --- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h +++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h @@ -45,6 +45,7 @@ struct nmPebiWellInputSnapshot QString m_sWellCode; ///< 项目内稳定井编码。 QString m_sWellName; ///< 捕获时井名称。 QString m_sWellInstanceId; ///< 完整场结果使用的稳定井 UUID。 + QString m_sGaugeInputSha1; ///< 与井角色无关的规范化 Gauge 输入签名。 QVector m_vecFlowDurations; ///< 去除框架占位行后的公共流动段时长。 QVector m_vecOilRates; ///< 与公共流动段逐项对应的油相流量。 QVector m_vecGasRates; ///< 与公共流动段逐项对应的气相流量。 diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h index 85e3a9a..38d0a0c 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeManager.h +++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h @@ -744,7 +744,7 @@ public: const nmNumericalWindowPayloadReferences* pPayloadReferences = NULL); /** - * @brief 将本窗口完整写入保存会话指定的 v3 代次目录。 + * @brief 将本窗口完整写入保存会话指定的 v5 代次目录。 * @note 主 JSON 最后写入,并在返回前校验全部引用文件。 */ bool saveNmResultV3( @@ -753,7 +753,7 @@ public: QString* pError = NULL); /** - * @brief 校验一个 v3 窗口主 JSON 明确引用的全部载荷。 + * @brief 校验一个 v5 窗口主 JSON 明确引用的全部载荷。 * @note 保存会话用它复核从旧根清单保留、但本轮未重写的窗口。 */ static bool validateNmResultV3Window( @@ -781,8 +781,7 @@ public: return bExists; } - // 加载井数组里对应的压力、流量数据,这个是在井里没有这些数据的情况下调用 - /** @brief 按保存井别重新读取工程井压力和流量,任一井映射失败时返回 false。 */ + /** @brief 显式刷新框架井压力和流量;成果加载流程不得调用。 */ bool loadWellPreAndFlow(); // 设置当前分析下的VTK网格对象 @@ -802,6 +801,8 @@ public: QVector>& vvecHistoryData, QVector>& vvecLogPreData, QVector>& vvecSemiLogPreData); + /** @brief 使用同一份 Gauge 输入原子更新井的三组历史曲线及签名。 */ + bool updateWellHistoryData(nmDataWellBase* pWellData); // 获取许可证路径 void setLicensePath(const QString& licensePath); @@ -834,7 +835,7 @@ public: private: /** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */ void rebindPebiResultSnapshotToWells(); - /** @brief 在候选 Manager 内完整读取并校验一套 v3 成果。 */ + /** @brief 在候选 Manager 内完整读取并校验一套 v5 成果。 */ bool loadNmResultV3InPlace(const QString& sLoadAnalDir); /** @brief 通过交换所有权一次发布已完整校验的候选项目状态。 */ void swapLoadedProjectState(nmDataAnalyzeManager& oLoadedManager); diff --git a/Include/nmNum/nmData/nmDataGaugeRecord.h b/Include/nmNum/nmData/nmDataGaugeRecord.h index 1ca1475..e49256f 100644 --- a/Include/nmNum/nmData/nmDataGaugeRecord.h +++ b/Include/nmNum/nmData/nmDataGaugeRecord.h @@ -8,7 +8,7 @@ /** * @brief 工程井下一条压力测量记录的只读快照。 - * @note 记录本身不持久化到数值项目文件,每次都从工程井节点重新读取; + * @note 首次导入时从框架读取,保存数值成果后作为只读副本随成果持久化; * 损坏或为空的记录仍保留在列表中,只是不可被选中。 */ struct nmPressureGaugeRecord { @@ -27,7 +27,8 @@ struct nmPressureGaugeRecord { /** * @brief 工程井下一条流量测量记录的只读快照。 * @note GaugeDataEx2 非空时按 N 行 [段时长、油、气、水] 解析多相数据; - * 否则解析 GaugeData,并按井别归入对应的单相流量。 + * 否则解析 GaugeData,并按井别归入对应的单相流量;成果保存后整条记录 + * 作为只读副本持久化,不再从框架重新推断。 */ struct nmFlowGaugeRecord { QString sGaugeCode; // 对应 ZxDataGaugeF::getCode(),选择持久化键 @@ -47,3 +48,41 @@ struct nmFlowGaugeRecord { { } }; + +/** @brief 与井角色无关、供历史曲线和求解捕获共同使用的规范化 Gauge 输入。 */ +struct nmWellGaugeInputData { + QString sWellInstanceId; + QString sWellCode; + NM_WELL_CATEGORY eWellCategory; + QString sPressureGaugeCode; + QVector vecPressurePoints; + QString sFlowGaugeCode; + bool bUseOilRate; + bool bUseGasRate; + bool bUseWaterRate; + NM_PHASE_TYPE eFlowSchedulePhase; + QVector vecFlowDurations; + QVector vecOilRates; + QVector vecGasRates; + QVector vecWaterRates; + int nFlowSectionIndex; + + nmWellGaugeInputData() + : eWellCategory(NM_WellCategory_Unknown) + , bUseOilRate(false) + , bUseGasRate(false) + , bUseWaterRate(false) + , eFlowSchedulePhase(PHASE_UNKNOWN) + , nFlowSectionIndex(0) + { + } + + bool hasAlignedFlowSchedule() const + { + return eFlowSchedulePhase != PHASE_UNKNOWN && + !vecFlowDurations.isEmpty() && + vecOilRates.size() == vecFlowDurations.size() && + vecGasRates.size() == vecFlowDurations.size() && + vecWaterRates.size() == vecFlowDurations.size(); + } +}; diff --git a/Include/nmNum/nmData/nmDataWellBase.h b/Include/nmNum/nmData/nmDataWellBase.h index 88d3e74..d9aa02a 100644 --- a/Include/nmNum/nmData/nmDataWellBase.h +++ b/Include/nmNum/nmData/nmDataWellBase.h @@ -199,6 +199,16 @@ public: void setUseOilRate(bool bUsed); void setUseGasRate(bool bUsed); void setUseWaterRate(bool bUsed); + /** @brief 构造与井角色无关的规范化 Gauge 输入。 */ + void buildGaugeInputData(nmWellGaugeInputData& oInput) const; + /** @brief 按固定二进制格式计算指定规范化 Gauge 输入的 SHA-1。 */ + static QString calculateGaugeInputSha1( + const nmWellGaugeInputData& oInput); + /** @brief 返回当前井规范化 Gauge 输入的 SHA-1。 */ + QString calculateGaugeInputSha1() const; + /** @brief 返回记录自身、不受分相开关影响的真实段数。 */ + static int getRawFlowRecordSegmentCount( + const nmFlowGaugeRecord& oRecord); int getIndexF() const; void setIndexF(const int newIndex); @@ -229,6 +239,14 @@ public: QVector> getHistorySemiLog(); void setHistorySemiLog(QVector> semiLogData); + /** @brief 原子写入三组历史曲线及其对应的 Gauge 输入签名。 */ + void setHistoryData( + const QVector >& vecPressure, + const QVector >& vecLogLog, + const QVector >& vecSemiLog, + const QString& sGaugeInputSha1); + /** @brief 返回生成当前三组历史曲线时冻结的 Gauge 输入签名。 */ + QString getHistoryGaugeInputSha1() const; void setWellType(NM_WELL_MODEL newWellType); NM_WELL_MODEL getWellType() const; @@ -362,6 +380,7 @@ protected: QVector> m_vvecHsyPressure; // 历史压力数据 QVector> m_vvecHsyLogLog; // 历史双对数数据 QVector> m_vvecHsySemiLog; // 历史半对数数据 + QString m_sHistoryGaugeInputSha1; // 三组历史曲线对应的规范化 Gauge 输入签名 /** @brief 最后结果快照的弱引用,不延长历史结果生命周期。 */ QWeakPointer m_pPebiResultSnapshot; diff --git a/Include/nmNum/nmData/nmNumericalResultPersistence.h b/Include/nmNum/nmData/nmNumericalResultPersistence.h index 19e5c14..fc51af0 100644 --- a/Include/nmNum/nmData/nmNumericalResultPersistence.h +++ b/Include/nmNum/nmData/nmNumericalResultPersistence.h @@ -2,6 +2,7 @@ #define NMNUMERICALRESULTPERSISTENCE_H #include "nmData_global.h" +#include "nmDataGaugeRecord.h" #include #include @@ -12,7 +13,7 @@ class QFile; class vtkUnstructuredGrid; -/** @brief v3 清单中对一个不可变文件的长度和 SHA-1 引用。 */ +/** @brief 数值成果清单中对一个不可变文件的长度和 SHA-1 引用。 */ struct NM_DATA_EXPORT nmNumericalFileReference { /** @brief 初始化空文件引用。 */ @@ -29,9 +30,11 @@ struct NM_DATA_EXPORT nmNumericalFileReference bool isValid() const; }; -/** @brief 一口实时井在 v3 成果中的冻结历史曲线。 */ +/** @brief 一口实时井在数值成果中的冻结历史曲线。 */ struct NM_DATA_EXPORT nmNumericalWellHistoryData { + /** @brief 生成三组曲线时使用的 Gauge 输入签名。 */ + QString m_sGaugeInputSha1; /** @brief 压力历史曲线。 */ QVector > m_vecPressure; /** @brief 双对数历史曲线。 */ @@ -40,6 +43,17 @@ struct NM_DATA_EXPORT nmNumericalWellHistoryData QVector > m_vecSemiLog; }; +/** @brief 一口实时井在数值成果中的完整 Gauge 候选记录副本。 */ +struct NM_DATA_EXPORT nmNumericalWellGaugeData +{ + /** @brief 记录所属实时井的稳定 UUID。 */ + QString m_sWellInstanceId; + /** @brief 全部压力候选记录。 */ + QVector m_vecPressureRecords; + /** @brief 全部流量候选记录。 */ + QVector m_vecFlowRecords; +}; + /** @brief 一个窗口主 JSON 引用的全部外部载荷。 */ struct NM_DATA_EXPORT nmNumericalWindowPayloadReferences { @@ -49,6 +63,8 @@ struct NM_DATA_EXPORT nmNumericalWindowPayloadReferences nmNumericalFileReference m_oCurrentGrid; /** @brief 按实时井 UUID 保存的历史曲线文件引用。 */ QMap m_mapWellHistories; + /** @brief 按实时井 UUID 保存的完整 Gauge 记录文件引用。 */ + QMap m_mapWellGaugeRecords; /** @brief 最后成功结果快照是否存在。 */ bool m_bHasSnapshot; /** @brief 快照元数据 JSON 文件引用。 */ @@ -59,7 +75,7 @@ struct NM_DATA_EXPORT nmNumericalWindowPayloadReferences }; /** - * @brief v3 持久化共用的路径、哈希、网格和小型二进制工具。 + * @brief 数值成果持久化共用的路径、哈希、网格和小型二进制工具。 * * 大文件始终分块处理;JSON 可以由调用方在受限文件大小校验后读取。 */ @@ -99,6 +115,17 @@ public: nmNumericalWellHistoryData& oHistory, QString* pError = NULL); + /** @brief 写入一口井的全部压力和流量候选记录。 */ + static bool writeWellGaugeData( + const QString& sFilePath, + const nmNumericalWellGaugeData& oGaugeData, + QString* pError = NULL); + /** @brief 严格读取一口井的全部压力和流量候选记录。 */ + static bool readWellGaugeData( + const QString& sFilePath, + nmNumericalWellGaugeData& oGaugeData, + QString* pError = NULL); + /** @brief 以二进制 VTU 写出网格并检查 VTK 返回值和文件存在性。 */ static bool writeGrid(vtkUnstructuredGrid* pGrid, const QString& sFilePath, diff --git a/Include/nmNum/nmData/nmPebiResultSnapshot.h b/Include/nmNum/nmData/nmPebiResultSnapshot.h index 05f6143..7b70f33 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshot.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshot.h @@ -76,6 +76,8 @@ struct NM_DATA_EXPORT nmPebiResultWellSnapshot NM_WELL_CATEGORY m_eWellCategory; /** @brief 求解时冻结的井角色。 */ NM_CASE_WELL_MODE m_eWellMode; + /** @brief 求解时冻结的规范化 Gauge 输入签名。 */ + QString m_sGaugeInputSha1; /** @brief 求解时冻结的井平面位置。 */ QPointF m_oLocation; /** @brief 求解时是否存在射孔数据。 */ @@ -343,7 +345,7 @@ private: /** @brief 求解时冻结的求解设置。 */ nmPebiResultSolverSettings m_oSolverSettings; - // 只有 Builder 和后续 v3 序列化器可以在发布前填充私有数据。 + // 只有 Builder 和快照序列化器可以在发布前填充私有数据。 friend class nmPebiResultSnapshotBuilder; friend class nmPebiResultSnapshotRenderAdapter; friend class nmPebiResultSnapshotSerializer; diff --git a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h index 67317f2..62c1c55 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h @@ -21,7 +21,7 @@ public: /** @brief 销毁尚未被 takeCandidate() 转交的候选。 */ ~nmPebiResultSnapshotBuilder(); - /** @brief v4 加载时恢复已保存的快照 UUID;求解候选可使用自动生成值。 */ + /** @brief 加载时恢复已保存的快照 UUID;求解候选可使用自动生成值。 */ bool setSnapshotId(const QString& sSnapshotId); /** @brief 设置候选创建时对应的网格输入版本和求解输入版本。 */ bool setInputRevisions(quint64 nGridInputRevision, diff --git a/Include/nmNum/nmData/nmPebiResultSnapshotSerializer.h b/Include/nmNum/nmData/nmPebiResultSnapshotSerializer.h index bf2018f..7b6e7fb 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshotSerializer.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshotSerializer.h @@ -6,11 +6,12 @@ #include #include +#include class nmPebiResultSnapshot; /** - * @brief 负责 v4 快照三文件载荷及 Snapshot.json 的流式保存和加载。 + * @brief 负责当前快照三文件载荷及 Snapshot.json 的流式保存和加载。 * * 序列化器是快照的受限友元,只在保存时读取内部 VTK 对象;加载仍通过 * nmPebiResultSnapshotBuilder 构造和封闭候选。 @@ -31,6 +32,13 @@ public: const nmNumericalFileReference& oSnapshotJsonReference, QString* pError = NULL); + /** @brief 返回 Snapshot.json 中三个规范化载荷路径,供窗口级全局查重。 */ + static bool getPayloadRelativePaths( + const QString& sWindowDirectory, + const nmNumericalFileReference& oSnapshotJsonReference, + QStringList& listRelativePaths, + QString* pError = NULL); + /** @brief 完整读取并通过 Builder 返回尚未发布的可写候选。 */ static bool load( const QString& sWindowDirectory, diff --git a/Include/nmNum/nmPlot/nmPlotGraphicBinder.h b/Include/nmNum/nmPlot/nmPlotGraphicBinder.h index 1594b2b..fa420a0 100644 --- a/Include/nmNum/nmPlot/nmPlotGraphicBinder.h +++ b/Include/nmNum/nmPlot/nmPlotGraphicBinder.h @@ -190,10 +190,6 @@ public: void updateWellDataMapping(nmObjPointWell* pGraphic, nmDataWellBase* pNewData) { m_mapWellData[pGraphic] = pNewData; } // 井相关数据中心委托方法 - void calculateWellLogData(nmDataWellBase* pWellData, - QVector>& pressure, - QVector>& logLog, - QVector>& semiLog); bool hasLayers() const; nmDataWellBase* getCurrentWellData() const; void setCurrentWellData(nmDataWellBase* pWellData); diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 74b22de..bbcfa11 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -219,6 +219,14 @@ bool captureResultWellMetadata( return false; } + const QString sCurrentGaugeInputSha1 = + pWellData->calculateGaugeInputSha1(); + if(sCurrentGaugeInputSha1.isEmpty() || + sCurrentGaugeInputSha1 != oWellInput.m_sGaugeInputSha1 || + sCurrentGaugeInputSha1 != pWellData->getHistoryGaugeInputSha1()) { + return false; + } + // 正式结果所需井值在主线程一次冻结;后台只读取本结构。 oWellInput.m_sWellInstanceId = pWellData->getWellInstanceId(); oWellInput.m_sWellName = pWellData->getWellName(); @@ -1972,6 +1980,7 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate() oWell.m_eWellMode = oWellInput.m_bRateControlled ? NM_CaseWell_RateControlled : NM_CaseWell_Observation; + oWell.m_sGaugeInputSha1 = oWellInput.m_sGaugeInputSha1; oWell.m_oLocation = oWellInput.m_oLocation; oWell.m_bHasPerforation = oWellInput.m_bHasPerforation; oWell.m_bHasSkin = oWellInput.m_bHasPerforation; diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp index c3baa86..89b6cd0 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp @@ -209,64 +209,21 @@ void fillScenePseudoPressureTable(nmDataBinaryTools::NM_PEBI_SCENE& scene, // 把当前所选流量记录整理成求解器的公共段时长和三相数组。 // 单相记录缺少的相只补同轴零值;多相记录的三相数值不做业务修正。 bool captureWellFlowSchedule( - nmDataWellBase* pWellData, + const nmWellGaugeInputData& oGaugeInput, nmPebiWellInputSnapshot& oWellInput) { oWellInput.m_vecFlowDurations.clear(); oWellInput.m_vecOilRates.clear(); oWellInput.m_vecGasRates.clear(); oWellInput.m_vecWaterRates.clear(); - if(pWellData == nullptr) { + if(!oGaugeInput.hasAlignedFlowSchedule()) { return false; } - - const QVector vecOilPoints = - pWellData->getFlowSegmentPoints(PHASE_Oil); - const QVector vecGasPoints = - pWellData->getFlowSegmentPoints(PHASE_Gas); - const QVector vecWaterPoints = - pWellData->getFlowSegmentPoints(PHASE_Water); - - // 公共段时长取自当前至少一个启用相;井别只影响首选顺序,不裁剪相数据。 - oWellInput.m_eFlowSchedulePhase = - pWellData->getFlowSchedulePhase(); - const QVector vecTimePoints = - pWellData->getFlowSegmentPoints(); - if(oWellInput.m_eFlowSchedulePhase == PHASE_UNKNOWN || - vecTimePoints.isEmpty()) { - return false; - } - - const int nSegmentCount = vecTimePoints.size(); - // GaugeDataEx2 本身只有一列公共段时长。这里仅防御启用相数组长度异常, - // 未启用相即使保留原始数据,也不会参与本次快照组装。 - if((pWellData->getUseOilRate() && !vecOilPoints.isEmpty() && - vecOilPoints.size() != nSegmentCount) || - (pWellData->getUseGasRate() && !vecGasPoints.isEmpty() && - vecGasPoints.size() != nSegmentCount) || - (pWellData->getUseWaterRate() && !vecWaterPoints.isEmpty() && - vecWaterPoints.size() != nSegmentCount)) { - return false; - } - - oWellInput.m_vecFlowDurations.reserve(nSegmentCount); - oWellInput.m_vecOilRates.reserve(nSegmentCount); - oWellInput.m_vecGasRates.reserve(nSegmentCount); - oWellInput.m_vecWaterRates.reserve(nSegmentCount); - // 求解器要求 qo/qg/qw 与 Rate.t 等长。用户设为“无”的相用同轴零值占位, - // 只关闭该相源汇项,不改变其他相的段数和时间位置。 - for(int nIndex = 0; nIndex < nSegmentCount; ++nIndex) { - oWellInput.m_vecFlowDurations.append(vecTimePoints[nIndex].x()); - oWellInput.m_vecOilRates.append( - !pWellData->getUseOilRate() || vecOilPoints.isEmpty() - ? 0.0 : vecOilPoints[nIndex].y()); - oWellInput.m_vecGasRates.append( - !pWellData->getUseGasRate() || vecGasPoints.isEmpty() - ? 0.0 : vecGasPoints[nIndex].y()); - oWellInput.m_vecWaterRates.append( - !pWellData->getUseWaterRate() || vecWaterPoints.isEmpty() - ? 0.0 : vecWaterPoints[nIndex].y()); - } + oWellInput.m_eFlowSchedulePhase = oGaugeInput.eFlowSchedulePhase; + oWellInput.m_vecFlowDurations = oGaugeInput.vecFlowDurations; + oWellInput.m_vecOilRates = oGaugeInput.vecOilRates; + oWellInput.m_vecGasRates = oGaugeInput.vecGasRates; + oWellInput.m_vecWaterRates = oGaugeInput.vecWaterRates; return true; } @@ -1170,7 +1127,11 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot( oWellInput.m_sWellCode = pWellData->getWellCode(); oWellInput.m_sWellName = pWellData->getWellName(); oWellInput.m_eWellCategory = pWellData->getWellCategory(); - oWellInput.m_nFlowSectionIndex = pWellData->getIndexF(); + nmWellGaugeInputData oGaugeInput; + pWellData->buildGaugeInputData(oGaugeInput); + oWellInput.m_sGaugeInputSha1 = + nmDataWellBase::calculateGaugeInputSha1(oGaugeInput); + oWellInput.m_nFlowSectionIndex = oGaugeInput.nFlowSectionIndex; oWellInput.m_oLocation = QPointF( pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble()); @@ -1181,9 +1142,10 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot( : 0.0; oWellInput.m_bRateControlled = nWellMode == static_cast(NM_CaseWell_RateControlled); - if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) || + if(oWellInput.m_sGaugeInputSha1.isEmpty() || + !nmIsValidWellCategory(oWellInput.m_eWellCategory) || (oWellInput.m_bRateControlled && - (!captureWellFlowSchedule(pWellData, oWellInput) || + (!captureWellFlowSchedule(oGaugeInput, oWellInput) || oWellInput.m_nFlowSectionIndex < 1 || oWellInput.m_nFlowSectionIndex > oWellInput.m_vecFlowDurations.size()))) { @@ -1460,7 +1422,11 @@ bool nmCalculationPebiGrid::captureInputSnapshot( oWellInput.m_bRealWell = true; oWellInput.m_sWellName = pWellData->getWellName(); oWellInput.m_eWellCategory = pWellData->getWellCategory(); - oWellInput.m_nFlowSectionIndex = pWellData->getIndexF(); + nmWellGaugeInputData oGaugeInput; + pWellData->buildGaugeInputData(oGaugeInput); + oWellInput.m_sGaugeInputSha1 = + nmDataWellBase::calculateGaugeInputSha1(oGaugeInput); + oWellInput.m_nFlowSectionIndex = oGaugeInput.nFlowSectionIndex; oWellInput.m_oLocation = QPointF( pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble()); @@ -1473,15 +1439,16 @@ bool nmCalculationPebiGrid::captureInputSnapshot( oWellInput.m_bRateControlled = mapWellModes.value(oWellRef.m_sWellCode) == static_cast(NM_CaseWell_RateControlled); - if(oSnapshot.m_bRequireSolverInput && - !nmIsValidWellCategory(oWellInput.m_eWellCategory)) { + if(oWellInput.m_sGaugeInputSha1.isEmpty() || + (oSnapshot.m_bRequireSolverInput && + !nmIsValidWellCategory(oWellInput.m_eWellCategory))) { qWarning() << "Solver well has an invalid category:" << oWellRef.m_sWellCode; return false; } if(oSnapshot.m_bRequireSolverInput && oWellInput.m_bRateControlled && - (!captureWellFlowSchedule(pWellData, oWellInput) || + (!captureWellFlowSchedule(oGaugeInput, oWellInput) || oWellInput.m_nFlowSectionIndex < 1 || oWellInput.m_nFlowSectionIndex > oWellInput.m_vecFlowDurations.size())) { diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index d90152d..556ebbd 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -940,16 +940,118 @@ bool readNumericalFileReference( 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()) + !oJson.HasMember("Sha1") || !oJson["Sha1"].IsString() || + oJson["Sha1"].GetStringLength() != 40u) { return false; } - oReference.m_sRelativePath = QString::fromUtf8(oJson["Path"].GetString()); + oReference.m_sRelativePath = QString::fromUtf8( + oJson["Path"].GetString(), + static_cast(oJson["Path"].GetStringLength())); oReference.m_nLength = oJson["Length"].GetUint64(); - oReference.m_sSha1 = QString::fromLatin1(oJson["Sha1"].GetString()); + oReference.m_sSha1 = QString::fromLatin1( + oJson["Sha1"].GetString(), + static_cast(oJson["Sha1"].GetStringLength())); return oReference.isValid(); } +bool addUniquePayloadPath( + const nmNumericalFileReference& oReference, + QSet& setPathKeys) +{ + QString sNormalizedPath; + if(!nmNumericalResultPersistence::normalizeRelativePath( + oReference.m_sRelativePath, sNormalizedPath) || + sNormalizedPath != oReference.m_sRelativePath) + { + return false; + } + const QString sPathKey = sNormalizedPath.toLower(); + if(setPathKeys.contains(sPathKey)) + { + return false; + } + setPathKeys.insert(sPathKey); + return true; +} + +bool hasUniqueUsablePressureRecord( + const QVector& vecRecords, + const QString& sGaugeCode) +{ + int nMatchCount = 0; + bool bUsable = false; + for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) + { + if(vecRecords[nIndex].sGaugeCode == sGaugeCode) + { + ++nMatchCount; + bUsable = vecRecords[nIndex].eStatus == NM_GaugeRecord_Usable; + } + } + return nMatchCount == 1 && bUsable; +} + +const nmFlowGaugeRecord* findUniqueUsableFlowRecord( + const QVector& vecRecords, + const QString& sGaugeCode) +{ + const nmFlowGaugeRecord* pMatch = NULL; + int nMatchCount = 0; + for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) + { + if(vecRecords[nIndex].sGaugeCode == sGaugeCode) + { + pMatch = &vecRecords[nIndex]; + ++nMatchCount; + } + } + return nMatchCount == 1 && pMatch != NULL && + pMatch->eStatus == NM_GaugeRecord_Usable ? pMatch : NULL; +} + +bool isWellGaugeSelectionValid( + const nmDataWellBase* pWell, + bool bRateControlled) +{ + if(pWell == NULL || pWell->getIndexF() < 0) + { + return false; + } + const QString sPressureCode = pWell->getSelectedPressureGaugeCode(); + const QVector vecPressureRecords = + pWell->getPressureRecords(); + if(!sPressureCode.isEmpty() && + !hasUniqueUsablePressureRecord(vecPressureRecords, sPressureCode)) + { + return false; + } + + const QString sFlowCode = pWell->getSelectedFlowGaugeCode(); + const QVector vecFlowRecords = pWell->getFlowRecords(); + const nmFlowGaugeRecord* pFlowRecord = sFlowCode.isEmpty() ? NULL : + findUniqueUsableFlowRecord(vecFlowRecords, sFlowCode); + if((sFlowCode.isEmpty() && pWell->getIndexF() != 0) || + (!sFlowCode.isEmpty() && pFlowRecord == NULL) || + (bRateControlled && sFlowCode.isEmpty())) + { + return false; + } + const int nRawSegmentCount = pFlowRecord == NULL ? 0 : + nmDataWellBase::getRawFlowRecordSegmentCount(*pFlowRecord); + if(!bRateControlled) + { + return pWell->getIndexF() == 0 || + pWell->getIndexF() <= nRawSegmentCount; + } + + nmWellGaugeInputData oGaugeInput; + pWell->buildGaugeInputData(oGaugeInput); + return oGaugeInput.hasAlignedFlowSchedule() && + pWell->getIndexF() >= 1 && + pWell->getIndexF() <= oGaugeInput.vecFlowDurations.size(); +} + bool readV3WindowHeader( const QString& sMainJsonPath, nmNumericalWindowPayloadReferences& oReferences, @@ -963,7 +1065,7 @@ bool readV3WindowHeader( if(!oInfo.isFile() || oInfo.size() <= 0 || oInfo.size() > 64ll * 1024ll * 1024ll) { - if(pError != NULL) *pError = "Numerical v3 main JSON is missing or too large."; + if(pError != NULL) *pError = "Numerical v5 main JSON is missing or too large."; return false; } rapidjson::Document oDocument; @@ -982,6 +1084,8 @@ bool readV3WindowHeader( { *pError = nVersion == 2 ? "Numerical project version 2 is not supported." + : nVersion == 4 + ? "Numerical project version 4 is not supported." : "Numerical project version is not supported."; } return false; @@ -993,9 +1097,49 @@ bool readV3WindowHeader( !oDocument.HasMember("PayloadFiles") || !oDocument["PayloadFiles"].IsObject()) { - if(pError != NULL) *pError = "Numerical v3 main JSON header is incomplete."; + if(pError != NULL) *pError = "Numerical v5 main JSON header is incomplete."; + return false; + } + if(!oDocument.HasMember("Wells") || + !oDocument["Wells"].IsArray() || + oDocument["Wells"].Size() > 10000u) + { + if(pError != NULL) *pError = "Numerical v5 well directory is invalid."; return false; } + QSet setSavedWellInstanceIds; + const rapidjson::Value& oWells = oDocument["Wells"]; + for(rapidjson::SizeType nIndex = 0; nIndex < oWells.Size(); ++nIndex) + { + const rapidjson::Value& oWell = oWells[nIndex]; + if(!oWell.IsObject() || + !oWell.HasMember("WellInstanceId") || + !oWell["WellInstanceId"].IsString() || + !oWell.HasMember("SelectedPressureGaugeCode") || + !oWell["SelectedPressureGaugeCode"].IsString() || + !oWell.HasMember("SelectedFlowGaugeCode") || + !oWell["SelectedFlowGaugeCode"].IsString() || + !oWell.HasMember("UseOilRate") || !oWell["UseOilRate"].IsBool() || + !oWell.HasMember("UseGasRate") || !oWell["UseGasRate"].IsBool() || + !oWell.HasMember("UseWaterRate") || !oWell["UseWaterRate"].IsBool() || + !oWell.HasMember("IndexFlow") || !oWell["IndexFlow"].IsInt() || + oWell["IndexFlow"].GetInt() < 0) + { + if(pError != NULL) *pError = "Numerical v5 well Gauge fields are invalid."; + return false; + } + const QString sWellInstanceId = QString::fromUtf8( + oWell["WellInstanceId"].GetString()); + const QUuid oUuid(sWellInstanceId); + if(oUuid.isNull() || + oUuid.toString().remove('{').remove('}') != sWellInstanceId || + setSavedWellInstanceIds.contains(sWellInstanceId)) + { + if(pError != NULL) *pError = "Numerical v5 well UUID is invalid or duplicated."; + return false; + } + setSavedWellInstanceIds.insert(sWellInstanceId); + } const rapidjson::Value& oRevisions = oDocument["InputRevisions"]; if(!oRevisions.HasMember("GridInputRevision") || !oRevisions["GridInputRevision"].IsUint64() || @@ -1024,6 +1168,9 @@ bool readV3WindowHeader( !oPayload.HasMember("WellHistories") || !oPayload["WellHistories"].IsArray() || oPayload["WellHistories"].Size() > 10000u || + !oPayload.HasMember("WellGaugeRecords") || + !oPayload["WellGaugeRecords"].IsArray() || + oPayload["WellGaugeRecords"].Size() > 10000u || !oPayload.HasMember("Snapshot")) { if(pError != NULL) *pError = "Numerical payload reference list is invalid."; @@ -1040,7 +1187,11 @@ bool readV3WindowHeader( return false; } oReferences.m_bHasCurrentGrid = true; - setPaths.insert(oReferences.m_oCurrentGrid.m_sRelativePath); + if(!addUniquePayloadPath(oReferences.m_oCurrentGrid, setPaths)) + { + if(pError != NULL) *pError = "Current grid path is duplicated."; + return false; + } } const rapidjson::Value& oHistories = oPayload["WellHistories"]; for(rapidjson::SizeType nIndex = 0; nIndex < oHistories.Size(); ++nIndex) @@ -1059,24 +1210,88 @@ bool readV3WindowHeader( oJson["WellInstanceId"].GetString()); if(sWellInstanceId.isEmpty() || oReferences.m_mapWellHistories.contains(sWellInstanceId) || - setPaths.contains(oReference.m_sRelativePath)) + !addUniquePayloadPath(oReference, setPaths)) { if(pError != NULL) *pError = "Well history references are duplicated."; return false; } oReferences.m_mapWellHistories.insert(sWellInstanceId, oReference); - setPaths.insert(oReference.m_sRelativePath); + } + + const rapidjson::Value& oGaugeRecords = oPayload["WellGaugeRecords"]; + for(rapidjson::SizeType nIndex = 0; nIndex < oGaugeRecords.Size(); ++nIndex) + { + const rapidjson::Value& oJson = oGaugeRecords[nIndex]; + nmNumericalFileReference oReference; + if(!oJson.IsObject() || + !oJson.HasMember("WellInstanceId") || + !oJson["WellInstanceId"].IsString() || + !readNumericalFileReference(oJson, "File", oReference)) + { + if(pError != NULL) *pError = "Well Gauge reference is invalid."; + return false; + } + const QString sWellInstanceId = QString::fromUtf8( + oJson["WellInstanceId"].GetString()); + if(sWellInstanceId.isEmpty() || + oReferences.m_mapWellGaugeRecords.contains(sWellInstanceId) || + !addUniquePayloadPath(oReference, setPaths)) + { + if(pError != NULL) *pError = "Well Gauge references are duplicated."; + return false; + } + oReferences.m_mapWellGaugeRecords.insert(sWellInstanceId, oReference); + } + + QSet setHistoryWellIds; + QMap::const_iterator oHistoryIt = + oReferences.m_mapWellHistories.constBegin(); + for(; oHistoryIt != oReferences.m_mapWellHistories.constEnd(); ++oHistoryIt) + { + setHistoryWellIds.insert(oHistoryIt.key()); + } + QSet setGaugeWellIds; + QMap::const_iterator oGaugeIt = + oReferences.m_mapWellGaugeRecords.constBegin(); + for(; oGaugeIt != oReferences.m_mapWellGaugeRecords.constEnd(); ++oGaugeIt) + { + setGaugeWellIds.insert(oGaugeIt.key()); + } + if(setHistoryWellIds != setSavedWellInstanceIds || + setGaugeWellIds != setSavedWellInstanceIds) + { + if(pError != NULL) *pError = + "Numerical v5 well payload UUID sets are incomplete."; + return false; } if(!oPayload["Snapshot"].IsNull()) { if(!readNumericalFileReference(oPayload, "Snapshot", oReferences.m_oSnapshot) || - setPaths.contains(oReferences.m_oSnapshot.m_sRelativePath)) + !addUniquePayloadPath(oReferences.m_oSnapshot, setPaths)) { if(pError != NULL) *pError = "Snapshot reference is invalid or duplicated."; return false; } oReferences.m_bHasSnapshot = true; + QStringList listSnapshotPaths; + if(!nmPebiResultSnapshotSerializer::getPayloadRelativePaths( + QFileInfo(sMainJsonPath).absolutePath(), + oReferences.m_oSnapshot, listSnapshotPaths, pError)) + { + return false; + } + for(int nIndex = 0; nIndex < listSnapshotPaths.size(); ++nIndex) + { + nmNumericalFileReference oPathOnly; + oPathOnly.m_sRelativePath = listSnapshotPaths[nIndex]; + if(!addUniquePayloadPath(oPathOnly, setPaths)) + { + if(pError != NULL) *pError = + "Snapshot payload path is duplicated."; + return false; + } + } } return true; } @@ -1844,16 +2059,7 @@ void nmDataAnalyzeManager::initCurWellData() sCurrentFlowGaugeCode, nCurrentFlowIndex); - QVector> vvecHistoryPressureData; //压力历史数据 - QVector> vvecHistoryLogData; // 历史双对数曲线数据 - QVector> vvecHistorySemiLogData; // 历史半对数曲线数据 - - this->calculationLogData(pVerticalWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - // 使用setter方法存储历史数据到井对象中 - pVerticalWell->setHistoryPressure(vvecHistoryPressureData); - pVerticalWell->setHistoryLogLog(vvecHistoryLogData); - pVerticalWell->setHistorySemiLog(vvecHistorySemiLogData); + updateWellHistoryData(pVerticalWell); // 设置为当前查看的井 this->setCurWellData(pVerticalWell); @@ -1887,16 +2093,7 @@ void nmDataAnalyzeManager::initCurWellData() sCurrentFlowGaugeCode, nCurrentFlowIndex); - QVector> vvecHistoryPressureData; //压力历史数据 - QVector> vvecHistoryLogData; // 历史双对数曲线数据 - QVector> vvecHistorySemiLogData; // 历史半对数曲线数据 - - this->calculationLogData(pVFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - // 使用setter方法存储历史数据到井对象中 - pVFracturedWell->setHistoryPressure(vvecHistoryPressureData); - pVFracturedWell->setHistoryLogLog(vvecHistoryLogData); - pVFracturedWell->setHistorySemiLog(vvecHistorySemiLogData); + updateWellHistoryData(pVFracturedWell); // 更新裂缝位置信息 pVFracturedWell->setFracs(); @@ -1933,16 +2130,7 @@ void nmDataAnalyzeManager::initCurWellData() sCurrentFlowGaugeCode, nCurrentFlowIndex); - QVector> vvecHistoryPressureData; //压力历史数据 - QVector> vvecHistoryLogData; // 历史双对数曲线数据 - QVector> vvecHistorySemiLogData; // 历史半对数曲线数据 - - this->calculationLogData(pHFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - //使用setter方法存储历史数据到井对象中 - pHFracturedWell->setHistoryPressure(vvecHistoryPressureData); - pHFracturedWell->setHistoryLogLog(vvecHistoryLogData); - pHFracturedWell->setHistorySemiLog(vvecHistorySemiLogData); + updateWellHistoryData(pHFracturedWell); // 计算裂缝数据 pHFracturedWell->setFracs(); @@ -1965,28 +2153,24 @@ void nmDataAnalyzeManager::initCurWellData() } } -void nmDataAnalyzeManager::calculationLogData( - nmDataWellBase* pWellData, - QVector>& vvecHistoryData, - QVector>& vvecLogPreData, - QVector>& vvecSemiLogPreData) +static void calculateLogDataFromGaugeInput( + const nmWellGaugeInputData& oGaugeInput, + 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(); + const QVector& vecPressure = oGaugeInput.vecPressurePoints; std::vector wellPressureData; // 填充 wellPressureData 和 vvecHistoryData @@ -2004,24 +2188,34 @@ void nmDataAnalyzeManager::calculationLogData( } // 流动段索引采用一基;这里只使用去除可选 (0,0) 占位点后的真实段。 - const QVector vecTimeQ = pWellData->getFlowSegmentPoints(); - if(vecPressure.isEmpty() || vecTimeQ.isEmpty() || - !pWellData->hasValidFlowSectionIndex()) { - return; - } + const QVector* pReferenceRates = nullptr; + if(oGaugeInput.eFlowSchedulePhase == PHASE_Oil) { + pReferenceRates = &oGaugeInput.vecOilRates; + } else if(oGaugeInput.eFlowSchedulePhase == PHASE_Gas) { + pReferenceRates = &oGaugeInput.vecGasRates; + } else if(oGaugeInput.eFlowSchedulePhase == PHASE_Water) { + pReferenceRates = &oGaugeInput.vecWaterRates; + } + if(vecPressure.isEmpty() || oGaugeInput.vecFlowDurations.isEmpty() || + pReferenceRates == nullptr || + pReferenceRates->size() != oGaugeInput.vecFlowDurations.size() || + oGaugeInput.nFlowSectionIndex < 1 || + oGaugeInput.nFlowSectionIndex > oGaugeInput.vecFlowDurations.size()) { + return; + } - const int nTimeNumQ = vecTimeQ.size(); + const int nTimeNumQ = oGaugeInput.vecFlowDurations.size(); std::vector timeQ(nTimeNumQ); std::vector q(nTimeNumQ); for(int i = 0; i < nTimeNumQ; ++i) { - timeQ[i] = vecTimeQ[i].x(); - q[i] = vecTimeQ[i].y(); + timeQ[i] = oGaugeInput.vecFlowDurations[i]; + q[i] = (*pReferenceRates)[i]; } // 调用 DLL 计算双对数曲线 std::vector logPre; - int iSectionFlowIndex = pWellData->getIndexF(); + int iSectionFlowIndex = oGaugeInput.nFlowSectionIndex; HMODULE hMod_solver = LoadLibrary(L"singlePhaseSolverDll.dll"); @@ -2798,6 +2992,47 @@ bool nmDataAnalyzeManager::resetFromDiagnostic() } } +void nmDataAnalyzeManager::calculationLogData( + nmDataWellBase* pWellData, + QVector>& vvecHistoryData, + QVector>& vvecLogPreData, + QVector>& vvecSemiLogPreData) +{ + if(pWellData == nullptr) { + vvecHistoryData.clear(); + vvecLogPreData.clear(); + vvecSemiLogPreData.clear(); + return; + } + nmWellGaugeInputData oGaugeInput; + pWellData->buildGaugeInputData(oGaugeInput); + calculateLogDataFromGaugeInput(oGaugeInput, vvecHistoryData, + vvecLogPreData, vvecSemiLogPreData); +} + +bool nmDataAnalyzeManager::updateWellHistoryData( + nmDataWellBase* pWellData) +{ + if(pWellData == nullptr) { + return false; + } + nmWellGaugeInputData oGaugeInput; + pWellData->buildGaugeInputData(oGaugeInput); + const QString sGaugeInputSha1 = + nmDataWellBase::calculateGaugeInputSha1(oGaugeInput); + if(sGaugeInputSha1.isEmpty()) { + return false; + } + QVector > vecHistoryPressure; + QVector > vecHistoryLogLog; + QVector > vecHistorySemiLog; + calculateLogDataFromGaugeInput(oGaugeInput, vecHistoryPressure, + vecHistoryLogLog, vecHistorySemiLog); + pWellData->setHistoryData(vecHistoryPressure, vecHistoryLogLog, + vecHistorySemiLog, sGaugeInputSha1); + return true; +} + bool nmDataAnalyzeManager::resetFromAnalytical() { // 获取当前井和储层数据 @@ -4541,7 +4776,7 @@ void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells() continue; } const bool bBelongsToSnapshot = - !m_pPebiResultSnapshot.isNull() && + isPebiResultSnapshotCurrent() && m_pPebiResultSnapshot->findWell( pWell->getWellInstanceId()) != NULL; pWell->bindPebiResultSnapshot(bBelongsToSnapshot @@ -4931,7 +5166,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) quint64 nSavedResultInputRevision = 0; if(!doc.HasMember("InputRevisions") || !doc["InputRevisions"].IsObject()) { - qWarning() << "Numerical v4 project has no input revisions:" << filePath; + qWarning() << "Numerical v5 project has no input revisions:" << filePath; return false; } const rapidjson::Value& oRevisionJson = doc["InputRevisions"]; @@ -4941,7 +5176,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) !oRevisionJson["BuiltGridRevision"].IsUint64() || !oRevisionJson.HasMember("ResultInputRevision") || !oRevisionJson["ResultInputRevision"].IsUint64()) { - qWarning() << "Numerical v4 project contains invalid input revisions:" + qWarning() << "Numerical v5 project contains invalid input revisions:" << filePath; return false; } @@ -4954,7 +5189,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) if(nSavedGridInputRevision == 0 || nSavedResultInputRevision == 0 || (nSavedBuiltGridRevision != 0 && nSavedBuiltGridRevision != nSavedGridInputRevision)) { - qWarning() << "Numerical v4 project input revision values are invalid:" + qWarning() << "Numerical v5 project input revision values are invalid:" << filePath; return false; } @@ -4966,7 +5201,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) QVector vecSavedIncludedWells; QVector vecSavedSolverOrder; - // 第一步:v4 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。 + // 第一步:v5 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。 if(!doc.HasMember("NumericalAnalysisCase") || !doc["NumericalAnalysisCase"].IsObject() || !doc.HasMember("Wells") || @@ -5024,7 +5259,20 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) !oWellJson.HasMember("WellType") || !oWellJson["WellType"].IsInt() || !oWellJson.HasMember("WellCategory") || - !oWellJson["WellCategory"].IsInt()) { + !oWellJson["WellCategory"].IsInt() || + !oWellJson.HasMember("SelectedPressureGaugeCode") || + !oWellJson["SelectedPressureGaugeCode"].IsString() || + !oWellJson.HasMember("SelectedFlowGaugeCode") || + !oWellJson["SelectedFlowGaugeCode"].IsString() || + !oWellJson.HasMember("UseOilRate") || + !oWellJson["UseOilRate"].IsBool() || + !oWellJson.HasMember("UseGasRate") || + !oWellJson["UseGasRate"].IsBool() || + !oWellJson.HasMember("UseWaterRate") || + !oWellJson["UseWaterRate"].IsBool() || + !oWellJson.HasMember("IndexFlow") || + !oWellJson["IndexFlow"].IsInt() || + oWellJson["IndexFlow"].GetInt() < 0) { qWarning() << "Numerical project contains an invalid well entry:" << filePath; return false; } @@ -5588,7 +5836,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) nSavedGridInputRevision, nSavedBuiltGridRevision, nSavedResultInputRevision)) { - qWarning() << "Failed to restore numerical v4 input revisions:" + qWarning() << "Failed to restore numerical v5 input revisions:" << filePath; return false; } @@ -5602,7 +5850,7 @@ bool nmDataAnalyzeManager::WriteProjectData( const nmNumericalWindowPayloadReferences* pPayloadReferences) { if(pPayloadReferences == NULL) { - qWarning() << "Numerical v4 project requires explicit payload references."; + qWarning() << "Numerical v5 project requires explicit payload references."; return false; } // 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。 @@ -5629,6 +5877,31 @@ bool nmDataAnalyzeManager::WriteProjectData( setWellInstanceIds.insert(sWellInstanceId); mapWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType()); } + if(pPayloadReferences->m_mapWellHistories.size() != setWellInstanceIds.size() || + pPayloadReferences->m_mapWellGaugeRecords.size() != setWellInstanceIds.size()) { + qWarning() << "Cannot save numerical project: incomplete well payload references."; + return false; + } + QSet::const_iterator oWellIdIt = setWellInstanceIds.constBegin(); + for(; oWellIdIt != setWellInstanceIds.constEnd(); ++oWellIdIt) { + if(!pPayloadReferences->m_mapWellHistories.contains(*oWellIdIt) || + !pPayloadReferences->m_mapWellGaugeRecords.contains(*oWellIdIt)) { + qWarning() << "Cannot save numerical project: missing well payload UUID."; + return false; + } + } + if((m_pPebiResultSnapshot.isNull() && + !m_sCurrentResultWellInstanceId.isEmpty()) || + (!m_pPebiResultSnapshot.isNull() && + ((m_pPebiResultSnapshot->getDisplayWellInstanceIds().isEmpty() && + !m_sCurrentResultWellInstanceId.isEmpty()) || + (!m_pPebiResultSnapshot->getDisplayWellInstanceIds().isEmpty() && + (m_sCurrentResultWellInstanceId.isEmpty() || + !m_pPebiResultSnapshot->isDisplayWell( + m_sCurrentResultWellInstanceId)))))) { + qWarning() << "Cannot save numerical project: invalid current result well."; + return false; + } const QString sPrimaryWellCode = getPrimaryWellCode(); if(!setWellCodes.contains(sPrimaryWellCode) || @@ -5989,6 +6262,19 @@ bool nmDataAnalyzeManager::WriteProjectData( oHistoryJson.PushBack(oWellJson, allocator); } oPayloadJson.AddMember("WellHistories", oHistoryJson, allocator); + rapidjson::Value oGaugeJson(rapidjson::kArrayType); + QMap::const_iterator oGaugeIt = + pPayloadReferences->m_mapWellGaugeRecords.constBegin(); + for(; oGaugeIt != pPayloadReferences->m_mapWellGaugeRecords.constEnd(); + ++oGaugeIt) { + rapidjson::Value oWellJson(rapidjson::kObjectType); + oWellJson.AddMember("WellInstanceId", numericalJsonString( + oGaugeIt.key(), allocator), allocator); + addNumericalFileReference(oWellJson, "File", + oGaugeIt.value(), allocator); + oGaugeJson.PushBack(oWellJson, allocator); + } + oPayloadJson.AddMember("WellGaugeRecords", oGaugeJson, allocator); if(pPayloadReferences->m_bHasSnapshot) { addNumericalFileReference(oPayloadJson, "Snapshot", pPayloadReferences->m_oSnapshot, allocator); @@ -6020,7 +6306,7 @@ bool nmDataAnalyzeManager::saveNmResultV3( try { if(sWindowDirectory.isEmpty() || !ensureDirectoryExists(sWindowDirectory)) { - if(pError != NULL) *pError = "Cannot create numerical v3 window directory."; + if(pError != NULL) *pError = "Cannot create numerical v5 window directory."; return false; } @@ -6046,9 +6332,33 @@ bool nmDataAnalyzeManager::saveNmResultV3( const QString sHistoryDirectory = QDir(sWindowDirectory).filePath( "WellHistory"); if(!ensureDirectoryExists(sHistoryDirectory)) { - if(pError != NULL) *pError = "Cannot create v3 well history directory."; + if(pError != NULL) *pError = "Cannot create v5 well history directory."; return false; } + const QString sGaugeDirectory = QDir(sWindowDirectory).filePath( + "WellGaugeRecords"); + if(!ensureDirectoryExists(sGaugeDirectory)) { + if(pError != NULL) *pError = + "Cannot create v5 well Gauge directory."; + return false; + } + QSet setRateControlledWellCodes; + if(m_oNumericalAnalysisCase.getPrimaryWellMode() == + NM_CaseWell_RateControlled) { + setRateControlledWellCodes.insert( + m_oNumericalAnalysisCase.getPrimaryWellCode()); + } + if(m_oNumericalAnalysisCase.getIncludeOtherWells()) { + const QVector vecIncludedWells = + m_oNumericalAnalysisCase.getIncludedWells(); + for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) { + if(vecIncludedWells[nIndex].m_eMode == + NM_CaseWell_RateControlled) { + setRateControlledWellCodes.insert( + vecIncludedWells[nIndex].m_sWellCode); + } + } + } QSet setWellInstanceIds; for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = m_vWellData[nIndex]; @@ -6058,7 +6368,23 @@ bool nmDataAnalyzeManager::saveNmResultV3( return false; } setWellInstanceIds.insert(pWell->getWellInstanceId()); + if(!isWellGaugeSelectionValid(pWell, + setRateControlledWellCodes.contains( + pWell->getWellCode()))) { + if(pError != NULL) *pError = + "Well Gauge selection or flow index is invalid."; + return false; + } + const QString sGaugeInputSha1 = + pWell->calculateGaugeInputSha1(); + if(sGaugeInputSha1.isEmpty() || + pWell->getHistoryGaugeInputSha1() != sGaugeInputSha1) { + if(pError != NULL) *pError = + "Well history does not match its current Gauge input."; + return false; + } nmNumericalWellHistoryData oHistory; + oHistory.m_sGaugeInputSha1 = sGaugeInputSha1; oHistory.m_vecPressure = pWell->getHistoryPressure(); oHistory.m_vecLogLog = pWell->getHistoryLogLog(); oHistory.m_vecSemiLog = pWell->getHistorySemiLog(); @@ -6075,6 +6401,25 @@ bool nmDataAnalyzeManager::saveNmResultV3( } oReferences.m_mapWellHistories.insert( pWell->getWellInstanceId(), oReference); + + nmNumericalWellGaugeData oGaugeData; + oGaugeData.m_sWellInstanceId = pWell->getWellInstanceId(); + oGaugeData.m_vecPressureRecords = pWell->getPressureRecords(); + oGaugeData.m_vecFlowRecords = pWell->getFlowRecords(); + const QString sGaugeRelativePath = + QString("WellGaugeRecords/%1.bin") + .arg(pWell->getWellInstanceId()); + const QString sGaugePath = QDir(sWindowDirectory).filePath( + sGaugeRelativePath); + nmNumericalFileReference oGaugeReference; + if(!nmNumericalResultPersistence::writeWellGaugeData( + sGaugePath, oGaugeData, pError) || + !nmNumericalResultPersistence::buildFileReference( + sGaugePath, sGaugeRelativePath, oGaugeReference)) { + return false; + } + oReferences.m_mapWellGaugeRecords.insert( + pWell->getWellInstanceId(), oGaugeReference); } if(!m_pPebiResultSnapshot.isNull()) { @@ -6094,7 +6439,7 @@ bool nmDataAnalyzeManager::saveNmResultV3( sMainJsonPath, "Numerical_Parameters.json", oMainJsonReference)) { if(pError != NULL && pError->isEmpty()) - *pError = "Cannot write numerical v3 main JSON."; + *pError = "Cannot write numerical v5 main JSON."; return false; } @@ -6103,7 +6448,7 @@ bool nmDataAnalyzeManager::saveNmResultV3( sWindowDirectory, sMainJsonPath, pError); } catch(const std::bad_alloc&) { - if(pError != NULL) *pError = "Not enough memory to save numerical v3 result."; + if(pError != NULL) *pError = "Not enough memory to save numerical v5 result."; return false; } } @@ -6124,7 +6469,7 @@ bool nmDataAnalyzeManager::validateNmResultV3Window( QDir::cleanPath(oMainInfo.absolutePath()).compare( sExpectedDirectory, Qt::CaseInsensitive) != 0) { if(pError != NULL) - *pError = "Numerical v3 main JSON leaves its window directory."; + *pError = "Numerical v5 main JSON leaves its window directory."; return false; } @@ -6138,13 +6483,11 @@ bool nmDataAnalyzeManager::validateNmResultV3Window( 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."; + *pError = "Numerical v5 result well is selected without a snapshot."; return false; } @@ -6168,16 +6511,54 @@ bool nmDataAnalyzeManager::validateNmResultV3Window( } } - if(oReferences.m_bHasSnapshot && - !nmPebiResultSnapshotSerializer::validate( - sWindowDirectory, oReferences.m_oSnapshot, pError)) { - return false; + QMap::const_iterator oGaugeIt = + oReferences.m_mapWellGaugeRecords.constBegin(); + for(; oGaugeIt != oReferences.m_mapWellGaugeRecords.constEnd(); + ++oGaugeIt) { + QString sGaugePath; + nmNumericalWellGaugeData oGaugeData; + if(!nmNumericalResultPersistence::validateFileReference( + sWindowDirectory, oGaugeIt.value(), + &sGaugePath, pError) || + !nmNumericalResultPersistence::readWellGaugeData( + sGaugePath, oGaugeData, pError) || + oGaugeData.m_sWellInstanceId != oGaugeIt.key()) { + if(pError != NULL && pError->isEmpty()) + *pError = "Numerical well Gauge UUID does not match its reference."; + return false; + } + } + + if(oReferences.m_bHasSnapshot) { + QSharedPointer pValidatedSnapshot; + if(!nmPebiResultSnapshotSerializer::load( + sWindowDirectory, oReferences.m_oSnapshot, + pValidatedSnapshot, pError) || + pValidatedSnapshot.isNull() || + pValidatedSnapshot->getGridInputRevision() > nGridInputRevision || + pValidatedSnapshot->getResultInputRevision() > nResultInputRevision) { + if(pError != NULL && pError->isEmpty()) + *pError = "Numerical snapshot has future input revisions."; + return false; + } + const QStringList& listDisplayIds = + pValidatedSnapshot->getDisplayWellInstanceIds(); + if((listDisplayIds.isEmpty() && + !sCurrentResultWellInstanceId.isEmpty()) || + (!listDisplayIds.isEmpty() && + (sCurrentResultWellInstanceId.isEmpty() || + !pValidatedSnapshot->isDisplayWell( + sCurrentResultWellInstanceId)))) { + if(pError != NULL) + *pError = "Numerical current result well selection is invalid."; + return false; + } } return true; } catch(const std::bad_alloc&) { if(pError != NULL) - *pError = "Not enough memory to validate numerical v3 result."; + *pError = "Not enough memory to validate numerical v5 result."; return false; } } @@ -6185,7 +6566,7 @@ bool nmDataAnalyzeManager::validateNmResultV3Window( bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) { try { - // v3 先在独立候选对象上完成全部解析和业务校验。 + // v5 先在独立候选对象上完成全部解析和业务校验。 // 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。 nmDataAnalyzeManager oLoadedManager; oLoadedManager.m_pOwnerFitting = m_pOwnerFitting; @@ -6209,7 +6590,7 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) return true; } catch(const std::bad_alloc&) { - qWarning() << "Not enough memory to stage numerical v3 result."; + qWarning() << "Not enough memory to stage numerical v5 result."; return false; } } @@ -6218,7 +6599,7 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( const QString& sLoadAnalDir) { try { - // 外部框架仍传入旧的“成果根/窗口标识”逻辑路径;v3 只从根清单解析真实代次。 + // 外部框架仍传入旧的“成果根/窗口标识”逻辑路径;v5 只从根清单解析真实代次。 const QFileInfo oLogicalWindowInfo(QDir::cleanPath(sLoadAnalDir)); const QString sWindowId = oLogicalWindowInfo.fileName(); const QString sResultRootDirectory = oLogicalWindowInfo.dir().absolutePath(); @@ -6231,7 +6612,7 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( qWarning() << "Numerical project version 2 is not supported:" << sLegacyJsonPath; } else { - qWarning() << "Numerical v3 root manifest is missing:" + qWarning() << "Numerical v5 root manifest is missing:" << sManifestPath; } return false; @@ -6291,6 +6672,28 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( mapLoadedHistories.insert(oHistoryIt.key(), oHistory); } + QMap mapLoadedGaugeData; + QMap::const_iterator oGaugeIt = + oReferences.m_mapWellGaugeRecords.constBegin(); + for(; oGaugeIt != oReferences.m_mapWellGaugeRecords.constEnd(); + ++oGaugeIt) { + QString sGaugePath; + nmNumericalWellGaugeData oGaugeData; + if(!nmNumericalResultPersistence::validateFileReference( + sWindowDirectory, oGaugeIt.value(), + &sGaugePath, &sError) || + !nmNumericalResultPersistence::readWellGaugeData( + sGaugePath, oGaugeData, &sError) || + oGaugeData.m_sWellInstanceId != oGaugeIt.key()) { + if(sError.isEmpty()) { + sError = "Loaded numerical well Gauge UUID is invalid."; + } + qWarning() << sError; + return false; + } + mapLoadedGaugeData.insert(oGaugeIt.key(), oGaugeData); + } + QSharedPointer pLoadedCandidate; if(oReferences.m_bHasSnapshot && !nmPebiResultSnapshotSerializer::load( @@ -6316,77 +6719,221 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( return false; } - // 实时井历史按 UUID 一一匹配;井名、井编码和数组顺序都不能替代身份。 - if(mapLoadedHistories.size() != m_vWellData.size()) { - qWarning() << "Loaded numerical well history directory is incomplete."; + // 产量控制角色来自保存的分析方案,不能因损坏的流量记录动态降级成观察井。 + QSet setRateControlledWellCodes; + if(m_oNumericalAnalysisCase.getPrimaryWellMode() == + NM_CaseWell_RateControlled) { + setRateControlledWellCodes.insert( + m_oNumericalAnalysisCase.getPrimaryWellCode()); + } + if(m_oNumericalAnalysisCase.getIncludeOtherWells()) { + const QVector vecIncludedWells = + m_oNumericalAnalysisCase.getIncludedWells(); + for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) { + if(vecIncludedWells[nIndex].m_eMode == + NM_CaseWell_RateControlled) { + setRateControlledWellCodes.insert( + vecIncludedWells[nIndex].m_sWellCode); + } + } + } + + // Gauge 和历史均按 UUID 一一恢复;JSON 中的选择状态保持原样。 + if(mapLoadedHistories.size() != m_vWellData.size() || + mapLoadedGaugeData.size() != m_vWellData.size()) { + qWarning() << "Loaded numerical well payload directory is incomplete."; return false; } for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) { nmDataWellBase* pWell = m_vWellData[nIndex]; + const QString sWellInstanceId = pWell == NULL + ? QString() : pWell->getWellInstanceId(); if(pWell == NULL || - !mapLoadedHistories.contains(pWell->getWellInstanceId())) { - qWarning() << "Loaded numerical well UUID has no history payload."; + !mapLoadedHistories.contains(sWellInstanceId) || + !mapLoadedGaugeData.contains(sWellInstanceId)) { + qWarning() << "Loaded numerical well UUID has incomplete payloads."; return false; } + + const nmNumericalWellGaugeData oGaugeData = + mapLoadedGaugeData.take(sWellInstanceId); const nmNumericalWellHistoryData oHistory = - mapLoadedHistories.take(pWell->getWellInstanceId()); - pWell->setHistoryPressure(oHistory.m_vecPressure); - pWell->setHistoryLogLog(oHistory.m_vecLogLog); - pWell->setHistorySemiLog(oHistory.m_vecSemiLog); + mapLoadedHistories.take(sWellInstanceId); + if(oGaugeData.m_sWellInstanceId != sWellInstanceId) { + qWarning() << "Loaded numerical Gauge payload belongs to another well."; + return false; + } + pWell->setPressureRecords(oGaugeData.m_vecPressureRecords); + pWell->setFlowRecords(oGaugeData.m_vecFlowRecords); + + const QString sPressureCode = + pWell->getSelectedPressureGaugeCode(); + if(!sPressureCode.isEmpty() && + !hasUniqueUsablePressureRecord( + oGaugeData.m_vecPressureRecords, sPressureCode)) { + qWarning() << "Loaded selected pressure Gauge Code is dangling:" + << sPressureCode; + return false; + } + + const bool bRateControlled = setRateControlledWellCodes.contains( + pWell->getWellCode()); + const QString sFlowCode = pWell->getSelectedFlowGaugeCode(); + const nmFlowGaugeRecord* pFlowRecord = sFlowCode.isEmpty() + ? NULL : findUniqueUsableFlowRecord( + oGaugeData.m_vecFlowRecords, sFlowCode); + if((sFlowCode.isEmpty() && pWell->getIndexF() != 0) || + (!sFlowCode.isEmpty() && pFlowRecord == NULL) || + (bRateControlled && sFlowCode.isEmpty())) { + qWarning() << "Loaded selected flow Gauge Code is invalid:" + << sFlowCode; + return false; + } + + const int nRawSegmentCount = pFlowRecord == NULL ? 0 : + nmDataWellBase::getRawFlowRecordSegmentCount(*pFlowRecord); + if(!bRateControlled && pWell->getIndexF() > 0 && + pWell->getIndexF() > nRawSegmentCount) { + qWarning() << "Loaded observation well flow index is out of range."; + return false; + } + if(bRateControlled) { + nmWellGaugeInputData oGaugeInput; + pWell->buildGaugeInputData(oGaugeInput); + if(!oGaugeInput.hasAlignedFlowSchedule() || + pWell->getIndexF() < 1 || + pWell->getIndexF() > oGaugeInput.vecFlowDurations.size()) { + qWarning() << "Loaded rate-controlled well has no valid flow schedule:" + << pWell->getWellCode(); + return false; + } + } + + const QString sGaugeInputSha1 = + pWell->calculateGaugeInputSha1(); + if(sGaugeInputSha1.isEmpty() || + oHistory.m_sGaugeInputSha1 != sGaugeInputSha1) { + qWarning() << "Loaded well history Gauge signature does not match."; + return false; + } + pWell->setHistoryData(oHistory.m_vecPressure, + oHistory.m_vecLogLog, oHistory.m_vecSemiLog, + oHistory.m_sGaugeInputSha1); } - if(!mapLoadedHistories.isEmpty()) { - qWarning() << "Numerical v4 project contains unreferenced well histories."; + if(!mapLoadedHistories.isEmpty() || !mapLoadedGaugeData.isEmpty()) { + qWarning() << "Numerical v5 project contains unreferenced well payloads."; return false; } - if(!loadWellPreAndFlow()) { - qWarning() << "Failed to reload numerical well pressure/rate data."; + const QVector vecCurrentSolverOrder = + m_oNumericalAnalysisCase.getSolverWellOrder(); + if(!m_oNumericalAnalysisCase.isGridValid() && + !vecCurrentSolverOrder.isEmpty()) { + qWarning() << "Invalid numerical grid retains a solver well order."; return false; } - // 结果快照保存的是求解时井别;加载时必须与当前工程井保持一致。 - if(!pLoadedCandidate.isNull()) { - for(int nIndex = 0; - nIndex < pLoadedCandidate->getWellCount(); ++nIndex) { - const nmPebiResultWellSnapshot* pSavedWell = - pLoadedCandidate->getWellAt(nIndex); - nmDataWellBase* pLiveWell = pSavedWell == nullptr - ? nullptr : findWellByCode(pSavedWell->m_sWellCode); - if(pSavedWell == nullptr || pLiveWell == nullptr || - pLiveWell->getWellInstanceId() != - pSavedWell->m_sWellInstanceId || - pLiveWell->getWellCategory() != - pSavedWell->m_eWellCategory) { - qWarning() << "Loaded PEBI result well category is stale."; - return false; + if(m_oNumericalAnalysisCase.isGridValid()) { + QSet setEffectiveWellCodes; + const QVector vecEffectiveWells = + getEffectiveCalculationWells(); + for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) { + setEffectiveWellCodes.insert( + vecEffectiveWells[nIndex].m_sWellCode); + } + QSet setOrderedWellCodes; + for(int nIndex = 0; nIndex < vecCurrentSolverOrder.size(); ++nIndex) { + if(vecCurrentSolverOrder[nIndex].m_eEntryKind == + NM_SolverEntry_Well) { + setOrderedWellCodes.insert( + vecCurrentSolverOrder[nIndex].m_sWellCode); } } - } - const QVector vecIncludedWells = - getIncludedCalculationWells(); - for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) { - nmDataWellBase* pWell = findWellByCode( - vecIncludedWells[nIndex].m_sWellCode); - if(!isSupportedNumericalWell(pWell) || - !nmIsValidWellCategory(pWell->getWellCategory()) || - pWell->getFlowSegmentCount() <= 0) { - qWarning() << "Failed to restore included numerical well:" - << vecIncludedWells[nIndex].m_sWellCode; + if(setEffectiveWellCodes != setOrderedWellCodes) { + qWarning() << "Loaded current grid solver well order is inconsistent."; 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()) { + bool bCurrentSnapshot = false; + if(!pLoadedCandidate.isNull()) { + const quint64 nSnapshotGridRevision = + pLoadedCandidate->getGridInputRevision(); + const quint64 nSnapshotResultRevision = + pLoadedCandidate->getResultInputRevision(); + if(nSnapshotGridRevision > nGridInputRevision || + nSnapshotResultRevision > nResultInputRevision) { + qWarning() << "Loaded PEBI snapshot has future input revisions."; + return false; + } + bCurrentSnapshot = nSnapshotGridRevision == nGridInputRevision && + nSnapshotResultRevision == nResultInputRevision; + + const QStringList& listDisplayIds = + pLoadedCandidate->getDisplayWellInstanceIds(); + if((listDisplayIds.isEmpty() && + !sSavedResultWellInstanceId.isEmpty()) || + (!listDisplayIds.isEmpty() && + (sSavedResultWellInstanceId.isEmpty() || + !pLoadedCandidate->isDisplayWell( + sSavedResultWellInstanceId)))) { + qWarning() << "Saved result well UUID is invalid for the snapshot."; + return false; + } + } else if(!sSavedResultWellInstanceId.isEmpty()) { qWarning() << "Numerical project selects a result well without a snapshot."; return false; } + if(bCurrentSnapshot) { + if(!m_oNumericalAnalysisCase.isGridValid()) { + qWarning() << "Current PEBI snapshot has no current input grid."; + return false; + } + const QVector& vecSolverOrder = + vecCurrentSolverOrder; + if(pLoadedCandidate->getSolverSlotCount() != + vecSolverOrder.size()) { + qWarning() << "Current PEBI snapshot solver slots are incomplete."; + return false; + } + for(int nIndex = 0; nIndex < vecSolverOrder.size(); ++nIndex) { + const nmSolverWellRef& oExpected = vecSolverOrder[nIndex]; + const nmPebiResultSolverSlot* pSlot = + pLoadedCandidate->getSolverSlotAt(nIndex); + if(pSlot == NULL || pSlot->m_nSolverIndex != nIndex || + pSlot->m_eEntryKind != oExpected.m_eEntryKind || + pSlot->m_eWellType != oExpected.m_eWellType || + pSlot->m_sWellCode != oExpected.m_sWellCode) { + qWarning() << "Current PEBI snapshot solver slot differs from input."; + return false; + } + if(pSlot->m_eEntryKind == NM_SolverEntry_ManualFracture) { + continue; + } + nmDataWellBase* pLiveWell = findWellByCode( + pSlot->m_sWellCode); + const nmPebiResultWellSnapshot* pSavedWell = pLiveWell == NULL + ? NULL : pLoadedCandidate->findWell( + pLiveWell->getWellInstanceId()); + const NM_CASE_WELL_MODE eExpectedMode = + setRateControlledWellCodes.contains(pSlot->m_sWellCode) + ? NM_CaseWell_RateControlled + : NM_CaseWell_Observation; + if(pLiveWell == NULL || pSavedWell == NULL || + pSlot->m_sWellInstanceId != pLiveWell->getWellInstanceId() || + pSlot->m_eWellCategory != pLiveWell->getWellCategory() || + pSavedWell->m_sWellCode != pLiveWell->getWellCode() || + pSavedWell->m_eWellType != pLiveWell->getWellType() || + pSavedWell->m_eWellCategory != pLiveWell->getWellCategory() || + pSavedWell->m_eWellMode != eExpectedMode || + pSavedWell->m_sGaugeInputSha1 != + pLiveWell->calculateGaugeInputSha1()) { + qWarning() << "Current PEBI snapshot well input does not match."; + return false; + } + } + } + // 所有可能失败的读取和映射已完成,再一次替换当前网格与规范快照。 m_pVtkUnstructuredGrid = pLoadedCurrentGrid; @@ -6395,15 +6942,8 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( } 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; + m_sCurrentResultWellInstanceId = sSavedResultWellInstanceId; pLoadedCandidate.clear(); rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); @@ -6413,7 +6953,7 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace( return true; } catch(const std::bad_alloc&) { - qWarning() << "Not enough memory to load numerical v3 result."; + qWarning() << "Not enough memory to load numerical v5 result."; return false; } @@ -6506,7 +7046,7 @@ bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath) bool nmDataAnalyzeManager::loadWellPreAndFlow() { - // 项目文件只保存当前选择的 Gauge Code 和段索引;曲线数据始终从工程井重新读取。 + // 仅供首次导入后的显式刷新使用;v5 成果加载只恢复成果内的只读副本。 if(zxCurProject == nullptr) { return false; } diff --git a/Src/nmNum/nmData/nmDataWellBase.cpp b/Src/nmNum/nmData/nmDataWellBase.cpp index fe5451e..3bb4896 100644 --- a/Src/nmNum/nmData/nmDataWellBase.cpp +++ b/Src/nmNum/nmData/nmDataWellBase.cpp @@ -9,8 +9,11 @@ #include #include #include +#include #include +#include + /** * @brief 按 Gauge Code 在记录列表中查找可写记录指针;Code 为空或未命中时返回 nullptr。 * @note 阶段一内部使用,不对外暴露;空 Code 统一表示“未选中”,因此不参与查找。 @@ -59,6 +62,76 @@ static bool hasNonZeroFlowSegment(const QVector& vecPoints) return false; } +static void appendGaugeUInt32(QByteArray& baData, quint32 nValue) +{ + for(int nByte = 0; nByte < 4; ++nByte) { + baData.append(static_cast((nValue >> (nByte * 8)) & 0xffu)); + } +} + +static void appendGaugeUInt64(QByteArray& baData, quint64 nValue) +{ + for(int nByte = 0; nByte < 8; ++nByte) { + baData.append(static_cast((nValue >> (nByte * 8)) & 0xffu)); + } +} + +static bool appendGaugeString(QByteArray& baData, const QString& sValue) +{ + const QByteArray baValue = sValue.toUtf8(); + if(baValue.size() < 0) { + return false; + } + appendGaugeUInt32(baData, static_cast(baValue.size())); + baData.append(baValue); + return true; +} + +static void appendGaugeDouble(QByteArray& baData, double dValue) +{ + quint64 nBits = 0; + memcpy(&nBits, &dValue, sizeof(double)); + appendGaugeUInt64(baData, nBits); +} + +static void appendGaugeDoubleVector( + QByteArray& baData, + const QVector& vecValues) +{ + appendGaugeUInt32(baData, static_cast(vecValues.size())); + for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) { + appendGaugeDouble(baData, vecValues[nIndex]); + } +} + +static void appendGaugePointVector( + QByteArray& baData, + const QVector& vecPoints) +{ + appendGaugeUInt32(baData, static_cast(vecPoints.size())); + for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { + appendGaugeDouble(baData, vecPoints[nIndex].x()); + appendGaugeDouble(baData, vecPoints[nIndex].y()); + } +} + +static void buildNormalizedPhaseRates( + const QVector& vecPoints, + bool bUsed, + int nScheduleCount, + QVector& vecRates) +{ + vecRates.clear(); + if(!bUsed || vecPoints.isEmpty()) { + vecRates.fill(0.0, nScheduleCount); + return; + } + vecRates.reserve(vecPoints.size()); + for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { + vecRates.append(vecPoints[nIndex].y()); + } +} + nmDataWellBase::nmDataWellBase() : m_eWellCategory(NM_WellCategory_Unknown) , m_bUseOilRate(true) @@ -219,6 +292,7 @@ nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other) m_vvecHsyPressure = other.m_vvecHsyPressure; m_vvecHsyLogLog = other.m_vvecHsyLogLog; m_vvecHsySemiLog = other.m_vvecHsySemiLog; + m_sHistoryGaugeInputSha1 = other.m_sHistoryGaugeInputSha1; // 默认与当前井身长度一致 m_dLastWellLength = m_wellLength.getValue().toDouble(); @@ -248,7 +322,7 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType& // 序列化井类型 wellObject.AddMember("WellType", rapidjson::Value(static_cast(m_eWellType)), allocator); - // 井别决定流量大字段的油、气、水索引,v4 项目必须显式保存。 + // 井别决定流量大字段的油、气、水索引,v5 项目必须显式保存。 wellObject.AddMember("WellCategory", rapidjson::Value(static_cast(m_eWellCategory)), allocator); @@ -346,7 +420,7 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue) m_eWellType = static_cast(jsonValue["WellType"].GetInt()); } - // v4 不兼容旧项目,缺失或非法井别由 Manager 的严格结构校验直接拒绝。 + // v5 不兼容旧项目,缺失或非法井别由 Manager 的严格结构校验直接拒绝。 if(jsonValue.HasMember("WellCategory") && jsonValue["WellCategory"].IsInt()) { m_eWellCategory = static_cast( @@ -465,7 +539,7 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue) m_nIndexF = jsonValue["IndexFlow"].GetInt(); } - // 先恢复选择键;记录数据在 Manager 完成 JSON 读取后从框架重新枚举。 + // 先恢复选择键;记录数据由 Manager 在 JSON 读取后从成果 Gauge 副本恢复。 if(jsonValue.HasMember("SelectedPressureGaugeCode") && jsonValue["SelectedPressureGaugeCode"].IsString()) { m_sSelectedPressureGaugeCode = QString::fromUtf8( @@ -1008,6 +1082,104 @@ void nmDataWellBase::setUseWaterRate(bool bUsed) m_bUseWaterRate = bUsed; } +void nmDataWellBase::buildGaugeInputData( + nmWellGaugeInputData& oInput) const +{ + oInput = nmWellGaugeInputData(); + oInput.sWellInstanceId = m_sWellInstanceId; + oInput.sWellCode = m_wellCode; + oInput.eWellCategory = m_eWellCategory; + oInput.sPressureGaugeCode = m_sSelectedPressureGaugeCode; + oInput.vecPressurePoints = getPressurePoints(); + oInput.sFlowGaugeCode = m_sSelectedFlowGaugeCode; + oInput.bUseOilRate = m_bUseOilRate; + oInput.bUseGasRate = m_bUseGasRate; + oInput.bUseWaterRate = m_bUseWaterRate; + oInput.eFlowSchedulePhase = getFlowSchedulePhase(); + oInput.nFlowSectionIndex = m_nIndexF; + + QVector vecSchedulePoints; + if(oInput.eFlowSchedulePhase != PHASE_UNKNOWN) { + vecSchedulePoints = getFlowSegmentPoints(oInput.eFlowSchedulePhase); + } + oInput.vecFlowDurations.reserve(vecSchedulePoints.size()); + for(int nIndex = 0; nIndex < vecSchedulePoints.size(); ++nIndex) { + oInput.vecFlowDurations.append(vecSchedulePoints[nIndex].x()); + } + + buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Oil), + m_bUseOilRate, vecSchedulePoints.size(), oInput.vecOilRates); + buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Gas), + m_bUseGasRate, vecSchedulePoints.size(), oInput.vecGasRates); + buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Water), + m_bUseWaterRate, vecSchedulePoints.size(), oInput.vecWaterRates); +} + +QString nmDataWellBase::calculateGaugeInputSha1( + const nmWellGaugeInputData& oInput) +{ + QByteArray baData; + appendGaugeUInt32(baData, 1u); + if(!appendGaugeString(baData, oInput.sWellInstanceId) || + !appendGaugeString(baData, oInput.sWellCode) || + !appendGaugeString(baData, oInput.sPressureGaugeCode) || + !appendGaugeString(baData, oInput.sFlowGaugeCode)) { + return QString(); + } + appendGaugeUInt32(baData, static_cast(oInput.eWellCategory)); + appendGaugePointVector(baData, oInput.vecPressurePoints); + baData.append(oInput.bUseOilRate ? '\1' : '\0'); + baData.append(oInput.bUseGasRate ? '\1' : '\0'); + baData.append(oInput.bUseWaterRate ? '\1' : '\0'); + appendGaugeUInt32(baData, + static_cast(oInput.eFlowSchedulePhase)); + appendGaugeDoubleVector(baData, oInput.vecFlowDurations); + appendGaugeDoubleVector(baData, oInput.vecOilRates); + appendGaugeDoubleVector(baData, oInput.vecGasRates); + appendGaugeDoubleVector(baData, oInput.vecWaterRates); + appendGaugeUInt32(baData, + static_cast(oInput.nFlowSectionIndex)); + return QString::fromLatin1(QCryptographicHash::hash( + baData, QCryptographicHash::Sha1).toHex()); +} + +QString nmDataWellBase::calculateGaugeInputSha1() const +{ + nmWellGaugeInputData oInput; + buildGaugeInputData(oInput); + return calculateGaugeInputSha1(oInput); +} + +int nmDataWellBase::getRawFlowRecordSegmentCount( + const nmFlowGaugeRecord& oRecord) +{ + bool bSharedPlaceholder = false; + if(oRecord.bMultiPhase && !oRecord.vecOilPoints.isEmpty() && + !oRecord.vecGasPoints.isEmpty() && + !oRecord.vecWaterPoints.isEmpty()) { + bSharedPlaceholder = oRecord.vecOilPoints.first() == QPointF(0.0, 0.0) && + oRecord.vecGasPoints.first() == QPointF(0.0, 0.0) && + oRecord.vecWaterPoints.first() == QPointF(0.0, 0.0); + } + const QVector arrPoints[] = { + oRecord.vecOilPoints, oRecord.vecGasPoints, oRecord.vecWaterPoints + }; + int nMaximumCount = 0; + for(int nPhase = 0; nPhase < 3; ++nPhase) { + int nCount = arrPoints[nPhase].size(); + if(oRecord.bMultiPhase) { + if(bSharedPlaceholder && nCount > 0) { + --nCount; + } + } else if(nCount > 0 && + arrPoints[nPhase].first() == QPointF(0.0, 0.0)) { + --nCount; + } + nMaximumCount = qMax(nMaximumCount, nCount); + } + return nMaximumCount; +} + QVector nmDataWellBase::getFlowSegmentPoints( NM_PHASE_TYPE eFlowPhase) const { @@ -1228,6 +1400,7 @@ QVector> nmDataWellBase::getHistoryPressure() { void nmDataWellBase::setHistoryPressure(QVector> pressureData) { m_vvecHsyPressure = pressureData; + m_sHistoryGaugeInputSha1.clear(); } QVector> nmDataWellBase::getHistoryLogLog() { @@ -1236,6 +1409,7 @@ QVector> nmDataWellBase::getHistoryLogLog() { void nmDataWellBase::setHistoryLogLog(QVector> loglogData) { m_vvecHsyLogLog = loglogData; + m_sHistoryGaugeInputSha1.clear(); } QVector> nmDataWellBase::getHistorySemiLog() { @@ -1244,6 +1418,24 @@ QVector> nmDataWellBase::getHistorySemiLog() { void nmDataWellBase::setHistorySemiLog(QVector> semiLogData) { m_vvecHsySemiLog = semiLogData; + m_sHistoryGaugeInputSha1.clear(); +} + +void nmDataWellBase::setHistoryData( + const QVector >& vecPressure, + const QVector >& vecLogLog, + const QVector >& vecSemiLog, + const QString& sGaugeInputSha1) +{ + m_vvecHsyPressure = vecPressure; + m_vvecHsyLogLog = vecLogLog; + m_vvecHsySemiLog = vecSemiLog; + m_sHistoryGaugeInputSha1 = sGaugeInputSha1; +} + +QString nmDataWellBase::getHistoryGaugeInputSha1() const +{ + return m_sHistoryGaugeInputSha1; } // 计算结果相关方法 diff --git a/Src/nmNum/nmData/nmNumericalResultPersistence.cpp b/Src/nmNum/nmData/nmNumericalResultPersistence.cpp index 29e9fc3..f0cfea5 100644 --- a/Src/nmNum/nmData/nmNumericalResultPersistence.cpp +++ b/Src/nmNum/nmData/nmNumericalResultPersistence.cpp @@ -1,14 +1,19 @@ #include "nmNumericalResultPersistence.h" +#include "nmDataWellBase.h" + +#include #include -#include #include #include #include +#include #include #include +#include #include +#include #include #include @@ -17,12 +22,17 @@ namespace { -const char g_aWellHistoryMagic[8] = { 'N', 'M', 'W', 'H', 'I', 'S', '3', 0 }; -const quint32 g_nWellHistoryFormatVersion = 1; +const char g_aWellHistoryMagic[8] = { 'N', 'M', 'W', 'H', 'I', 'S', '5', 0 }; +const char g_aWellGaugeMagic[8] = { 'N', 'M', 'W', 'G', 'A', 'U', '5', 0 }; +const quint32 g_nWellHistoryFormatVersion = 2; +const quint32 g_nWellGaugeFormatVersion = 1; const quint32 g_nEndianMarker = 0x01020304u; const quint32 g_nMaximumCurveSeries = 64u; const quint64 g_nMaximumCurvePoints = 50000000ull; +const quint32 g_nMaximumGaugeRecordCount = 10000u; +const quint32 g_nMaximumStringBytes = 16u * 1024u * 1024u; const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024; +const int g_nDoubleChunkCount = 8192; bool setError(QString* pError, const QString& sError) { @@ -69,18 +79,192 @@ bool readAll(QFile& oFile, char* pData, quint64 nBytes) return true; } -template -bool writePod(QFile& oFile, const T& oValue) +bool writeUInt32(QFile& oFile, quint32 nValue) +{ + char aBytes[4]; + for(int nIndex = 0; nIndex < 4; ++nIndex) + { + aBytes[nIndex] = static_cast((nValue >> (nIndex * 8)) & 0xffu); + } + return writeAll(oFile, aBytes, 4); +} + +bool readUInt32(QFile& oFile, quint32& nValue) +{ + unsigned char aBytes[4]; + if(!readAll(oFile, reinterpret_cast(aBytes), 4)) + { + return false; + } + nValue = 0; + for(int nIndex = 0; nIndex < 4; ++nIndex) + { + nValue |= static_cast(aBytes[nIndex]) << (nIndex * 8); + } + return true; +} + +bool writeUInt64(QFile& oFile, quint64 nValue) +{ + char aBytes[8]; + for(int nIndex = 0; nIndex < 8; ++nIndex) + { + aBytes[nIndex] = static_cast((nValue >> (nIndex * 8)) & 0xffu); + } + return writeAll(oFile, aBytes, 8); +} + +bool readUInt64(QFile& oFile, quint64& nValue) +{ + unsigned char aBytes[8]; + if(!readAll(oFile, reinterpret_cast(aBytes), 8)) + { + return false; + } + nValue = 0; + for(int nIndex = 0; nIndex < 8; ++nIndex) + { + nValue |= static_cast(aBytes[nIndex]) << (nIndex * 8); + } + return true; +} + +bool isFiniteValue(double dValue) +{ +#if defined(_MSC_VER) + return _finite(dValue) != 0; +#else + return qIsFinite(dValue); +#endif +} + +void appendDouble(QByteArray& baBytes, double dValue) +{ + quint64 nBits = 0; + memcpy(&nBits, &dValue, sizeof(nBits)); + for(int nIndex = 0; nIndex < 8; ++nIndex) + { + baBytes.append(static_cast((nBits >> (nIndex * 8)) & 0xffu)); + } +} + +bool readDouble(const char* pBytes, double& dValue) +{ + quint64 nBits = 0; + for(int nIndex = 0; nIndex < 8; ++nIndex) + { + nBits |= static_cast( + static_cast(pBytes[nIndex])) << (nIndex * 8); + } + memcpy(&dValue, &nBits, sizeof(dValue)); + return isFiniteValue(dValue); +} + +bool isSha1(const QString& sSha1) +{ + if(sSha1.size() != 40) + { + return false; + } + for(int nIndex = 0; nIndex < sSha1.size(); ++nIndex) + { + const QChar oChar = sSha1[nIndex]; + if(!((oChar >= '0' && oChar <= '9') || + (oChar >= 'a' && oChar <= 'f'))) + { + return false; + } + } + return true; +} + +bool writeString(QFile& oFile, const QString& sValue) +{ + const QByteArray baValue = sValue.toUtf8(); + return static_cast(baValue.size()) <= g_nMaximumStringBytes && + writeUInt32(oFile, static_cast(baValue.size())) && + (baValue.isEmpty() || writeAll(oFile, baValue.constData(), + static_cast(baValue.size()))); +} + +bool readString(QFile& oFile, QString& sValue) { - return writeAll(oFile, reinterpret_cast(&oValue), - static_cast(sizeof(T))); + quint32 nBytes = 0; + if(!readUInt32(oFile, nBytes) || nBytes > g_nMaximumStringBytes || + static_cast(nBytes) > + static_cast(oFile.bytesAvailable()) || + nBytes > static_cast(INT_MAX)) + { + return false; + } + QByteArray baValue; + baValue.resize(static_cast(nBytes)); + if(nBytes > 0 && !readAll(oFile, baValue.data(), nBytes)) + { + return false; + } + sValue = QString::fromUtf8(baValue.constData(), baValue.size()); + return sValue.toUtf8() == baValue; } -template -bool readPod(QFile& oFile, T& oValue) +bool writeDoubleVector(QFile& oFile, const QVector& vecValues) { - return readAll(oFile, reinterpret_cast(&oValue), - static_cast(sizeof(T))); + QByteArray baChunk; + baChunk.reserve(g_nDoubleChunkCount * static_cast(sizeof(double))); + for(int nStart = 0; nStart < vecValues.size(); + nStart += g_nDoubleChunkCount) + { + baChunk.clear(); + const int nEnd = qMin(nStart + g_nDoubleChunkCount, vecValues.size()); + for(int nIndex = nStart; nIndex < nEnd; ++nIndex) + { + if(!isFiniteValue(vecValues[nIndex])) + { + return false; + } + appendDouble(baChunk, vecValues[nIndex]); + } + if(!baChunk.isEmpty() && !writeAll(oFile, baChunk.constData(), + static_cast(baChunk.size()))) + { + return false; + } + } + return true; +} + +bool readDoubleVector(QFile& oFile, QVector& vecValues, + quint64 nPointCount) +{ + if(nPointCount > static_cast(INT_MAX)) + { + return false; + } + vecValues.resize(static_cast(nPointCount)); + QByteArray baChunk; + for(quint64 nStart = 0; nStart < nPointCount; + nStart += static_cast(g_nDoubleChunkCount)) + { + const int nCount = static_cast(qMin( + static_cast(g_nDoubleChunkCount), + nPointCount - nStart)); + baChunk.resize(nCount * static_cast(sizeof(double))); + if(!readAll(oFile, baChunk.data(), + static_cast(baChunk.size()))) + { + return false; + } + for(int nOffset = 0; nOffset < nCount; ++nOffset) + { + if(!readDouble(baChunk.constData() + + nOffset * static_cast(sizeof(double)), + vecValues[static_cast(nStart) + nOffset])) + { + return false; + } + } + } + return true; } bool safeMultiply(quint64 nLeft, quint64 nRight, quint64& nProduct) @@ -95,8 +279,12 @@ bool safeMultiply(quint64 nLeft, quint64 nRight, quint64& nProduct) bool writeCurve(QFile& oFile, const QVector >& vecCurve) { + if(static_cast(vecCurve.size()) > g_nMaximumCurveSeries) + { + return false; + } const quint32 nSeriesCount = static_cast(vecCurve.size()); - if(!writePod(oFile, nSeriesCount)) + if(!writeUInt32(oFile, nSeriesCount)) { return false; } @@ -106,10 +294,9 @@ bool writeCurve(QFile& oFile, const QVector >& vecCurve) const quint64 nPointCount = static_cast(vecValues.size()); quint64 nBytes = 0; if(!safeMultiply(nPointCount, sizeof(double), nBytes) || - !writePod(oFile, nPointCount) || - (nBytes > 0 && !writeAll(oFile, - reinterpret_cast(vecValues.constData()), - nBytes))) + nPointCount > g_nMaximumCurvePoints || + !writeUInt64(oFile, nPointCount) || + (nBytes > 0 && !writeDoubleVector(oFile, vecValues))) { return false; } @@ -122,7 +309,8 @@ bool readCurve(QFile& oFile, quint64& nTotalPointCount) { quint32 nSeriesCount = 0; - if(!readPod(oFile, nSeriesCount) || nSeriesCount > g_nMaximumCurveSeries) + if(!readUInt32(oFile, nSeriesCount) || + nSeriesCount > g_nMaximumCurveSeries) { return false; } @@ -133,7 +321,7 @@ bool readCurve(QFile& oFile, { quint64 nPointCount = 0; quint64 nBytes = 0; - if(!readPod(oFile, nPointCount) || + if(!readUInt64(oFile, nPointCount) || nPointCount > g_nMaximumCurvePoints || nTotalPointCount > g_nMaximumCurvePoints - nPointCount || !safeMultiply(nPointCount, sizeof(double), nBytes) || @@ -143,11 +331,8 @@ bool readCurve(QFile& oFile, return false; } nTotalPointCount += nPointCount; - vecLoaded[static_cast(nSeries)].resize( - static_cast(nPointCount)); - if(nBytes > 0 && !readAll(oFile, - reinterpret_cast( - vecLoaded[static_cast(nSeries)].data()), nBytes)) + if(nBytes > 0 && !readDoubleVector(oFile, + vecLoaded[static_cast(nSeries)], nPointCount)) { return false; } @@ -156,6 +341,183 @@ bool readCurve(QFile& oFile, return true; } +bool writePointVector(QFile& oFile, const QVector& vecPoints) +{ + const quint64 nPointCount = static_cast(vecPoints.size()); + if(nPointCount > g_nMaximumCurvePoints || + !writeUInt64(oFile, nPointCount)) + { + return false; + } + QByteArray baChunk; + baChunk.reserve(g_nDoubleChunkCount * 2 * static_cast(sizeof(double))); + for(int nStart = 0; nStart < vecPoints.size(); nStart += g_nDoubleChunkCount) + { + baChunk.clear(); + const int nEnd = qMin(nStart + g_nDoubleChunkCount, vecPoints.size()); + for(int nIndex = nStart; nIndex < nEnd; ++nIndex) + { + if(!isFiniteValue(vecPoints[nIndex].x()) || + !isFiniteValue(vecPoints[nIndex].y())) + { + return false; + } + appendDouble(baChunk, vecPoints[nIndex].x()); + appendDouble(baChunk, vecPoints[nIndex].y()); + } + if(!baChunk.isEmpty() && !writeAll(oFile, baChunk.constData(), + static_cast(baChunk.size()))) + { + return false; + } + } + return true; +} + +bool readPointVector(QFile& oFile, QVector& vecPoints, + quint64& nTotalPointCount) +{ + quint64 nPointCount = 0; + quint64 nDoubleCount = 0; + quint64 nBytes = 0; + if(!readUInt64(oFile, nPointCount) || + nPointCount > g_nMaximumCurvePoints || + nTotalPointCount > g_nMaximumCurvePoints - nPointCount || + !safeMultiply(nPointCount, 2, nDoubleCount) || + !safeMultiply(nDoubleCount, sizeof(double), nBytes) || + nBytes > static_cast(oFile.bytesAvailable()) || + nPointCount > static_cast(INT_MAX)) + { + return false; + } + nTotalPointCount += nPointCount; + vecPoints.resize(static_cast(nPointCount)); + QByteArray baChunk; + for(quint64 nStart = 0; nStart < nPointCount; + nStart += static_cast(g_nDoubleChunkCount)) + { + const int nCount = static_cast(qMin( + static_cast(g_nDoubleChunkCount), + nPointCount - nStart)); + baChunk.resize(nCount * 2 * static_cast(sizeof(double))); + if(!readAll(oFile, baChunk.data(), + static_cast(baChunk.size()))) + { + return false; + } + for(int nOffset = 0; nOffset < nCount; ++nOffset) + { + double dX = 0.0; + double dY = 0.0; + const char* pPoint = baChunk.constData() + + nOffset * 2 * static_cast(sizeof(double)); + if(!readDouble(pPoint, dX) || + !readDouble(pPoint + sizeof(double), dY)) + { + return false; + } + vecPoints[static_cast(nStart) + nOffset] = QPointF(dX, dY); + } + } + return true; +} + +bool isGaugeStatusValid(NM_GAUGE_RECORD_STATUS eStatus) +{ + return eStatus >= NM_GaugeRecord_Usable && + eStatus <= NM_GaugeRecord_InvalidIdentity; +} + +bool isCanonicalInstanceId(const QString& sInstanceId) +{ + const QUuid oUuid(sInstanceId); + return !oUuid.isNull() && + oUuid.toString().remove('{').remove('}') == sInstanceId; +} + +bool validateGaugeData(const nmNumericalWellGaugeData& oGaugeData) +{ + if(!isCanonicalInstanceId(oGaugeData.m_sWellInstanceId) || + oGaugeData.m_vecPressureRecords.size() > + static_cast(g_nMaximumGaugeRecordCount) || + oGaugeData.m_vecFlowRecords.size() > + static_cast(g_nMaximumGaugeRecordCount)) + { + return false; + } + quint64 nTotalPointCount = 0; + for(int nIndex = 0; + nIndex < oGaugeData.m_vecPressureRecords.size(); ++nIndex) + { + const nmPressureGaugeRecord& oRecord = + oGaugeData.m_vecPressureRecords[nIndex]; + const QByteArray baCode = oRecord.sGaugeCode.toUtf8(); + const QByteArray baName = oRecord.sGaugeName.toUtf8(); + const QByteArray baTime = oRecord.sGaugeTime.toUtf8(); + if(!isGaugeStatusValid(oRecord.eStatus) || + static_cast(baCode.size()) > g_nMaximumStringBytes || + static_cast(baName.size()) > g_nMaximumStringBytes || + static_cast(baTime.size()) > g_nMaximumStringBytes || + static_cast(oRecord.vecPressurePoints.size()) > + g_nMaximumCurvePoints - nTotalPointCount) + { + return false; + } + nTotalPointCount += static_cast( + oRecord.vecPressurePoints.size()); + for(int nPoint = 0; nPoint < oRecord.vecPressurePoints.size(); ++nPoint) + { + if(!isFiniteValue(oRecord.vecPressurePoints[nPoint].x()) || + !isFiniteValue(oRecord.vecPressurePoints[nPoint].y())) + { + return false; + } + } + } + for(int nIndex = 0; nIndex < oGaugeData.m_vecFlowRecords.size(); ++nIndex) + { + const nmFlowGaugeRecord& oRecord = oGaugeData.m_vecFlowRecords[nIndex]; + const QByteArray baCode = oRecord.sGaugeCode.toUtf8(); + const QByteArray baName = oRecord.sGaugeName.toUtf8(); + const QByteArray baTime = oRecord.sGaugeTime.toUtf8(); + const QVector arrPoints[] = { + oRecord.vecOilPoints, oRecord.vecGasPoints, oRecord.vecWaterPoints + }; + if(!isGaugeStatusValid(oRecord.eStatus) || + static_cast(baCode.size()) > g_nMaximumStringBytes || + static_cast(baName.size()) > g_nMaximumStringBytes || + static_cast(baTime.size()) > g_nMaximumStringBytes) + { + return false; + } + for(int nPhase = 0; nPhase < 3; ++nPhase) + { + if(static_cast(arrPoints[nPhase].size()) > + g_nMaximumCurvePoints - nTotalPointCount) + { + return false; + } + nTotalPointCount += static_cast(arrPoints[nPhase].size()); + for(int nPoint = 0; nPoint < arrPoints[nPhase].size(); ++nPoint) + { + if(!isFiniteValue(arrPoints[nPhase][nPoint].x()) || + !isFiniteValue(arrPoints[nPhase][nPoint].y())) + { + return false; + } + } + } + const int nSegmentCount = + nmDataWellBase::getRawFlowRecordSegmentCount(oRecord); + if(oRecord.nIndexF < 0 || + (oRecord.nIndexF > 0 && oRecord.nIndexF > nSegmentCount)) + { + return false; + } + } + return true; +} + bool hasExactMagic(const char* pActual, const char* pExpected, int nSize) { for(int nIndex = 0; nIndex < nSize; ++nIndex) @@ -204,8 +566,8 @@ nmNumericalWindowPayloadReferences::nmNumericalWindowPayloadReferences() int nmNumericalResultPersistence::projectVersion() { - // v4 强制保存井别,旧版本不再按默认油相解释流量字段。 - return 4; + // v5 保存完整 Gauge 副本及输入签名,不再依赖框架当前记录。 + return 5; } quint64 nmNumericalResultPersistence::maximumBinaryPayloadBytes() @@ -382,6 +744,10 @@ bool nmNumericalResultPersistence::writeWellHistory( { pError->clear(); } + if(!isSha1(oHistory.m_sGaugeInputSha1)) + { + return setError(pError, "Numerical well history Gauge signature is invalid."); + } QFile oFile(sFilePath); if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { @@ -389,8 +755,9 @@ bool nmNumericalResultPersistence::writeWellHistory( } const bool bWritten = writeAll(oFile, g_aWellHistoryMagic, 8) && - writePod(oFile, g_nWellHistoryFormatVersion) && - writePod(oFile, g_nEndianMarker) && + writeUInt32(oFile, g_nWellHistoryFormatVersion) && + writeUInt32(oFile, g_nEndianMarker) && + writeString(oFile, oHistory.m_sGaugeInputSha1) && writeCurve(oFile, oHistory.m_vecPressure) && writeCurve(oFile, oHistory.m_vecLogLog) && writeCurve(oFile, oHistory.m_vecSemiLog) && @@ -434,10 +801,12 @@ bool nmNumericalResultPersistence::readWellHistory( nmNumericalWellHistoryData oLoaded; const bool bRead = readAll(oFile, aMagic, 8) && hasExactMagic(aMagic, g_aWellHistoryMagic, 8) && - readPod(oFile, nFormatVersion) && + readUInt32(oFile, nFormatVersion) && nFormatVersion == g_nWellHistoryFormatVersion && - readPod(oFile, nEndianMarker) && + readUInt32(oFile, nEndianMarker) && nEndianMarker == g_nEndianMarker && + readString(oFile, oLoaded.m_sGaugeInputSha1) && + isSha1(oLoaded.m_sGaugeInputSha1) && readCurve(oFile, oLoaded.m_vecPressure, nTotalPointCount) && readCurve(oFile, oLoaded.m_vecLogLog, nTotalPointCount) && readCurve(oFile, oLoaded.m_vecSemiLog, nTotalPointCount) && @@ -456,6 +825,192 @@ bool nmNumericalResultPersistence::readWellHistory( } } +bool nmNumericalResultPersistence::writeWellGaugeData( + const QString& sFilePath, + const nmNumericalWellGaugeData& oGaugeData, + QString* pError) +{ + if(pError != NULL) + { + pError->clear(); + } + if(!validateGaugeData(oGaugeData)) + { + return setError(pError, "Numerical well Gauge data are invalid."); + } + + try + { + QFile oFile(sFilePath); + if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + return setError(pError, "Cannot create numerical well Gauge file."); + } + bool bWritten = writeAll(oFile, g_aWellGaugeMagic, 8) && + writeUInt32(oFile, g_nWellGaugeFormatVersion) && + writeUInt32(oFile, g_nEndianMarker) && + writeString(oFile, oGaugeData.m_sWellInstanceId) && + writeUInt32(oFile, static_cast( + oGaugeData.m_vecPressureRecords.size())); + for(int nIndex = 0; + bWritten && nIndex < oGaugeData.m_vecPressureRecords.size(); + ++nIndex) + { + const nmPressureGaugeRecord& oRecord = + oGaugeData.m_vecPressureRecords[nIndex]; + bWritten = writeString(oFile, oRecord.sGaugeCode) && + writeString(oFile, oRecord.sGaugeName) && + writeString(oFile, oRecord.sGaugeTime) && + writeUInt32(oFile, static_cast(oRecord.eStatus)) && + writePointVector(oFile, oRecord.vecPressurePoints); + } + bWritten = bWritten && writeUInt32(oFile, static_cast( + oGaugeData.m_vecFlowRecords.size())); + for(int nIndex = 0; + bWritten && nIndex < oGaugeData.m_vecFlowRecords.size(); ++nIndex) + { + const nmFlowGaugeRecord& oRecord = + oGaugeData.m_vecFlowRecords[nIndex]; + bWritten = writeString(oFile, oRecord.sGaugeCode) && + writeString(oFile, oRecord.sGaugeName) && + writeString(oFile, oRecord.sGaugeTime) && + writeUInt32(oFile, oRecord.bMultiPhase ? 1u : 0u) && + writeUInt32(oFile, static_cast(oRecord.eStatus)) && + writePointVector(oFile, oRecord.vecOilPoints) && + writePointVector(oFile, oRecord.vecGasPoints) && + writePointVector(oFile, oRecord.vecWaterPoints) && + writeUInt32(oFile, static_cast( + static_cast(oRecord.nIndexF))); + } + bWritten = bWritten && oFile.flush() && oFile.size() > 0 && + static_cast(oFile.size()) <= + maximumBinaryPayloadBytes(); + oFile.close(); + if(!bWritten) + { + QFile::remove(sFilePath); + return setError(pError, + "Cannot write complete numerical well Gauge file."); + } + return true; + } + catch(const std::bad_alloc&) + { + QFile::remove(sFilePath); + return setError(pError, + "Not enough memory to save numerical well Gauge data."); + } +} + +bool nmNumericalResultPersistence::readWellGaugeData( + const QString& sFilePath, + nmNumericalWellGaugeData& oGaugeData, + QString* pError) +{ + if(pError != NULL) + { + pError->clear(); + } + const QFileInfo oInfo(sFilePath); + if(!oInfo.isFile() || oInfo.size() <= 0 || + static_cast(oInfo.size()) > maximumBinaryPayloadBytes()) + { + return setError(pError, + "Numerical well Gauge payload is missing or too large."); + } + + try + { + QFile oFile(sFilePath); + if(!oFile.open(QIODevice::ReadOnly)) + { + return setError(pError, "Cannot open numerical well Gauge file."); + } + char aMagic[8] = { 0 }; + quint32 nFormatVersion = 0; + quint32 nEndianMarker = 0; + quint32 nPressureCount = 0; + quint32 nFlowCount = 0; + quint64 nTotalPointCount = 0; + nmNumericalWellGaugeData oLoaded; + bool bRead = readAll(oFile, aMagic, 8) && + hasExactMagic(aMagic, g_aWellGaugeMagic, 8) && + readUInt32(oFile, nFormatVersion) && + nFormatVersion == g_nWellGaugeFormatVersion && + readUInt32(oFile, nEndianMarker) && + nEndianMarker == g_nEndianMarker && + readString(oFile, oLoaded.m_sWellInstanceId) && + isCanonicalInstanceId(oLoaded.m_sWellInstanceId) && + readUInt32(oFile, nPressureCount) && + nPressureCount <= g_nMaximumGaugeRecordCount; + oLoaded.m_vecPressureRecords.reserve( + bRead ? static_cast(nPressureCount) : 0); + for(quint32 nIndex = 0; bRead && nIndex < nPressureCount; ++nIndex) + { + nmPressureGaugeRecord oRecord; + quint32 nStatus = 0; + bRead = readString(oFile, oRecord.sGaugeCode) && + readString(oFile, oRecord.sGaugeName) && + readString(oFile, oRecord.sGaugeTime) && + readUInt32(oFile, nStatus) && + nStatus <= static_cast( + NM_GaugeRecord_InvalidIdentity) && + readPointVector(oFile, oRecord.vecPressurePoints, + nTotalPointCount); + if(bRead) + { + oRecord.eStatus = static_cast(nStatus); + oLoaded.m_vecPressureRecords.append(oRecord); + } + } + bRead = bRead && readUInt32(oFile, nFlowCount) && + nFlowCount <= g_nMaximumGaugeRecordCount; + oLoaded.m_vecFlowRecords.reserve( + bRead ? static_cast(nFlowCount) : 0); + for(quint32 nIndex = 0; bRead && nIndex < nFlowCount; ++nIndex) + { + nmFlowGaugeRecord oRecord; + quint32 nMultiPhase = 0; + quint32 nStatus = 0; + quint32 nIndexF = 0; + bRead = readString(oFile, oRecord.sGaugeCode) && + readString(oFile, oRecord.sGaugeName) && + readString(oFile, oRecord.sGaugeTime) && + readUInt32(oFile, nMultiPhase) && nMultiPhase <= 1u && + readUInt32(oFile, nStatus) && + nStatus <= static_cast( + NM_GaugeRecord_InvalidIdentity) && + readPointVector(oFile, oRecord.vecOilPoints, + nTotalPointCount) && + readPointVector(oFile, oRecord.vecGasPoints, + nTotalPointCount) && + readPointVector(oFile, oRecord.vecWaterPoints, + nTotalPointCount) && + readUInt32(oFile, nIndexF) && nIndexF <= INT_MAX; + if(bRead) + { + oRecord.bMultiPhase = nMultiPhase != 0; + oRecord.eStatus = static_cast(nStatus); + oRecord.nIndexF = static_cast(nIndexF); + oLoaded.m_vecFlowRecords.append(oRecord); + } + } + bRead = bRead && oFile.atEnd() && validateGaugeData(oLoaded); + oFile.close(); + if(!bRead) + { + return setError(pError, "Numerical well Gauge file is damaged."); + } + oGaugeData = oLoaded; + return true; + } + catch(const std::bad_alloc&) + { + return setError(pError, + "Not enough memory to load numerical well Gauge data."); + } +} + bool nmNumericalResultPersistence::writeGrid( vtkUnstructuredGrid* pGrid, const QString& sFilePath, diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp index 822b350..912ce9e 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp @@ -35,6 +35,24 @@ bool isValidInstanceId(const QString& sInstanceId) return !sInstanceId.isEmpty() && !QUuid(sInstanceId).isNull(); } +bool isGaugeInputSha1(const QString& sSha1) +{ + if(sSha1.size() != 40) + { + return false; + } + for(int nIndex = 0; nIndex < sSha1.size(); ++nIndex) + { + const QChar oChar = sSha1[nIndex]; + if(!((oChar >= '0' && oChar <= '9') || + (oChar >= 'a' && oChar <= 'f'))) + { + return false; + } + } + return true; +} + bool isFiniteVector(const QVector& vecValues) { for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) @@ -522,6 +540,7 @@ bool nmPebiResultSnapshotBuilder::takeWell( qSwap(oTarget.m_sWellInstanceId, oWell.m_sWellInstanceId); qSwap(oTarget.m_sWellCode, oWell.m_sWellCode); qSwap(oTarget.m_sWellName, oWell.m_sWellName); + qSwap(oTarget.m_sGaugeInputSha1, oWell.m_sGaugeInputSha1); oTarget.m_eWellType = oWell.m_eWellType; oTarget.m_eWellCategory = oWell.m_eWellCategory; oTarget.m_eWellMode = oWell.m_eWellMode; @@ -721,6 +740,7 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError) m_pCandidate->m_vecWells[nIndex]; if(!isValidInstanceId(oWell.m_sWellInstanceId) || oWell.m_sWellCode.isEmpty() || oWell.m_sWellName.isEmpty() || + !isGaugeInputSha1(oWell.m_sGaugeInputSha1) || oWell.m_eWellType == Unknow_Well || !nmIsValidWellCategory(oWell.m_eWellCategory) || (oWell.m_eWellMode != NM_CaseWell_RateControlled && diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp index 27163ed..e301448 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp @@ -171,13 +171,18 @@ bool readFileReference(const rapidjson::Value& oParent, 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()) + !oJson.HasMember("Sha1") || !oJson["Sha1"].IsString() || + oJson["Sha1"].GetStringLength() != 40u) { return false; } - oReference.m_sRelativePath = QString::fromUtf8(oJson["Path"].GetString()); + oReference.m_sRelativePath = QString::fromUtf8( + oJson["Path"].GetString(), + static_cast(oJson["Path"].GetStringLength())); oReference.m_nLength = oJson["Length"].GetUint64(); - oReference.m_sSha1 = QString::fromLatin1(oJson["Sha1"].GetString()); + oReference.m_sSha1 = QString::fromLatin1( + oJson["Sha1"].GetString(), + static_cast(oJson["Sha1"].GetStringLength())); return oReference.isValid(); } @@ -528,7 +533,7 @@ bool parseSnapshotJson( if(!oDocument.IsObject() || !oDocument.HasMember("SnapshotFormatVersion") || !oDocument["SnapshotFormatVersion"].IsInt() || - oDocument["SnapshotFormatVersion"].GetInt() != 2 || + oDocument["SnapshotFormatVersion"].GetInt() != 3 || !oDocument.HasMember("ResultCellCount") || !oDocument["ResultCellCount"].IsUint64() || !oDocument.HasMember("PressureFrameCount") || @@ -547,9 +552,12 @@ bool parseSnapshotJson( if(nCellCount == 0 || nCellCount > g_nMaximumCellCount || nFrameCount == 0 || nFrameCount > g_nMaximumFrameCount || nWellCount > g_nMaximumWellCount || - oGridReference.m_sRelativePath == oPressureReference.m_sRelativePath || - oGridReference.m_sRelativePath == oCurvesReference.m_sRelativePath || - oPressureReference.m_sRelativePath == oCurvesReference.m_sRelativePath) + oGridReference.m_sRelativePath.compare( + oPressureReference.m_sRelativePath, Qt::CaseInsensitive) == 0 || + oGridReference.m_sRelativePath.compare( + oCurvesReference.m_sRelativePath, Qt::CaseInsensitive) == 0 || + oPressureReference.m_sRelativePath.compare( + oCurvesReference.m_sRelativePath, Qt::CaseInsensitive) == 0) { return setError(pError, "Snapshot.json declares invalid or duplicate payloads."); } @@ -692,8 +700,8 @@ bool nmPebiResultSnapshotSerializer::save( oDocument.SetObject(); rapidjson::Document::AllocatorType& oAllocator = oDocument.GetAllocator(); - // v2 增加井别元数据;压力帧和井曲线二进制格式保持不变。 - oDocument.AddMember("SnapshotFormatVersion", 2, oAllocator); + // v3 强制保存每口结果井的 Gauge 输入签名。 + oDocument.AddMember("SnapshotFormatVersion", 3, oAllocator); oDocument.AddMember("SnapshotId", toJsonString(pSnapshot->m_sSnapshotId, oAllocator), oAllocator); oDocument.AddMember("GridInputRevision", @@ -748,6 +756,8 @@ bool nmPebiResultSnapshotSerializer::save( oJson.AddMember("WellCategory", static_cast( oWell.m_eWellCategory), oAllocator); oJson.AddMember("WellMode", static_cast(oWell.m_eWellMode), oAllocator); + oJson.AddMember("GaugeInputSha1", toJsonString( + oWell.m_sGaugeInputSha1, oAllocator), oAllocator); oJson.AddMember("X", oWell.m_oLocation.x(), oAllocator); oJson.AddMember("Y", oWell.m_oLocation.y(), oAllocator); oJson.AddMember("HasPerforation", oWell.m_bHasPerforation, oAllocator); @@ -890,6 +900,38 @@ bool nmPebiResultSnapshotSerializer::validate( return true; } +bool nmPebiResultSnapshotSerializer::getPayloadRelativePaths( + const QString& sWindowDirectory, + const nmNumericalFileReference& oSnapshotJsonReference, + QStringList& listRelativePaths, + QString* pError) +{ + listRelativePaths.clear(); + QString sJsonPath; + if(!nmNumericalResultPersistence::validateFileReference( + sWindowDirectory, oSnapshotJsonReference, &sJsonPath, pError)) + { + return false; + } + rapidjson::Document oDocument; + nmNumericalFileReference oGridReference; + nmNumericalFileReference oPressureReference; + nmNumericalFileReference oCurvesReference; + quint64 nCellCount = 0; + quint32 nFrameCount = 0; + quint32 nWellCount = 0; + if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference, + oPressureReference, oCurvesReference, nCellCount, + nFrameCount, nWellCount, pError)) + { + return false; + } + listRelativePaths << oGridReference.m_sRelativePath + << oPressureReference.m_sRelativePath + << oCurvesReference.m_sRelativePath; + return true; +} + bool nmPebiResultSnapshotSerializer::load( const QString& sWindowDirectory, const nmNumericalFileReference& oSnapshotJsonReference, @@ -1027,6 +1069,9 @@ bool nmPebiResultSnapshotSerializer::load( !oJson["WellInstanceId"].IsString() || !oJson.HasMember("WellCode") || !oJson["WellCode"].IsString() || !oJson.HasMember("WellName") || !oJson["WellName"].IsString() || + !oJson.HasMember("GaugeInputSha1") || + !oJson["GaugeInputSha1"].IsString() || + oJson["GaugeInputSha1"].GetStringLength() != 40u || !readRequiredInt(oJson, "WellType", nWellType) || !readRequiredInt(oJson, "WellCategory", nWellCategory) || !readRequiredInt(oJson, "WellMode", nWellMode) || @@ -1046,6 +1091,10 @@ bool nmPebiResultSnapshotSerializer::load( oJson["WellInstanceId"].GetString()); oWell.m_sWellCode = QString::fromUtf8(oJson["WellCode"].GetString()); oWell.m_sWellName = QString::fromUtf8(oJson["WellName"].GetString()); + oWell.m_sGaugeInputSha1 = QString::fromLatin1( + oJson["GaugeInputSha1"].GetString(), + static_cast( + oJson["GaugeInputSha1"].GetStringLength())); oWell.m_eWellType = static_cast(nWellType); oWell.m_eWellCategory = static_cast( nWellCategory); diff --git a/Src/nmNum/nmPlot/nmGuiPlot.cpp b/Src/nmNum/nmPlot/nmGuiPlot.cpp index 83f43f0..961cf38 100644 --- a/Src/nmNum/nmPlot/nmGuiPlot.cpp +++ b/Src/nmNum/nmPlot/nmGuiPlot.cpp @@ -265,11 +265,6 @@ void nmGuiPlot::initDefultGeoObj() return; } - // 历史数据 - QVector> vvecHistoryPressureData; - QVector> vvecHistoryLogData; - QVector> vvecHistorySemiLogData; - if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) { // 初始化直井默认参数 nmDataWellBase* pWell = pDataManager->createWell(NM_WELL_MODEL::Vertical_Well); @@ -297,13 +292,7 @@ void nmGuiPlot::initDefultGeoObj() nmDataAnalyzeManager::applyGaugeRecordsToWell( m_VerticalWell, vecPressureRecords, vecFlowRecords); - // TODO: 计算井的历史双对数/半对数数据 - pDataManager->calculationLogData(m_VerticalWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - // 存储历史数据到井对象 - m_VerticalWell->setHistoryPressure(vvecHistoryPressureData); - m_VerticalWell->setHistoryLogLog(vvecHistoryLogData); - m_VerticalWell->setHistorySemiLog(vvecHistorySemiLogData); + pDataManager->updateWellHistoryData(m_VerticalWell); // 设置为当前查看的井 pDataManager->setCurWellData(m_VerticalWell); @@ -335,13 +324,7 @@ void nmGuiPlot::initDefultGeoObj() nmDataAnalyzeManager::applyGaugeRecordsToWell( m_VFracturedWell, vecPressureRecords, vecFlowRecords); - // TODO: 计算井的历史双对数/半对数数据 - pDataManager->calculationLogData(m_VFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - // 存储历史数据到井对象 - m_VFracturedWell->setHistoryPressure(vvecHistoryPressureData); - m_VFracturedWell->setHistoryLogLog(vvecHistoryLogData); - m_VFracturedWell->setHistorySemiLog(vvecHistorySemiLogData); + pDataManager->updateWellHistoryData(m_VFracturedWell); // 更新裂缝位置信息 m_VFracturedWell->setFracs(); @@ -376,13 +359,7 @@ void nmGuiPlot::initDefultGeoObj() nmDataAnalyzeManager::applyGaugeRecordsToWell( m_HFracturedWell, vecPressureRecords, vecFlowRecords); - // TODO: 计算井的历史双对数/半对数数据 - pDataManager->calculationLogData(m_HFracturedWell, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - - // 存储历史数据到井对象 - m_HFracturedWell->setHistoryPressure(vvecHistoryPressureData); - m_HFracturedWell->setHistoryLogLog(vvecHistoryLogData); - m_HFracturedWell->setHistorySemiLog(vvecHistorySemiLogData); + pDataManager->updateWellHistoryData(m_HFracturedWell); // 计算裂缝数据 m_HFracturedWell->setFracs(); diff --git a/Src/nmNum/nmPlot/nmPlotGraphicBinder.cpp b/Src/nmNum/nmPlot/nmPlotGraphicBinder.cpp index 87deaf4..7b479b5 100644 --- a/Src/nmNum/nmPlot/nmPlotGraphicBinder.cpp +++ b/Src/nmNum/nmPlot/nmPlotGraphicBinder.cpp @@ -1796,14 +1796,10 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic, } } - QVector> vvecHistoryPressureData; - QVector> vvecHistoryLogData; - QVector> vvecHistorySemiLogData; // 初始化历史压力、双对数和半对数数据,保证重新显示后可直接分析。 - calculateWellLogData(pData, vvecHistoryPressureData, vvecHistoryLogData, vvecHistorySemiLogData); - pData->setHistoryPressure(vvecHistoryPressureData); - pData->setHistoryLogLog(vvecHistoryLogData); - pData->setHistorySemiLog(vvecHistorySemiLogData); + if(m_pDataMgr) { + m_pDataMgr->updateWellHistoryData(pData); + } // 通知 PEBI 面板新增井分组 if (m_pDataMgr) { @@ -1933,15 +1929,6 @@ void nmPlotGraphicBinder::onGraphicWellVisibleChanged(bool bVisible) pData->setPlotVisible(bVisible); } -void nmPlotGraphicBinder::calculateWellLogData(nmDataWellBase* pWellData, - QVector>& pressure, - QVector>& logLog, - QVector>& semiLog) -{ - if (!m_pDataMgr || !pWellData) return; - m_pDataMgr->calculationLogData(pWellData, pressure, logLog, semiLog); -} - bool nmPlotGraphicBinder::hasLayers() const { if (!m_pDataMgr) return false; diff --git a/Src/nmNum/nmSubWxs/nmWellEditorSession.cpp b/Src/nmNum/nmSubWxs/nmWellEditorSession.cpp index 325f40f..84cfa60 100644 --- a/Src/nmNum/nmSubWxs/nmWellEditorSession.cpp +++ b/Src/nmNum/nmSubWxs/nmWellEditorSession.cpp @@ -387,9 +387,10 @@ bool nmWellEditorSession::copySharedWellData( pTarget->setUseGasRate(pSource->getUseGasRate()); pTarget->setUseWaterRate(pSource->getUseWaterRate()); pTarget->setIndexF(pSource->getIndexF()); - pTarget->setHistoryPressure(pSource->getHistoryPressure()); - pTarget->setHistoryLogLog(pSource->getHistoryLogLog()); - pTarget->setHistorySemiLog(pSource->getHistorySemiLog()); + pTarget->setHistoryData(pSource->getHistoryPressure(), + pSource->getHistoryLogLog(), + pSource->getHistorySemiLog(), + pSource->getHistoryGaugeInputSha1()); // 第三步:保留图元显示属性和跨井型开关。 pTarget->setPlotVisible(pSource->getPlotVisible()); diff --git a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp index fe53f83..6adf2a5 100644 --- a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp +++ b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp @@ -708,18 +708,9 @@ void nmWxEditWellPlot::onOkClicked() if(bCurveSelectionChanged) { nmDataWellBase* pUpdatedWell = pManager->findWellByInstanceId( m_pNmDataWell->getWellInstanceId()); - if(pUpdatedWell != nullptr) { - QVector > vecHistoryPressure; - QVector > vecHistoryLogLog; - QVector > vecHistorySemiLog; - pManager->calculationLogData(pUpdatedWell, - vecHistoryPressure, - vecHistoryLogLog, - vecHistorySemiLog); - pUpdatedWell->setHistoryPressure(vecHistoryPressure); - pUpdatedWell->setHistoryLogLog(vecHistoryLogLog); - pUpdatedWell->setHistorySemiLog(vecHistorySemiLog); - } + if(pUpdatedWell != nullptr) { + pManager->updateWellHistoryData(pUpdatedWell); + } } // 人工确认后的输入变化必须推进版本,阻止后台旧任务提交过期结果。 diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index a06c16f..6f9f356 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -1162,16 +1162,7 @@ void nmWxNumericalDesign::onOptionsIconClicked() pWellData->setIndexF(oWellRow.m_nFlowSegmentIndex); if(bCurveSelectionChanged) { - QVector > vecHistoryPressure; - QVector > vecHistoryLogLog; - QVector > vecHistorySemiLog; - m_pDataManager->calculationLogData(pWellData, - vecHistoryPressure, - vecHistoryLogLog, - vecHistorySemiLog); - pWellData->setHistoryPressure(vecHistoryPressure); - pWellData->setHistoryLogLog(vecHistoryLogLog); - pWellData->setHistorySemiLog(vecHistorySemiLog); + m_pDataManager->updateWellHistoryData(pWellData); if(bWasIncluded || bWillBeIncluded) { bIncludedCurveSelectionChanged = true; }