diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h index f3d13be..b5d8115 100644 --- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h +++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h @@ -34,6 +34,9 @@ struct nmPebiWellInputSnapshot m_dWellboreStorage(0.0), m_dSkin(0.0), m_dDfc(0.0), + m_bUseOilRate(false), + m_bUseGasRate(false), + m_bUseWaterRate(false), m_bRateControlled(false), m_bRealWell(false), m_bHasPerforation(false), @@ -64,6 +67,9 @@ struct nmPebiWellInputSnapshot double m_dWellboreStorage; ///< 求解时井筒储集系数。 double m_dSkin; ///< 求解时表皮系数。 double m_dDfc; ///< 求解时裂缝导流能力。 + bool m_bUseOilRate; ///< 求解时是否启用油相流量。 + bool m_bUseGasRate; ///< 求解时是否启用气相流量。 + bool m_bUseWaterRate; ///< 求解时是否启用水相流量。 bool m_bRateControlled; ///< 是否提供源汇项。 bool m_bRealWell; ///< 是否为真实井而非手工裂缝槽位。 bool m_bHasPerforation; ///< 求解时是否存在射孔。 diff --git a/Include/nmNum/nmData/nmPebiResultSnapshot.h b/Include/nmNum/nmData/nmPebiResultSnapshot.h index 7b70f33..8b5efa4 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshot.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshot.h @@ -78,6 +78,24 @@ struct NM_DATA_EXPORT nmPebiResultWellSnapshot NM_CASE_WELL_MODE m_eWellMode; /** @brief 求解时冻结的规范化 Gauge 输入签名。 */ QString m_sGaugeInputSha1; + /** @brief 求解时冻结的公共流动段时长,不包含框架占位行。 */ + QVector m_vecFlowDurations; + /** @brief 与公共流动段逐项对应的油相流量。 */ + QVector m_vecOilRates; + /** @brief 与公共流动段逐项对应的气相流量。 */ + QVector m_vecGasRates; + /** @brief 与公共流动段逐项对应的水相流量。 */ + QVector m_vecWaterRates; + /** @brief 历史流量图及旧单相后处理使用的制度参考相。 */ + NM_PHASE_TYPE m_eFlowSchedulePhase; + /** @brief 求解时冻结的一基流动段索引。 */ + int m_nFlowSectionIndex; + /** @brief 求解时是否启用油相流量。 */ + bool m_bUseOilRate; + /** @brief 求解时是否启用气相流量。 */ + bool m_bUseGasRate; + /** @brief 求解时是否启用水相流量。 */ + bool m_bUseWaterRate; /** @brief 求解时冻结的井平面位置。 */ QPointF m_oLocation; /** @brief 求解时是否存在射孔数据。 */ diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index ce26e00..dae7dad 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -240,7 +240,7 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { // 填充已经存在的数值三维和结果参数页面。 bool populateResultWidgets(iSubWndFitting* pSubWndFit, nmDataAnalyzeManager* pDataManager); - /** @brief 用快照中冻结的历史和计算曲线刷新三个结果窗口。 */ + /** @brief 用快照中冻结的压力、流量和计算曲线刷新三个结果窗口。 */ bool refreshPebiResultCurves( iSubWndFitting* pSubWndFit, nmDataAnalyzeManager* pDataManager, diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 4b27c7c..3c49f3a 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -2015,6 +2015,24 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate() ? NM_CaseWell_RateControlled : NM_CaseWell_Observation; oWell.m_sGaugeInputSha1 = oWellInput.m_sGaugeInputSha1; + if(oWellInput.m_bRateControlled) { + // 结果切井必须显示本轮求解实际使用的制度,不能重新读取实时井。 + oWell.m_eFlowSchedulePhase = + oWellInput.m_eFlowSchedulePhase; + oWell.m_nFlowSectionIndex = + oWellInput.m_nFlowSectionIndex; + oWell.m_bUseOilRate = oWellInput.m_bUseOilRate; + oWell.m_bUseGasRate = oWellInput.m_bUseGasRate; + oWell.m_bUseWaterRate = oWellInput.m_bUseWaterRate; + qSwap(oWell.m_vecFlowDurations, + oWellInput.m_vecFlowDurations); + qSwap(oWell.m_vecOilRates, + oWellInput.m_vecOilRates); + qSwap(oWell.m_vecGasRates, + oWellInput.m_vecGasRates); + qSwap(oWell.m_vecWaterRates, + oWellInput.m_vecWaterRates); + } 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 89b6cd0..156b058 100644 --- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp @@ -216,6 +216,10 @@ bool captureWellFlowSchedule( oWellInput.m_vecOilRates.clear(); oWellInput.m_vecGasRates.clear(); oWellInput.m_vecWaterRates.clear(); + oWellInput.m_eFlowSchedulePhase = PHASE_UNKNOWN; + oWellInput.m_bUseOilRate = false; + oWellInput.m_bUseGasRate = false; + oWellInput.m_bUseWaterRate = false; if(!oGaugeInput.hasAlignedFlowSchedule()) { return false; } @@ -224,6 +228,9 @@ bool captureWellFlowSchedule( oWellInput.m_vecOilRates = oGaugeInput.vecOilRates; oWellInput.m_vecGasRates = oGaugeInput.vecGasRates; oWellInput.m_vecWaterRates = oGaugeInput.vecWaterRates; + oWellInput.m_bUseOilRate = oGaugeInput.bUseOilRate; + oWellInput.m_bUseGasRate = oGaugeInput.bUseGasRate; + oWellInput.m_bUseWaterRate = oGaugeInput.bUseWaterRate; return true; } diff --git a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp index 623a2dd..838a1f3 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp @@ -19,6 +19,11 @@ nmPebiResultWellSnapshot::nmPebiResultWellSnapshot() : m_eWellType(Unknow_Well), m_eWellCategory(NM_WellCategory_Unknown), m_eWellMode(NM_CaseWell_Observation), + m_eFlowSchedulePhase(PHASE_UNKNOWN), + m_nFlowSectionIndex(0), + m_bUseOilRate(false), + m_bUseGasRate(false), + m_bUseWaterRate(false), m_bHasPerforation(false), m_bHasSkin(false), m_bHasDfc(false), diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp index 912ce9e..0abe911 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp @@ -20,7 +20,7 @@ namespace { // Builder 只校验内存模型的完整性;持久化文件的数量上限和字节上限 -// 由 v3 加载器在分配 VTK/Qt 容器之前单独检查。 +// 由当前成果加载器在分配 VTK/Qt 容器之前单独检查。 bool isFiniteValue(double dValue) { #if defined(_MSC_VER) @@ -128,6 +128,45 @@ bool areWellCurvesValid(const nmPebiResultWellCurves& oCurves) isCurveValid(oCurves.m_vecResultSemiLog, 2, false); } +bool isWellFlowScheduleValid(const nmPebiResultWellSnapshot& oWell) +{ + // 观察井不向求解器提供源汇项,结果快照中也不伪造流量制度。 + if(oWell.m_eWellMode == NM_CaseWell_Observation) + { + return oWell.m_vecFlowDurations.isEmpty() && + oWell.m_vecOilRates.isEmpty() && + oWell.m_vecGasRates.isEmpty() && + oWell.m_vecWaterRates.isEmpty() && + oWell.m_eFlowSchedulePhase == PHASE_UNKNOWN && + oWell.m_nFlowSectionIndex == 0 && + !oWell.m_bUseOilRate && !oWell.m_bUseGasRate && + !oWell.m_bUseWaterRate; + } + + const int nSegmentCount = oWell.m_vecFlowDurations.size(); + if(nSegmentCount <= 0 || + oWell.m_vecOilRates.size() != nSegmentCount || + oWell.m_vecGasRates.size() != nSegmentCount || + oWell.m_vecWaterRates.size() != nSegmentCount || + !isFiniteVector(oWell.m_vecFlowDurations) || + !isFiniteVector(oWell.m_vecOilRates) || + !isFiniteVector(oWell.m_vecGasRates) || + !isFiniteVector(oWell.m_vecWaterRates) || + oWell.m_nFlowSectionIndex < 1 || + oWell.m_nFlowSectionIndex > nSegmentCount) + { + return false; + } + + // 参考相必须是本次求解实际启用的相,历史流量图才能与对数曲线一致。 + return (oWell.m_eFlowSchedulePhase == PHASE_Oil && + oWell.m_bUseOilRate) || + (oWell.m_eFlowSchedulePhase == PHASE_Gas && + oWell.m_bUseGasRate) || + (oWell.m_eFlowSchedulePhase == PHASE_Water && + oWell.m_bUseWaterRate); +} + bool areReservoirParametersFinite( const nmPebiResultReservoirParameters& oParameters) { @@ -541,9 +580,18 @@ bool nmPebiResultSnapshotBuilder::takeWell( qSwap(oTarget.m_sWellCode, oWell.m_sWellCode); qSwap(oTarget.m_sWellName, oWell.m_sWellName); qSwap(oTarget.m_sGaugeInputSha1, oWell.m_sGaugeInputSha1); + qSwap(oTarget.m_vecFlowDurations, oWell.m_vecFlowDurations); + qSwap(oTarget.m_vecOilRates, oWell.m_vecOilRates); + qSwap(oTarget.m_vecGasRates, oWell.m_vecGasRates); + qSwap(oTarget.m_vecWaterRates, oWell.m_vecWaterRates); oTarget.m_eWellType = oWell.m_eWellType; oTarget.m_eWellCategory = oWell.m_eWellCategory; oTarget.m_eWellMode = oWell.m_eWellMode; + oTarget.m_eFlowSchedulePhase = oWell.m_eFlowSchedulePhase; + oTarget.m_nFlowSectionIndex = oWell.m_nFlowSectionIndex; + oTarget.m_bUseOilRate = oWell.m_bUseOilRate; + oTarget.m_bUseGasRate = oWell.m_bUseGasRate; + oTarget.m_bUseWaterRate = oWell.m_bUseWaterRate; oTarget.m_oLocation = oWell.m_oLocation; oTarget.m_bHasPerforation = oWell.m_bHasPerforation; oTarget.m_bHasSkin = oWell.m_bHasSkin; @@ -569,6 +617,11 @@ bool nmPebiResultSnapshotBuilder::takeWell( oWell.m_eWellType = Unknow_Well; oWell.m_eWellCategory = NM_WellCategory_Unknown; oWell.m_eWellMode = NM_CaseWell_Observation; + oWell.m_eFlowSchedulePhase = PHASE_UNKNOWN; + oWell.m_nFlowSectionIndex = 0; + oWell.m_bUseOilRate = false; + oWell.m_bUseGasRate = false; + oWell.m_bUseWaterRate = false; oWell.m_oLocation = QPointF(); oWell.m_bHasPerforation = false; oWell.m_bHasSkin = false; @@ -762,6 +815,11 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError) return fail(QString("PEBI result well curves are invalid: %1") .arg(oWell.m_sWellCode), pError); } + if(!isWellFlowScheduleValid(oWell)) + { + return fail(QString("PEBI result well flow schedule is invalid: %1") + .arg(oWell.m_sWellCode), pError); + } setWellIds.insert(oWell.m_sWellInstanceId); setWellCodes.insert(oWell.m_sWellCode); } @@ -815,7 +873,11 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError) { const QString& sWellInstanceId = m_pCandidate->m_listDisplayWellInstanceIds[nIndex]; + const nmPebiResultWellSnapshot* pDisplayWell = + m_pCandidate->findWell(sWellInstanceId); if(!setWellIds.contains(sWellInstanceId) || + pDisplayWell == NULL || + pDisplayWell->m_eWellMode != NM_CaseWell_RateControlled || setDisplayWellIds.contains(sWellInstanceId)) { return fail("PEBI display well list is invalid.", pError); diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp index e301448..8c51c3b 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotSerializer.cpp @@ -27,6 +27,7 @@ namespace { const char g_aPressureMagic[8] = { 'N', 'M', 'P', 'R', 'E', 'S', '3', 0 }; const char g_aCurvesMagic[8] = { 'N', 'M', 'C', 'U', 'R', 'V', '3', 0 }; const quint32 g_nBinaryFormatVersion = 1; +const int g_nSnapshotFormatVersion = 4; const quint32 g_nEndianMarker = 0x01020304u; const quint64 g_nMaximumCellCount = 50000000ull; const quint32 g_nMaximumFrameCount = 10000u; @@ -533,7 +534,8 @@ bool parseSnapshotJson( if(!oDocument.IsObject() || !oDocument.HasMember("SnapshotFormatVersion") || !oDocument["SnapshotFormatVersion"].IsInt() || - oDocument["SnapshotFormatVersion"].GetInt() != 3 || + oDocument["SnapshotFormatVersion"].GetInt() != + g_nSnapshotFormatVersion || !oDocument.HasMember("ResultCellCount") || !oDocument["ResultCellCount"].IsUint64() || !oDocument.HasMember("PressureFrameCount") || @@ -700,8 +702,9 @@ bool nmPebiResultSnapshotSerializer::save( oDocument.SetObject(); rapidjson::Document::AllocatorType& oAllocator = oDocument.GetAllocator(); - // v3 强制保存每口结果井的 Gauge 输入签名。 - oDocument.AddMember("SnapshotFormatVersion", 3, oAllocator); + // v4 在井目录中增加求解时冻结的完整流量制度,不兼容旧快照。 + oDocument.AddMember("SnapshotFormatVersion", + g_nSnapshotFormatVersion, oAllocator); oDocument.AddMember("SnapshotId", toJsonString(pSnapshot->m_sSnapshotId, oAllocator), oAllocator); oDocument.AddMember("GridInputRevision", @@ -758,6 +761,21 @@ bool nmPebiResultSnapshotSerializer::save( oJson.AddMember("WellMode", static_cast(oWell.m_eWellMode), oAllocator); oJson.AddMember("GaugeInputSha1", toJsonString( oWell.m_sGaugeInputSha1, oAllocator), oAllocator); + oJson.AddMember("FlowSchedulePhase", static_cast( + oWell.m_eFlowSchedulePhase), oAllocator); + oJson.AddMember("FlowSectionIndex", + oWell.m_nFlowSectionIndex, oAllocator); + oJson.AddMember("UseOilRate", oWell.m_bUseOilRate, oAllocator); + oJson.AddMember("UseGasRate", oWell.m_bUseGasRate, oAllocator); + oJson.AddMember("UseWaterRate", oWell.m_bUseWaterRate, oAllocator); + addDoubleVector(oJson, "FlowDurations", + oWell.m_vecFlowDurations, oAllocator); + addDoubleVector(oJson, "OilRates", + oWell.m_vecOilRates, oAllocator); + addDoubleVector(oJson, "GasRates", + oWell.m_vecGasRates, oAllocator); + addDoubleVector(oJson, "WaterRates", + oWell.m_vecWaterRates, oAllocator); oJson.AddMember("X", oWell.m_oLocation.x(), oAllocator); oJson.AddMember("Y", oWell.m_oLocation.y(), oAllocator); oJson.AddMember("HasPerforation", oWell.m_bHasPerforation, oAllocator); @@ -1064,6 +1082,7 @@ bool nmPebiResultSnapshotSerializer::load( int nWellType = 0; int nWellCategory = 0; int nWellMode = 0; + int nFlowSchedulePhase = 0; if(!oJson.IsObject() || !oJson.HasMember("WellInstanceId") || !oJson["WellInstanceId"].IsString() || @@ -1075,6 +1094,24 @@ bool nmPebiResultSnapshotSerializer::load( !readRequiredInt(oJson, "WellType", nWellType) || !readRequiredInt(oJson, "WellCategory", nWellCategory) || !readRequiredInt(oJson, "WellMode", nWellMode) || + !readRequiredInt(oJson, "FlowSchedulePhase", + nFlowSchedulePhase) || + !readRequiredInt(oJson, "FlowSectionIndex", + oWell.m_nFlowSectionIndex) || + !readRequiredBool(oJson, "UseOilRate", + oWell.m_bUseOilRate) || + !readRequiredBool(oJson, "UseGasRate", + oWell.m_bUseGasRate) || + !readRequiredBool(oJson, "UseWaterRate", + oWell.m_bUseWaterRate) || + !readDoubleVector(oJson, "FlowDurations", + oWell.m_vecFlowDurations) || + !readDoubleVector(oJson, "OilRates", + oWell.m_vecOilRates) || + !readDoubleVector(oJson, "GasRates", + oWell.m_vecGasRates) || + !readDoubleVector(oJson, "WaterRates", + oWell.m_vecWaterRates) || !readRequiredDouble(oJson, "X", oWell.m_oLocation.rx()) || !readRequiredDouble(oJson, "Y", oWell.m_oLocation.ry()) || !readRequiredBool(oJson, "HasPerforation", oWell.m_bHasPerforation) || @@ -1099,6 +1136,8 @@ bool nmPebiResultSnapshotSerializer::load( oWell.m_eWellCategory = static_cast( nWellCategory); oWell.m_eWellMode = static_cast(nWellMode); + oWell.m_eFlowSchedulePhase = static_cast( + nFlowSchedulePhase); if(setJsonWellIds.contains(oWell.m_sWellInstanceId) || !mapCurves.contains(oWell.m_sWellInstanceId)) { diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index b81b67c..eff98bc 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -64,6 +64,8 @@ #include "nmWxGeoRefDlg.h" #include "iSubWndFitting.h" +#include "iGuiPlotPF.h" +#include "ZxObjCurveFlow.h" #include "iAnalRun.h" #include @@ -99,6 +101,25 @@ namespace // QPointer会在QObject销毁后自动变空,避免保留已经释放的线程指针。 QPointer s_pRunningSolverTask; QPointer s_pPendingSolverWindow; + +// 历史窗口仍是单流量曲线结构,因此显示与双对数、半对数后处理一致的制度参考相。 +const QVector* resultReferenceRatesOf( + const nmPebiResultWellSnapshot* pWell) +{ + if(pWell == NULL) { + return NULL; + } + switch(pWell->m_eFlowSchedulePhase) { + case PHASE_Oil: + return pWell->m_bUseOilRate ? &pWell->m_vecOilRates : NULL; + case PHASE_Gas: + return pWell->m_bUseGasRate ? &pWell->m_vecGasRates : NULL; + case PHASE_Water: + return pWell->m_bUseWaterRate ? &pWell->m_vecWaterRates : NULL; + default: + return NULL; + } +} } nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) : @@ -2772,6 +2793,51 @@ bool nmSubWndMain::refreshPebiResultCurves( vecResultPressure, false); pSubWndFit->adjustFitSubPlotBy( FSRT_Hist, vecResultPressure, true, &sErrorMessage); + + // 压力和流量属于同一口结果井;切井时必须一起替换,避免下图残留上一口井制度。 + const QVector* pReferenceRates = + resultReferenceRatesOf(pWell); + iCurveDesc oFlowCurve; + oFlowCurve.m_oCurveType = POT_CurveFlow; + oFlowCurve.m_sCurveName = s_HistorySouce_CurveF; + oFlowCurve.m_bLineVisible = true; + oFlowCurve.m_bPointVisible = false; + oFlowCurve.m_oPen.setColor(Qt::red); + if(pWell != NULL && pReferenceRates != NULL && + pReferenceRates->size() == pWell->m_vecFlowDurations.size()) { + // 快照只保存真实流量段;绘图对象仍按框架原始格式使用首个 + // (0,0) 占位点,一基流动段索引因此可以直接恢复而无需换算。 + oFlowCurve.m_vecX = pWell->m_vecFlowDurations; + oFlowCurve.m_vecY = *pReferenceRates; + oFlowCurve.m_vecX.prepend(0.0); + oFlowCurve.m_vecY.prepend(0.0); + } + QVector vecHistoryFlow; + vecHistoryFlow.append(oFlowCurve); + pSubWndFit->adjustFitSubPlotBy( + FSRT_Hist, vecHistoryFlow, true, &sErrorMessage); + + // 框架流动段索引与 ZxSegmentInfo 相同,均为一基。这里只更新只读结果图元, + // 阻断选择信号,不能让结果井切换反向修改当前分析井的流动段配置。 + QWidget* pHistoryWidget = pSubWndFit->getFitSubRstWxOf( + FSRT_Hist, false, &sErrorMessage); + iGuiPlotPF* pHistoryPlot = qobject_cast(pHistoryWidget); + if(pHistoryPlot != NULL) { + ZxObjCurveBase* pFlowObject = NULL; + if(pHistoryPlot->getChartDataObjOfPF(pFlowObject, false) && + pFlowObject != NULL) { + ZxObjCurveFlow* pFlowCurve = + dynamic_cast(pFlowObject); + if(pFlowCurve != NULL) { + const bool bSignalsBlocked = pFlowCurve->blockSignals(true); + pFlowCurve->setCurSegPtIndex( + pWell == NULL ? 0 : + pWell->m_nFlowSectionIndex); + pFlowCurve->blockSignals(bSignalsBlocked); + pHistoryPlot->runUpdate(); + } + } + } return true; }