diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index da15d6b..91a9ef4 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -2842,6 +2842,10 @@ Reason: %1
solver error
+
+ The solver currently supports oil, gas, and water single-phase models and the oil-water two-phase model only.
+ 当前求解器仅支持油、气、水单相模型和油水两相模型。
+
solver failed!
@@ -7185,6 +7189,79 @@ Average pressure in contour: %2 MPa
hh
MPaMPa
m³/dm³/d
+ Pressure Record:压力记录:
+ Flow Record:流量记录:
+ Display Phase:显示相:
+ (Invalid) (无效)
+ (Empty) (空)
+ (Corrupt) (损坏)
+ Oil油
+ Gas气
+ Water水
+ Check Data检查数据
+ Pressure/Flow Data Check压力流量数据检查
+ Close关闭
+ No well is bound to this page.当前页面未绑定井。
+ === Well ====== 井信息 ===
+ Well name: %1井名称:%1
+ Well code: %1井编码:%1
+ Well category: %1井别:%1
+ Reference flow phase: %1井别参考相:%1
+ Oil well油井
+ Gas well气井
+ Water well水井
+ Unknown未知
+ Usable可用
+ Empty空
+ Corrupt损坏
+ Invalid identity身份无效
+ (none)(无)
+ === Pressure records ====== 压力记录 ===
+ Total pressure records: %1压力记录总数:%1
+ Selected pressure gauge code: %1当前选中压力记录 Code:%1
+ [%1] Name: %2[%1] 名称:%2
+ Code: %1Code:%1
+ Time: %1测量时间:%1
+ Status: %1状态:%1
+ Point count: %1点数:%1
+ No pressure points.无压力点。
+ First point = %1, last point = %2首点 = %1,末点 = %2
+ All points = %1全部点 = %1
+ === Flow records ====== 流量记录 ===
+ Total flow records: %1流量记录总数:%1
+ Selected flow gauge code: %1当前选中流量记录 Code:%1
+ Current display phase: %1当前显示相:%1
+ Multiphase: %1多相:%1
+ Yes是
+ No否
+ Own flow segment index (1-based): %1自身流动段索引(一基):%1
+ no data无数据
+ raw points = %1, real segments = %2, total duration = %3原始点数 = %1,真实段数 = %2,累计时长 = %3
+ first point is a (0,0) placeholder首个点为 (0,0) 占位点
+ Phase time consistency: %1相间时间一致性:%1
+ less than two phases with real data, no comparison有真实数据的相少于两个,不比较
+ segment counts differ: %1段数不一致:%1
+ segment counts equal (%1), but segment %2 time differs: %3 = %4, %5 = %6段数一致(%1),但第 %2 段时间不一致:%3 = %4,%5 = %6
+ segment counts equal (%1) and all segment times identical段数一致(%1),且每段时间完全一致
+ Oil + Gas油 + 气
+ Oil + Water油 + 水
+ Gas + Water气 + 水
+ Oil + Gas + Water油 + 气 + 水
+ None无
+ Other (%1)其他(%1)
+ === Current flow-segment multiphase data ====== 当前流动段分析多相数据 ===
+ Current fitting primary well code: %1当前流动段分析主井 Code:%1
+ Snapshot belongs to edited well: %1该快照是否属于正在编辑的井:%1
+ This snapshot belongs to the current fitting primary well and does not override the edited other well.该快照属于当前流动段分析主井,不会覆盖正在编辑的其他井。
+ Snapshot available: %1快照可获取:%1
+ PVT phase type: %1PVT 相态:%1
+ Segment multiphase flag: %1流动段多相标志:%1
+ Flow curve name: %1流量曲线名称:%1
+ Matrix vector count: %1矩阵向量数:%1
+ Vector sizes: %1各向量长度:%1
+ Four-vector shape valid: %1四向量结构有效:%1
+ Data rows (time, oil, gas, water):数据行(时间、油、气、水):
+ ... %1 additional rows omitted ... 另有 %1 行未显示
nmReservoirParameterPage
diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
index 3a19c92..4f2b7a2 100644
--- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
+++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h
@@ -44,7 +44,10 @@ struct nmPebiWellInputSnapshot
QString m_sWellCode; ///< 项目内稳定井编码。
QString m_sWellName; ///< 捕获时井名称。
QString m_sWellInstanceId; ///< 完整场结果使用的稳定井 UUID。
- QVector m_vecFlowPoints; ///< 求解使用的流量制度。
+ QVector m_vecFlowDurations; ///< 去除框架占位行后的公共流动段时长。
+ QVector m_vecOilRates; ///< 与公共流动段逐项对应的油相流量。
+ QVector m_vecGasRates; ///< 与公共流动段逐项对应的气相流量。
+ QVector m_vecWaterRates; ///< 与公共流动段逐项对应的水相流量。
NM_WELL_CATEGORY m_eWellCategory; ///< 求解时冻结的油井、气井或水井井别。
/** @brief 求解时冻结的历史压力曲线。 */
QVector > m_vecHistoryPressure;
@@ -77,6 +80,7 @@ struct nmPebiGridInputSnapshot
nmPebiGridInputSnapshot()
: m_nGridInputRevision(0),
m_bCaptured(false),
+ m_bRequireSolverInput(false),
m_bValid(false)
{
}
@@ -88,6 +92,7 @@ struct nmPebiGridInputSnapshot
QString m_sLicensePath; ///< 网格 DLL 授权文件路径副本。
quint64 m_nGridInputRevision; ///< 捕获时的几何输入版本。
bool m_bCaptured; ///< DataManager 值是否已在所属线程完整捕获。
+ bool m_bRequireSolverInput; ///< 是否还需组装产量、PVT 等求解输入。
bool m_bValid; ///< 后台场景准备是否完成,可否提交给网格 DLL。
};
@@ -119,10 +124,11 @@ public:
nmCalculationPebiGrid();
~nmCalculationPebiGrid();
- /** @brief 在调用线程从 DataManager 复制一份完整且不含对象指针的网格输入。 */
+ /** @brief 在调用线程从 DataManager 复制一份完整且不含对象指针的输入快照。 */
bool captureInputSnapshot(nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot,
- bool bDeferPreparation = false);
+ bool bDeferPreparation = false,
+ bool bRequireSolverInput = true);
/** @brief 初始化手工求解的分批网格快照,只在 DataManager 所属线程调用。 */
bool beginManualInputSnapshot(nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot);
diff --git a/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h b/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h
index 28de168..19c0ce1 100644
--- a/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h
+++ b/Include/nmNum/nmData/nmDataAnalyzeContextProvider.h
@@ -18,6 +18,20 @@ class NM_DATA_EXPORT nmDataAnalyzeContextProvider {
// 获取当前分析窗口选中的流动段索引
virtual bool getCurrentSegmentIndex(void* pFitting, int& nIndexF) = 0;
+ // 获取当前流动段分析实际使用的压力、流量 Gauge Code。
+ virtual bool getCurrentSegmentGaugeCodes(
+ void* pFitting,
+ QString& sPressureCurveName,
+ QString& sFlowCurveName) = 0;
+
+ // 获取当前流动段分析保存的多相流量矩阵。
+ // 数据按纵向向量组织:[0]时间、[1]油、[2]气、[3]水;这里只复制窗口快照,不修改业务数据。
+ virtual bool getCurrentSegmentMultiPhaseData(
+ void* pFitting,
+ bool& bMultiPhase,
+ QString& sFlowCurveName,
+ VVecDouble& vvecMultiPhaseData) = 0;
+
// 获取基础数据中的PVT相态类型
virtual bool getBasicPft(void* pFitting, PvtFluidType& eType) = 0;
diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h
index 57409d1..85e3a9a 100644
--- a/Include/nmNum/nmData/nmDataAnalyzeManager.h
+++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h
@@ -123,6 +123,24 @@ enum NM_SOLVER_MODEL_TYPE {
SMT_Oil_Gas_Water_ThreePhase = 10
};
+/** @brief 当前 PEBI 接入层已经开放的求解模型范围。 */
+inline bool nmIsSupportedPebiSolverModel(
+ NM_SOLVER_MODEL_TYPE eSolverModelType)
+{
+ switch(eSolverModelType) {
+ case SMT_Oil_ConstPvt:
+ case SMT_Oil_VariablePvt:
+ case SMT_Water_ConstPvt:
+ case SMT_Water_VariablePvt:
+ case SMT_Gas_VariablePvt:
+ case SMT_Gas_PseudoPressure:
+ case SMT_Oil_Water_TwoPhase:
+ return true;
+ default:
+ return false;
+ }
+}
+
/** @brief 判断求解模型是否包含指定工程井别对应的流量相。 */
inline bool nmSolverModelSupportsWellCategory(
NM_SOLVER_MODEL_TYPE eSolverModelType,
@@ -243,13 +261,16 @@ public:
NM_WELL_CATEGORY& eWellCategory);
/**
- * @brief 将枚举到的记录列表写入数值井,并按默认规则选中压力/流量记录。
+ * @brief 将枚举到的记录列表写入数值井,并优先恢复指定的压力、流量记录。
* @note 找不到任何可用记录时仍会写入列表,只是选中 Code 为空,不阻止建井。
*/
static void applyGaugeRecordsToWell(
nmDataWellBase* pWellData,
const QVector& vecPressureRecords,
- const QVector& vecFlowRecords);
+ const QVector& vecFlowRecords,
+ const QString& sPreferredPressureGaugeCode = QString(),
+ const QString& sPreferredFlowGaugeCode = QString(),
+ int nPreferredFlowIndex = 0);
// 井的接口
// 添加井
diff --git a/Include/nmNum/nmData/nmDataGaugeRecord.h b/Include/nmNum/nmData/nmDataGaugeRecord.h
index 798869a..c9d46ad 100644
--- a/Include/nmNum/nmData/nmDataGaugeRecord.h
+++ b/Include/nmNum/nmData/nmDataGaugeRecord.h
@@ -26,8 +26,8 @@ struct nmPressureGaugeRecord {
/**
* @brief 工程井下一条流量测量记录的只读快照。
- * @note 单相记录只解析主块并按井别归入对应相,忽略可能残留的 Ex2/Ex3;
- * 多相记录分别解析主块、Ex2、Ex3 为油、气、水三相。
+ * @note 单相记录解析 GaugeData 并按井别归入对应相;多相记录从
+ * GaugeDataEx2 的 N 行数据中按 [段时长、油、气、水] 解析全部相。
*/
struct nmFlowGaugeRecord {
QString sGaugeCode; // 对应 ZxDataGaugeF::getCode(),选择持久化键
diff --git a/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h b/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h
index 1b26f1f..15f1ce1 100644
--- a/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h
+++ b/Include/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.h
@@ -13,6 +13,19 @@ class NM_SUB_WND_EXPORT nmSubWndDataAnalyzeController : public nmDataAnalyzeCont
// 获取当前分析窗口选中的流动段索引
bool getCurrentSegmentIndex(void* pFitting, int& nIndexF);
+ // 获取当前流动段分析实际使用的压力、流量 Gauge Code
+ bool getCurrentSegmentGaugeCodes(
+ void* pFitting,
+ QString& sPressureCurveName,
+ QString& sFlowCurveName);
+
+ // 获取当前流动段分析中的多相流量矩阵
+ bool getCurrentSegmentMultiPhaseData(
+ void* pFitting,
+ bool& bMultiPhase,
+ QString& sFlowCurveName,
+ VVecDouble& vvecMultiPhaseData);
+
// 获取基础数据中的PVT相态类型
bool getBasicPft(void* pFitting, PvtFluidType& eType);
diff --git a/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
index f87fa2a..6bb9304 100644
--- a/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
+++ b/Include/nmNum/nmSubWxs/nmWellPressureFlowPage.h
@@ -14,6 +14,7 @@ class iUnitItem;
class nmDataWellBase;
class nmWxPressFlowChartWidget;
class QComboBox;
+class QPushButton;
class QSplitter;
class QStackedWidget;
class QTableWidget;
@@ -67,6 +68,9 @@ private slots:
/** @brief 切换显示相下拉框,仅重新按新相取点刷新视图,不改变井数据。 */
void slotDisplayPhaseChanged(int nIndex);
+ /** @brief 临时诊断入口:弹出只读窗口展示当前井全部压力/流量记录摘要,不修改业务数据。 */
+ void slotCheckDataClicked();
+
private:
/** @brief 创建左右分栏、上下图表和只读流量表格。 */
void createUi();
@@ -136,6 +140,9 @@ private:
/** @brief 清空数据副本和视图,但保留当前单位及井绑定。 */
void resetDataViews();
+ /** @brief 即时从井对象重新读取全部记录并生成只读诊断文本,约15位有效数字。 */
+ QString buildDiagnosticText() const;
+
nmDataWellBase* m_pWell; ///< 当前工作副本,不拥有。
QStackedWidget* m_pContentStack;
QSplitter* m_pHorizontalSplitter;
@@ -165,6 +172,7 @@ private:
QComboBox* m_pFlowGaugeCombo; ///< 流量记录下拉框,itemData 存 Gauge Code。
QComboBox* m_pDisplayPhaseCombo; ///< 显示相下拉框,itemData 存 NM_PHASE_TYPE。
QWidget* m_pDisplayPhaseContainer; ///< 承载显示相下拉框与标签,整体隐藏用。
+ QPushButton* m_pCheckDataButton; ///< 临时“检查数据”诊断按钮,只读展示用。
NM_PHASE_TYPE m_eDisplayPhase; ///< 当前预览的流量相,纯展示状态,不写回井对象。
QVector m_vecFlowGaugeRecords; ///< 每次填充流量下拉框时缓存的记录列表,供 currentFlowRecord() 安全返回内部指针。
};
diff --git a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
index 0efc62f..c064b08 100644
--- a/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
+++ b/Include/nmNum/nmSubWxs/nmWxIncludeOtherWells.h
@@ -23,7 +23,7 @@ struct WellDataRow {
bool m_bIsIncluded; ///< 是否被用户勾选为其他主动井。
bool m_bIsPrimary; ///< 主井防御标记;主井正常情况下不会进入本表。
bool m_bCanCalculate; ///< 当前井型和几何参数是否可进入 PEBI 模型。
- NM_WELL_CATEGORY m_eWellCategory; ///< 工程井井别,决定哪一列显示流量。
+ NM_WELL_CATEGORY m_eWellCategory; ///< 工程井井别,用于单相记录归相及求解模型匹配。
NM_CASE_WELL_MODE m_eMode; ///< 本列表固定保存有产量主动井角色。
QString m_sOilProd; ///< 油产量列的摘要文本。
QString m_sGasProd; ///< 气产量列的摘要文本。
diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
index 0bd5288..4b6b5c8 100644
--- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp
@@ -176,6 +176,23 @@ bool isFiniteSolverNumber(double value)
#endif
}
+// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时按井别取参考相。
+// PEBI 主求解已经在场景中接收油、气、水三相全部流量。
+const QVector* referenceRatesOf(
+ const nmPebiWellInputSnapshot& oWellInput)
+{
+ switch(oWellInput.m_eWellCategory) {
+ case NM_WellCategory_Oil:
+ return &oWellInput.m_vecOilRates;
+ case NM_WellCategory_Gas:
+ return &oWellInput.m_vecGasRates;
+ case NM_WellCategory_Water:
+ return &oWellInput.m_vecWaterRates;
+ default:
+ return nullptr;
+ }
+}
+
bool isDisplayResultWell(
const nmDataNumericalAnalysisCase* pAnalysisCase,
const QString& sWellCode)
@@ -215,7 +232,7 @@ bool captureResultWellMetadata(
// 无产量井仍作为观察井保留在求解槽位和快照元数据中,但不进入结果下拉框。
oWellInput.m_bDisplayResultWell = bDisplayResultWell &&
oWellInput.m_bRateControlled &&
- !oWellInput.m_vecFlowPoints.isEmpty();
+ !oWellInput.m_vecFlowDurations.isEmpty();
nmDataVerticalFracturedWell* pVerticalFracturedWell =
dynamic_cast(pWellData);
@@ -1512,12 +1529,17 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
return false;
}
+ const QVector* pReferenceRates =
+ referenceRatesOf(oWellInput);
const bool bRateControlled =
oWellInput.m_bRateControlled &&
- !oWellInput.m_vecFlowPoints.isEmpty() &&
+ pReferenceRates != nullptr &&
+ pReferenceRates->size() ==
+ oWellInput.m_vecFlowDurations.size() &&
+ !oWellInput.m_vecFlowDurations.isEmpty() &&
oWellInput.m_nFlowSectionIndex >= 1 &&
oWellInput.m_nFlowSectionIndex <=
- oWellInput.m_vecFlowPoints.size();
+ oWellInput.m_vecFlowDurations.size();
vvecLogLog.clear();
vvecSemiLog.clear();
@@ -1552,16 +1574,15 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
}
// 准备流量段数据
- const QVector& vecTimeQ =
- oWellInput.m_vecFlowPoints;
// 输入快照已经移除可选 (0,0) 占位点,此处逐段传给曲线 DLL。
- const int nTimeNumQ = vecTimeQ.size();
+ const int nTimeNumQ =
+ oWellInput.m_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] = oWellInput.m_vecFlowDurations[i];
+ q[i] = (*pReferenceRates)[i];
}
// 调用外部 DLL 计算双对数曲线
diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
index added2b..7f2d963 100644
--- a/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationPebiGrid.cpp
@@ -206,6 +206,73 @@ void fillScenePseudoPressureTable(nmDataBinaryTools::NM_PEBI_SCENE& scene,
}
}
+// 把当前所选流量记录整理成求解器的公共段时长和三相数组。
+// 单相记录缺少的相只补同轴零值;多相记录的三相数值不做业务修正。
+bool captureWellFlowSchedule(
+ nmDataWellBase* pWellData,
+ nmPebiWellInputSnapshot& oWellInput)
+{
+ oWellInput.m_vecFlowDurations.clear();
+ oWellInput.m_vecOilRates.clear();
+ oWellInput.m_vecGasRates.clear();
+ oWellInput.m_vecWaterRates.clear();
+ if(pWellData == nullptr) {
+ return false;
+ }
+
+ const QVector vecOilPoints =
+ pWellData->getFlowSegmentPoints(PHASE_Oil);
+ const QVector vecGasPoints =
+ pWellData->getFlowSegmentPoints(PHASE_Gas);
+ const QVector vecWaterPoints =
+ pWellData->getFlowSegmentPoints(PHASE_Water);
+
+ const QVector* pTimePoints = nullptr;
+ switch(pWellData->getReferenceFlowPhase()) {
+ case PHASE_Oil:
+ pTimePoints = &vecOilPoints;
+ break;
+ case PHASE_Gas:
+ pTimePoints = &vecGasPoints;
+ break;
+ case PHASE_Water:
+ pTimePoints = &vecWaterPoints;
+ break;
+ default:
+ break;
+ }
+ if(pTimePoints == nullptr || pTimePoints->isEmpty()) {
+ pTimePoints = !vecOilPoints.isEmpty() ? &vecOilPoints
+ : (!vecGasPoints.isEmpty() ? &vecGasPoints
+ : (!vecWaterPoints.isEmpty() ? &vecWaterPoints : nullptr));
+ }
+ if(pTimePoints == nullptr || pTimePoints->isEmpty()) {
+ return false;
+ }
+
+ const int nSegmentCount = pTimePoints->size();
+ if((!vecOilPoints.isEmpty() && vecOilPoints.size() != nSegmentCount) ||
+ (!vecGasPoints.isEmpty() && vecGasPoints.size() != nSegmentCount) ||
+ (!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);
+ for(int nIndex = 0; nIndex < nSegmentCount; ++nIndex) {
+ oWellInput.m_vecFlowDurations.append((*pTimePoints)[nIndex].x());
+ oWellInput.m_vecOilRates.append(
+ vecOilPoints.isEmpty() ? 0.0 : vecOilPoints[nIndex].y());
+ oWellInput.m_vecGasRates.append(
+ vecGasPoints.isEmpty() ? 0.0 : vecGasPoints[nIndex].y());
+ oWellInput.m_vecWaterRates.append(
+ vecWaterPoints.isEmpty() ? 0.0 : vecWaterPoints[nIndex].y());
+ }
+ return true;
+}
+
}
#ifdef Q_OS_WIN
@@ -999,6 +1066,8 @@ bool nmCalculationPebiGrid::beginManualInputSnapshot(
return false;
}
+ oSnapshot.m_bRequireSolverInput = true;
+
const nmDataNumericalAnalysisCase* pAnalysisCase =
pDataManager->getNumericalAnalysisCase();
if(pAnalysisCase == nullptr) {
@@ -1104,7 +1173,6 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
oWellInput.m_sWellCode = pWellData->getWellCode();
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_eWellCategory = pWellData->getWellCategory();
- oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
@@ -1118,8 +1186,10 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
nWellMode == static_cast(NM_CaseWell_RateControlled);
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
(oWellInput.m_bRateControlled &&
- (oWellInput.m_vecFlowPoints.isEmpty() ||
- !pWellData->hasValidFlowSectionIndex()))) {
+ (!captureWellFlowSchedule(pWellData, oWellInput) ||
+ oWellInput.m_nFlowSectionIndex < 1 ||
+ oWellInput.m_nFlowSectionIndex >
+ oWellInput.m_vecFlowDurations.size()))) {
return false;
}
@@ -1257,7 +1327,8 @@ bool nmCalculationPebiGrid::finishManualInputSnapshot(
bool nmCalculationPebiGrid::captureInputSnapshot(
nmDataAnalyzeManager* pDataManager,
nmPebiGridInputSnapshot& oSnapshot,
- bool bDeferPreparation)
+ bool bDeferPreparation,
+ bool bRequireSolverInput)
{
// 使用具名常量触发复制赋值,兼容 Qt 4.8 配套的 VS2010 运行库 ABI。
const nmPebiGridInputSnapshot oEmptySnapshot;
@@ -1265,6 +1336,7 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
if(pDataManager == nullptr) {
return false;
}
+ oSnapshot.m_bRequireSolverInput = bRequireSolverInput;
// DataManager 及井对象都由所属线程维护。快照只允许在该线程创建,后台任务
// 随后只读取复制出的 STL/Qt 值类型,不能再次访问这些可变对象。
@@ -1344,6 +1416,15 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
return false;
}
+ // 独立建网到此已经取得 DLL 实际消费的全部几何输入和井槽位顺序。
+ // 不再读取流量、流动段、PVT、储层或时间步,避免建网依赖求解数据状态。
+ if(!oSnapshot.m_bRequireSolverInput) {
+ oSnapshot.m_sLicensePath = pDataManager->getLicensePath();
+ oSnapshot.m_bCaptured = true;
+ return bDeferPreparation
+ ? true : prepareInputSnapshot(oSnapshot);
+ }
+
// 第五步:建立 WellCode 索引并一次复制每口井,避免五十口井时反复线性查找。
QHash mapWellsByCode;
const QVector vecAllWells =
@@ -1382,7 +1463,6 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oWellInput.m_bRealWell = true;
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_eWellCategory = pWellData->getWellCategory();
- oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
@@ -1396,14 +1476,18 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oWellInput.m_bRateControlled =
mapWellModes.value(oWellRef.m_sWellCode) ==
static_cast(NM_CaseWell_RateControlled);
- if(!nmIsValidWellCategory(oWellInput.m_eWellCategory)) {
+ if(oSnapshot.m_bRequireSolverInput &&
+ !nmIsValidWellCategory(oWellInput.m_eWellCategory)) {
qWarning() << "Solver well has an invalid category:"
<< oWellRef.m_sWellCode;
return false;
}
- if(oWellInput.m_bRateControlled &&
- (oWellInput.m_vecFlowPoints.isEmpty() ||
- !pWellData->hasValidFlowSectionIndex())) {
+ if(oSnapshot.m_bRequireSolverInput &&
+ oWellInput.m_bRateControlled &&
+ (!captureWellFlowSchedule(pWellData, oWellInput) ||
+ oWellInput.m_nFlowSectionIndex < 1 ||
+ oWellInput.m_nFlowSectionIndex >
+ oWellInput.m_vecFlowDurations.size())) {
qWarning() << "Rate-controlled well has no valid rate schedule:"
<< oWellRef.m_sWellCode;
return false;
@@ -1483,12 +1567,21 @@ bool nmCalculationPebiGrid::prepareInputSnapshot(
{
oSnapshot.m_bValid = false;
if(!oSnapshot.m_bCaptured ||
- oSnapshot.m_vecWellInputs.size() !=
- oSnapshot.m_vecSolverWellOrder.size() ||
isCancellationRequested(pCancelRequested)) {
return false;
}
+ // 独立建网只消费几何输入,不因流量记录或求解参数不可用而失败。
+ if(!oSnapshot.m_bRequireSolverInput) {
+ oSnapshot.m_bValid = true;
+ return true;
+ }
+
+ if(oSnapshot.m_vecWellInputs.size() !=
+ oSnapshot.m_vecSolverWellOrder.size()) {
+ return false;
+ }
+
return buildPebiScene(oSnapshot, pCancelRequested);
}
@@ -1529,9 +1622,14 @@ bool nmCalculationPebiGrid::buildPebiScene(
oScene.wellName.push_back(vecWellInputs[nIndex].m_sWellName);
}
- // 第二步:按每口井冻结的井别填充产量制度;求解模型只负责相容性校验。
+ // 第二步:把冻结的公共时间轴及油、气、水流量原样写入求解器输入。
const NM_SOLVER_MODEL_TYPE eSolverModelType =
static_cast(oScene.solverType);
+ if(!nmIsSupportedPebiSolverModel(eSolverModelType)) {
+ qWarning() << "PEBI solver model is not supported:"
+ << static_cast(eSolverModelType);
+ return false;
+ }
oScene.Rate.t.resize(vecSolverWellOrder.size());
oScene.Rate.qo.resize(vecSolverWellOrder.size());
oScene.Rate.qg.resize(vecSolverWellOrder.size());
@@ -1555,45 +1653,48 @@ bool nmCalculationPebiGrid::buildPebiScene(
continue;
}
- if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
- !nmSolverModelSupportsWellCategory(
- eSolverModelType, oWellInput.m_eWellCategory) ||
- oWellInput.m_vecFlowPoints.isEmpty() ||
+ // 多相记录已经明确提供 qo/qg/qw,井别不再用于裁剪或拦截流量相。
+ // 此处只检查求解器数组能否按公共段时长逐项组装。
+ if(oWellInput.m_vecFlowDurations.isEmpty() ||
+ oWellInput.m_vecOilRates.size() !=
+ oWellInput.m_vecFlowDurations.size() ||
+ oWellInput.m_vecGasRates.size() !=
+ oWellInput.m_vecFlowDurations.size() ||
+ oWellInput.m_vecWaterRates.size() !=
+ oWellInput.m_vecFlowDurations.size() ||
oWellInput.m_nFlowSectionIndex < 1 ||
oWellInput.m_nFlowSectionIndex >
- oWellInput.m_vecFlowPoints.size()) {
- qWarning() << "Rate-controlled well input is incompatible with solver model:"
+ oWellInput.m_vecFlowDurations.size()) {
+ qWarning() << "Rate-controlled well has inconsistent rate schedule arrays:"
<< oWellInput.m_sWellCode;
return false;
}
std::vector vecTime;
- std::vector vecRate;
- vecTime.reserve(oWellInput.m_vecFlowPoints.size());
- vecRate.reserve(oWellInput.m_vecFlowPoints.size());
+ std::vector vecOil;
+ std::vector vecGas;
+ std::vector vecWater;
+ vecTime.reserve(oWellInput.m_vecFlowDurations.size());
+ vecOil.reserve(oWellInput.m_vecFlowDurations.size());
+ vecGas.reserve(oWellInput.m_vecFlowDurations.size());
+ vecWater.reserve(oWellInput.m_vecFlowDurations.size());
for(int nPointIndex = 0;
- nPointIndex < oWellInput.m_vecFlowPoints.size();
+ nPointIndex < oWellInput.m_vecFlowDurations.size();
++nPointIndex) {
if((nPointIndex % 256) == 0 &&
isCancellationRequested(pCancelRequested)) {
return false;
}
- vecTime.push_back(oWellInput.m_vecFlowPoints[nPointIndex].x());
- vecRate.push_back(oWellInput.m_vecFlowPoints[nPointIndex].y());
+ vecTime.push_back(oWellInput.m_vecFlowDurations[nPointIndex]);
+ vecOil.push_back(oWellInput.m_vecOilRates[nPointIndex]);
+ vecGas.push_back(oWellInput.m_vecGasRates[nPointIndex]);
+ vecWater.push_back(oWellInput.m_vecWaterRates[nPointIndex]);
}
oScene.Rate.t[nWellIndex] = vecTime;
- oScene.Rate.qo[nWellIndex].assign(vecRate.size(), 0.0);
- oScene.Rate.qg[nWellIndex].assign(vecRate.size(), 0.0);
- oScene.Rate.qw[nWellIndex].assign(vecRate.size(), 0.0);
-
- if(oWellInput.m_eWellCategory == NM_WellCategory_Gas) {
- oScene.Rate.qg[nWellIndex] = vecRate;
- } else if(oWellInput.m_eWellCategory == NM_WellCategory_Water) {
- oScene.Rate.qw[nWellIndex] = vecRate;
- } else {
- oScene.Rate.qo[nWellIndex] = vecRate;
- }
+ oScene.Rate.qo[nWellIndex] = vecOil;
+ oScene.Rate.qg[nWellIndex] = vecGas;
+ oScene.Rate.qw[nWellIndex] = vecWater;
}
// 第三步:复制井筒储集、表皮和流量段索引。
diff --git a/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp b/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp
index b5697fb..a6b1d9b 100644
--- a/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationPebiGridTask.cpp
@@ -15,11 +15,12 @@ nmCalculationPebiGridTask::nmCalculationPebiGridTask(
m_pDataManager->beginBackgroundUse();
m_bManagerUseActive = true;
- // 第一步:构造函数运行在主线程,此处一次性复制全部 Map 和求解输入。
+ // 第一步:构造函数运行在主线程,此处只复制建网所需的 Map 几何输入。
+ // 流量、PVT 等求解制度不可用时不能阻止独立建网。
// run() 启动后不得再读取 DataManager 中的井、断层或参数对象。
m_bSnapshotValid =
nmCalculationPebiGrid::getInstance()->captureInputSnapshot(
- m_pDataManager, m_oInputSnapshot);
+ m_pDataManager, m_oInputSnapshot, false, false);
}
}
diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
index 02a61a5..798d8c5 100644
--- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
+++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp
@@ -192,27 +192,137 @@ static NM_GAUGE_RECORD_STATUS readBlockAndClassify(
? NM_GaugeRecord_Usable : NM_GaugeRecord_Corrupt;
}
-// 多相流量记录:主块=油、Ex2=气、Ex3=水;任一非空块损坏则整条记录损坏,三块全空才是空记录。
+// 框架的 GaugeDataEx2 保存完整多相表,格式为 N 行 x 4 列;
+// 每行依次为 [段时长、油、气、水]。数值模块读取时拆成三组同轴流量点,
+// 不根据井别或非零值删改任一相的数据。
+static bool readMultiPhaseFlowTable(
+ const QByteArray& baGaugeData,
+ QVector& vecOilPoints,
+ QVector& vecGasPoints,
+ QVector& vecWaterPoints)
+{
+ vecOilPoints.clear();
+ vecGasPoints.clear();
+ vecWaterPoints.clear();
+ if(baGaugeData.isEmpty()) {
+ return true;
+ }
+
+ QByteArray baData = baGaugeData;
+ VVecVariant vvecRows;
+ if(!ZxBaHelper::convertBa2VVec(vvecRows, baData) ||
+ vvecRows.isEmpty()) {
+ return false;
+ }
+
+ const int nPointCount = vvecRows.size();
+ for(int nPointIndex = 0;
+ nPointIndex < nPointCount;
+ ++nPointIndex) {
+ if(vvecRows[nPointIndex].size() != 4) {
+ return false;
+ }
+ }
+
+ vecOilPoints.reserve(nPointCount);
+ vecGasPoints.reserve(nPointCount);
+ vecWaterPoints.reserve(nPointCount);
+ for(int nPointIndex = 0;
+ nPointIndex < nPointCount;
+ ++nPointIndex) {
+ bool bTimeOk = false;
+ bool bOilOk = false;
+ bool bGasOk = false;
+ bool bWaterOk = false;
+ const QVector& vecRow = vvecRows[nPointIndex];
+ const double dTime = vecRow[0].toDouble(&bTimeOk);
+ const double dOil = vecRow[1].toDouble(&bOilOk);
+ const double dGas = vecRow[2].toDouble(&bGasOk);
+ const double dWater = vecRow[3].toDouble(&bWaterOk);
+ if(!bTimeOk || !bOilOk || !bGasOk || !bWaterOk) {
+ vecOilPoints.clear();
+ vecGasPoints.clear();
+ vecWaterPoints.clear();
+ return false;
+ }
+
+ vecOilPoints.append(QPointF(dTime, dOil));
+ vecGasPoints.append(QPointF(dTime, dGas));
+ vecWaterPoints.append(QPointF(dTime, dWater));
+ }
+ return true;
+}
+
+// 多相流量记录只解析 GaugeDataEx2;GaugeDataEx3 不是水相数据来源。
static NM_GAUGE_RECORD_STATUS classifyMultiPhaseFlowRecord(
ZxDataGaugeF* pGaugeF,
QVector& vecOilPoints,
QVector& vecGasPoints,
QVector& vecWaterPoints)
{
- const QByteArray baOil = pGaugeF->getGaugeData();
- const QByteArray baGas = pGaugeF->getGaugeDataEx2();
- const QByteArray baWater = pGaugeF->getGaugeDataEx3();
+ const QByteArray baMultiPhase = pGaugeF->getGaugeDataEx2();
+ if(baMultiPhase.isEmpty()) {
+ return NM_GaugeRecord_Empty;
+ }
+ return readMultiPhaseFlowTable(
+ baMultiPhase,
+ vecOilPoints,
+ vecGasPoints,
+ vecWaterPoints)
+ ? NM_GaugeRecord_Usable : NM_GaugeRecord_Corrupt;
+}
- const bool bOilOk = readFlowGaugeBlock(baOil, vecOilPoints);
- const bool bGasOk = readFlowGaugeBlock(baGas, vecGasPoints);
- const bool bWaterOk = readFlowGaugeBlock(baWater, vecWaterPoints);
- if(!bOilOk || !bGasOk || !bWaterOk) {
- return NM_GaugeRecord_Corrupt;
+// 当前分析窗口的多相工作副本按列保存:[0]时间、[1]油、[2]气、[3]水。
+// 它只覆盖同一 Flow Gauge Code 的主井记录,不能套用到包含井。
+static bool applyCurrentMultiPhaseFlowData(
+ QVector& vecRecords,
+ const QString& sFlowGaugeCode,
+ const VVecDouble& vvecMultiPhaseData,
+ int nIndexF)
+{
+ if(sFlowGaugeCode.isEmpty() || vvecMultiPhaseData.size() != 4) {
+ return false;
}
- if(baOil.isEmpty() && baGas.isEmpty() && baWater.isEmpty()) {
- return NM_GaugeRecord_Empty;
+
+ const int nPointCount = vvecMultiPhaseData[0].size();
+ if(nPointCount <= 0 ||
+ vvecMultiPhaseData[1].size() != nPointCount ||
+ vvecMultiPhaseData[2].size() != nPointCount ||
+ vvecMultiPhaseData[3].size() != nPointCount) {
+ return false;
+ }
+
+ for(int nRecordIndex = 0;
+ nRecordIndex < vecRecords.size();
+ ++nRecordIndex) {
+ nmFlowGaugeRecord& oRecord = vecRecords[nRecordIndex];
+ if(oRecord.sGaugeCode != sFlowGaugeCode) {
+ continue;
+ }
+
+ oRecord.vecOilPoints.clear();
+ oRecord.vecGasPoints.clear();
+ oRecord.vecWaterPoints.clear();
+ oRecord.vecOilPoints.reserve(nPointCount);
+ oRecord.vecGasPoints.reserve(nPointCount);
+ oRecord.vecWaterPoints.reserve(nPointCount);
+ for(int nPointIndex = 0;
+ nPointIndex < nPointCount;
+ ++nPointIndex) {
+ const double dTime = vvecMultiPhaseData[0][nPointIndex];
+ oRecord.vecOilPoints.append(QPointF(
+ dTime, vvecMultiPhaseData[1][nPointIndex]));
+ oRecord.vecGasPoints.append(QPointF(
+ dTime, vvecMultiPhaseData[2][nPointIndex]));
+ oRecord.vecWaterPoints.append(QPointF(
+ dTime, vvecMultiPhaseData[3][nPointIndex]));
+ }
+ oRecord.bMultiPhase = true;
+ oRecord.eStatus = NM_GaugeRecord_Usable;
+ oRecord.nIndexF = nIndexF;
+ return true;
}
- return NM_GaugeRecord_Usable;
+ return false;
}
// 单相流量记录:只解析主块,并按井别把数据归入油、气或水相;忽略可能残留的 Ex2/Ex3。
@@ -355,22 +465,55 @@ static QString selectDefaultFlowGaugeCode(
return QString();
}
-// 把枚举到的记录列表写入数值井,并按默认规则选中压力/流量记录。
+// 把枚举到的记录列表写入数值井;只恢复仍然存在且可用的首选记录。
// 找不到任何可用记录时仍会写入(可能为空的)列表,只是选中 Code 为空——不阻止建井。
void nmDataAnalyzeManager::applyGaugeRecordsToWell(
nmDataWellBase* pWellData,
const QVector& vecPressureRecords,
- const QVector& vecFlowRecords)
+ const QVector& vecFlowRecords,
+ const QString& sPreferredPressureGaugeCode,
+ const QString& sPreferredFlowGaugeCode,
+ int nPreferredFlowIndex)
{
if(pWellData == nullptr) {
return;
}
pWellData->setPressureRecords(vecPressureRecords);
pWellData->setFlowRecords(vecFlowRecords);
- pWellData->selectPressureGaugeCode(
- selectDefaultPressureGaugeCode(vecPressureRecords));
- pWellData->selectFlowGaugeCode(
- selectDefaultFlowGaugeCode(vecFlowRecords));
+
+ QString sSelectedPressureGaugeCode =
+ selectDefaultPressureGaugeCode(vecPressureRecords);
+ for(int nIndex = 0; nIndex < vecPressureRecords.size(); ++nIndex) {
+ if(vecPressureRecords[nIndex].sGaugeCode ==
+ sPreferredPressureGaugeCode &&
+ vecPressureRecords[nIndex].eStatus == NM_GaugeRecord_Usable) {
+ sSelectedPressureGaugeCode = sPreferredPressureGaugeCode;
+ break;
+ }
+ }
+ pWellData->selectPressureGaugeCode(sSelectedPressureGaugeCode);
+
+ QString sSelectedFlowGaugeCode =
+ selectDefaultFlowGaugeCode(vecFlowRecords);
+ for(int nIndex = 0; nIndex < vecFlowRecords.size(); ++nIndex) {
+ if(vecFlowRecords[nIndex].sGaugeCode == sPreferredFlowGaugeCode &&
+ vecFlowRecords[nIndex].eStatus == NM_GaugeRecord_Usable) {
+ sSelectedFlowGaugeCode = sPreferredFlowGaugeCode;
+ break;
+ }
+ }
+ pWellData->selectFlowGaugeCode(sSelectedFlowGaugeCode);
+
+ // 每条流量记录可以有自己的段数。首选索引不适用于回退记录,
+ // 因此回退时统一选择该记录的最后一个真实流动段。
+ const int nFlowSegmentCount = pWellData->getFlowSegmentCount();
+ int nSelectedFlowIndex = sSelectedFlowGaugeCode ==
+ sPreferredFlowGaugeCode ? nPreferredFlowIndex : 0;
+ if(nSelectedFlowIndex < 1 ||
+ nSelectedFlowIndex > nFlowSegmentCount) {
+ nSelectedFlowIndex = nFlowSegmentCount;
+ }
+ pWellData->setIndexF(nSelectedFlowIndex);
}
static bool isWellParaAttrName(const QString& name)
@@ -1612,6 +1755,48 @@ void nmDataAnalyzeManager::initCurWellData()
return;
}
+ // 主井必须跟随当前流动段分析实际使用的压力、流量 Gauge Code,
+ // 而不是默认选择工程树中的第一条记录。多相时再覆盖同 Code 工作矩阵。
+ QString sCurrentPressureGaugeCode;
+ QString sCurrentFlowGaugeCode;
+ int nCurrentFlowIndex = 0;
+ bool bCurrentMultiPhase = false;
+ VVecDouble vvecCurrentMultiPhaseData;
+ iSubWndFitting* pSubWndFitting = nmDataAnalyzeManager::getCurrentFitting();
+ nmDataAnalyzeContextProvider* pContextProvider =
+ nmDataAnalyzeContext::provider();
+ if(pSubWndFitting != nullptr && pContextProvider != nullptr) {
+ pContextProvider->getCurrentSegmentIndex(
+ pSubWndFitting, nCurrentFlowIndex);
+ pContextProvider->getCurrentSegmentGaugeCodes(
+ pSubWndFitting,
+ sCurrentPressureGaugeCode,
+ sCurrentFlowGaugeCode);
+ if(pContextProvider->getCurrentSegmentMultiPhaseData(
+ pSubWndFitting,
+ bCurrentMultiPhase,
+ sCurrentFlowGaugeCode,
+ vvecCurrentMultiPhaseData) &&
+ bCurrentMultiPhase) {
+ applyCurrentMultiPhaseFlowData(
+ vecFlowRecords,
+ sCurrentFlowGaugeCode,
+ vvecCurrentMultiPhaseData,
+ nCurrentFlowIndex);
+ }
+ }
+
+ // 单相记录同样保存当前流动段索引,切换到其他记录再切回时可以恢复。
+ for(int nRecordIndex = 0;
+ nRecordIndex < vecFlowRecords.size();
+ ++nRecordIndex) {
+ if(vecFlowRecords[nRecordIndex].sGaugeCode ==
+ sCurrentFlowGaugeCode) {
+ vecFlowRecords[nRecordIndex].nIndexF = nCurrentFlowIndex;
+ break;
+ }
+ }
+
if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) {
// 初始化直井默认参数
nmDataWellBase* pWell = this->createWell(NM_WELL_MODEL::Vertical_Well);
@@ -1634,7 +1819,12 @@ void nmDataAnalyzeManager::initCurWellData()
pVerticalWell->setRadius(tempAttr);
// 设置井的压力、流量记录列表,并按默认规则选中当前记录
- applyGaugeRecordsToWell(pVerticalWell, vecPressureRecords, vecFlowRecords);
+ applyGaugeRecordsToWell(pVerticalWell,
+ vecPressureRecords,
+ vecFlowRecords,
+ sCurrentPressureGaugeCode,
+ sCurrentFlowGaugeCode,
+ nCurrentFlowIndex);
QVector> vvecHistoryPressureData; //压力历史数据
QVector> vvecHistoryLogData; // 历史双对数曲线数据
@@ -1672,7 +1862,12 @@ void nmDataAnalyzeManager::initCurWellData()
pVFracturedWell->setRadius(tempAttr);
// 设置井的压力、流量记录列表,并按默认规则选中当前记录
- applyGaugeRecordsToWell(pVFracturedWell, vecPressureRecords, vecFlowRecords);
+ applyGaugeRecordsToWell(pVFracturedWell,
+ vecPressureRecords,
+ vecFlowRecords,
+ sCurrentPressureGaugeCode,
+ sCurrentFlowGaugeCode,
+ nCurrentFlowIndex);
QVector> vvecHistoryPressureData; //压力历史数据
QVector> vvecHistoryLogData; // 历史双对数曲线数据
@@ -1713,7 +1908,12 @@ void nmDataAnalyzeManager::initCurWellData()
pHFracturedWell->setRadius(tempAttr);
// 设置井的压力、流量记录列表,并按默认规则选中当前记录
- applyGaugeRecordsToWell(pHFracturedWell, vecPressureRecords, vecFlowRecords);
+ applyGaugeRecordsToWell(pHFracturedWell,
+ vecPressureRecords,
+ vecFlowRecords,
+ sCurrentPressureGaugeCode,
+ sCurrentFlowGaugeCode,
+ nCurrentFlowIndex);
QVector> vvecHistoryPressureData; //压力历史数据
QVector> vvecHistoryLogData; // 历史双对数曲线数据
@@ -2748,6 +2948,19 @@ void nmDataAnalyzeManager::initPvtParaFromSubFit()
break;
}
+ case WFT_Oil_Gas:
+ // 数据接收层保留完整相态,提交求解时统一提示当前未开放。
+ setSolverModelType(SMT_Oil_Gas_TwoPhase);
+ break;
+
+ case WFT_Gas_Water:
+ setSolverModelType(SMT_Gas_Water_TwoPhase);
+ break;
+
+ case WTF_Oil_Gas_Water:
+ setSolverModelType(SMT_Oil_Gas_Water_ThreePhase);
+ break;
+
default:
break;
}
@@ -6274,7 +6487,7 @@ bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath)
bool nmDataAnalyzeManager::loadWellPreAndFlow()
{
- // 当前项目文件只保存井别和井身份,压力/流量仍从工程井节点重新读取。
+ // 项目文件只保存当前选择的 Gauge Code 和段索引;曲线数据始终从工程井重新读取。
if(zxCurProject == nullptr) {
return false;
}
@@ -6298,6 +6511,13 @@ bool nmDataAnalyzeManager::loadWellPreAndFlow()
return false;
}
+ // 在替换记录列表之前保存 JSON 已恢复的选择,避免重新枚举后退回第一条记录。
+ const QString sPreferredPressureGaugeCode =
+ pWell->getSelectedPressureGaugeCode();
+ const QString sPreferredFlowGaugeCode =
+ pWell->getSelectedFlowGaugeCode();
+ const int nPreferredFlowIndex = pWell->getIndexF();
+
ZxDataWell* pFrameworkWell =
mapFrameworkWells.value(pWell->getWellCode(), nullptr);
QVector vecPressureRecords;
@@ -6313,7 +6533,12 @@ bool nmDataAnalyzeManager::loadWellPreAndFlow()
return false;
}
- applyGaugeRecordsToWell(pWell, vecPressureRecords, vecFlowRecords);
+ applyGaugeRecordsToWell(pWell,
+ vecPressureRecords,
+ vecFlowRecords,
+ sPreferredPressureGaugeCode,
+ sPreferredFlowGaugeCode,
+ nPreferredFlowIndex);
}
return true;
}
diff --git a/Src/nmNum/nmData/nmDataWellBase.cpp b/Src/nmNum/nmData/nmDataWellBase.cpp
index e7de6f1..c7f1c21 100644
--- a/Src/nmNum/nmData/nmDataWellBase.cpp
+++ b/Src/nmNum/nmData/nmDataWellBase.cpp
@@ -273,6 +273,19 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType&
// 序列化井的流动段索引 (m_nIndexF)
wellObject.AddMember("IndexFlow", m_nIndexF, allocator);
+ // 曲线内容由框架保存,这里只持久化当前选择的稳定 Gauge Code。
+ const QByteArray baPressureGaugeCode =
+ m_sSelectedPressureGaugeCode.toUtf8();
+ wellObject.AddMember("SelectedPressureGaugeCode",
+ rapidjson::Value(baPressureGaugeCode.constData(),
+ static_cast(baPressureGaugeCode.size()),
+ allocator).Move(), allocator);
+ const QByteArray baFlowGaugeCode = m_sSelectedFlowGaugeCode.toUtf8();
+ wellObject.AddMember("SelectedFlowGaugeCode",
+ rapidjson::Value(baFlowGaugeCode.constData(),
+ static_cast(baFlowGaugeCode.size()),
+ allocator).Move(), allocator);
+
// 序列化射孔段集合 (现在处理指针)
rapidjson::Value perforationsArray(rapidjson::kArrayType);
@@ -431,6 +444,18 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_nIndexF = jsonValue["IndexFlow"].GetInt();
}
+ // 先恢复选择键;记录数据在 Manager 完成 JSON 读取后从框架重新枚举。
+ if(jsonValue.HasMember("SelectedPressureGaugeCode") &&
+ jsonValue["SelectedPressureGaugeCode"].IsString()) {
+ m_sSelectedPressureGaugeCode = QString::fromUtf8(
+ jsonValue["SelectedPressureGaugeCode"].GetString());
+ }
+ if(jsonValue.HasMember("SelectedFlowGaugeCode") &&
+ jsonValue["SelectedFlowGaugeCode"].IsString()) {
+ m_sSelectedFlowGaugeCode = QString::fromUtf8(
+ jsonValue["SelectedFlowGaugeCode"].GetString());
+ }
+
// 反序列化射孔段集合 (现在处理指针)
if(jsonValue.HasMember("Perforations") && jsonValue["Perforations"].IsArray()) {
// 清空并释放现有射孔段对象
@@ -888,10 +913,29 @@ QVector nmDataWellBase::getFlowSegmentPoints(
NM_PHASE_TYPE eFlowPhase) const
{
QVector vecSegments = getFlowPoints(eFlowPhase);
- // 工程流量数据通常以 (0,0) 作为非业务占位点;真实数据没有占位点时不得误删首段。
- if(!vecSegments.isEmpty() &&
- vecSegments.first().x() == 0.0 &&
- vecSegments.first().y() == 0.0) {
+ const nmFlowGaugeRecord* pRecord =
+ findGaugeRecordByCode(m_vecFlowRecords,
+ m_sSelectedFlowGaugeCode);
+ bool bHasPlaceholder = false;
+ if(pRecord != nullptr && pRecord->bMultiPhase) {
+ // 多相数据共用同一时间列,只有完整的 (0,0,0,0) 才是占位行。
+ // 不能按单相分别删除,否则某相首段流量为零时会造成三相错位。
+ bHasPlaceholder = !pRecord->vecOilPoints.isEmpty() &&
+ !pRecord->vecGasPoints.isEmpty() &&
+ !pRecord->vecWaterPoints.isEmpty() &&
+ pRecord->vecOilPoints.first().x() == 0.0 &&
+ pRecord->vecGasPoints.first().x() == 0.0 &&
+ pRecord->vecWaterPoints.first().x() == 0.0 &&
+ pRecord->vecOilPoints.first().y() == 0.0 &&
+ pRecord->vecGasPoints.first().y() == 0.0 &&
+ pRecord->vecWaterPoints.first().y() == 0.0;
+ } else {
+ // 单相记录沿用框架约定,首个 (0,0) 为非业务占位点。
+ bHasPlaceholder = !vecSegments.isEmpty() &&
+ vecSegments.first().x() == 0.0 &&
+ vecSegments.first().y() == 0.0;
+ }
+ if(bHasPlaceholder && !vecSegments.isEmpty()) {
vecSegments.remove(0);
}
return vecSegments;
diff --git a/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp b/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp
index 50c7248..4dfa095 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndDataAnalyzeController.cpp
@@ -66,6 +66,57 @@ bool nmSubWndDataAnalyzeController::getCurrentSegmentIndex(void* pFitting, int&
return true;
}
+// 复制当前流动段绑定的压力、流量曲线 Code,供数值井恢复实际使用的记录。
+bool nmSubWndDataAnalyzeController::getCurrentSegmentGaugeCodes(
+ void* pFitting,
+ QString& sPressureCurveName,
+ QString& sFlowCurveName)
+{
+ sPressureCurveName.clear();
+ sFlowCurveName.clear();
+
+ iSubWndFitting* pSubWndFitting = toFitting(pFitting);
+ if(pSubWndFitting == nullptr) {
+ return false;
+ }
+
+ ZxSegmentInfo* pSegInfo = pSubWndFitting->getSegmentInfo();
+ if(pSegInfo == nullptr) {
+ return false;
+ }
+
+ sPressureCurveName = pSegInfo->m_sCurveNameP;
+ sFlowCurveName = pSegInfo->m_sCurveNameF;
+ return true;
+}
+
+// 复制当前流动段分析的多相流量快照,保持 nmData 与具体窗口类之间的模块边界。
+bool nmSubWndDataAnalyzeController::getCurrentSegmentMultiPhaseData(
+ void* pFitting,
+ bool& bMultiPhase,
+ QString& sFlowCurveName,
+ VVecDouble& vvecMultiPhaseData)
+{
+ bMultiPhase = false;
+ sFlowCurveName.clear();
+ vvecMultiPhaseData.clear();
+
+ iSubWndFitting* pSubWndFitting = toFitting(pFitting);
+ if(pSubWndFitting == nullptr) {
+ return false;
+ }
+
+ ZxSegmentInfo* pSegInfo = pSubWndFitting->getSegmentInfo();
+ if(pSegInfo == nullptr) {
+ return false;
+ }
+
+ bMultiPhase = pSegInfo->m_bMultiPhase;
+ sFlowCurveName = pSegInfo->m_sCurveNameF;
+ vvecMultiPhaseData = pSegInfo->m_vvecMpData;
+ return true;
+}
+
// 获取基础数据中的PVT相态类型
bool nmSubWndDataAnalyzeController::getBasicPft(void* pFitting, PvtFluidType& eType)
{
diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
index 6798e82..3603ee5 100644
--- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
+++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp
@@ -1583,6 +1583,17 @@ void nmSubWndMain::solveAndAnalyze()
}
// 第一步:求解时间轴必须由至少一口主动井的产注制度驱动。
+ // 当前数值接入只开放油、气、水单相和油水两相;其余相态保留数据,
+ // 等求解器支持后再开放,不在这里改写或丢弃框架流量。
+ if(!nmIsSupportedPebiSolverModel(
+ pDataManager->getSolverModelType())) {
+ QMessageBox::warning(
+ this,
+ tr("solver error"),
+ tr("The solver currently supports oil, gas, and water single-phase models and the oil-water two-phase model only."));
+ return;
+ }
+
// 观察井可以完全不传产量,但不能在所有井都是观察井时启动无源汇计算。
QVector vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
@@ -1621,16 +1632,6 @@ void nmSubWndMain::solveAndAnalyze()
.arg(pWellData->getWellName()));
return;
}
- if(!nmSolverModelSupportsWellCategory(
- pDataManager->getSolverModelType(),
- pWellData->getWellCategory())) {
- QMessageBox::warning(
- this,
- tr("solver error"),
- tr("Well '%1' category is not supported by the current solver model.")
- .arg(pWellData->getWellName()));
- return;
- }
}
if(!bHasRateControlledWell) {
QMessageBox::warning(
diff --git a/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
index 50f2a26..b6c15d5 100644
--- a/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
+++ b/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp
@@ -3,18 +3,29 @@
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "iUnitItem.h"
+#include "nmDataAnalyzeContext.h"
+#include "nmDataAnalyzeContextProvider.h"
+#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmWxPressFlowChartWidget.h"
#include "ZxBaseUtil.h"
+#include "ZxBaHelper.h"
+#include "ZxDataGaugeF.h"
+#include "ZxDataProject.h"
+#include "ZxDataWell.h"
#include "ZxTableHeaderViewUnit.h"
+#include "zxSysUtils.h"
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -85,21 +96,25 @@ QString displayValueText(double dValue, int nDigit)
return ZxBaseUtil::getValidStr(dValue, nDigit);
}
-// 判断某相流量点是否至少包含一个真实流动段,去除可选首个(0,0)占位点后非空即为真实。
-// 与 nmDataAnalyzeManager.cpp 中同名判断语义一致,但该处是其它文件的 file-static 函数,
-// 无法跨文件复用,因此在本文件内新增等价判断。
+// 显示相下拉框只列出实际出现过非零流量的相;多相表中的固定全零列
+// 仍保留在井记录和求解输入中,但不应显示成该井存在对应相产量。
bool hasRealPhaseData(const QVector& vecPoints)
{
- if (vecPoints.isEmpty())
+ int nStartIndex = 0;
+ if (!vecPoints.isEmpty() &&
+ vecPoints.first().x() == 0.0 &&
+ vecPoints.first().y() == 0.0)
{
- return false;
+ nStartIndex = 1;
}
- if (vecPoints.size() == 1 &&
- vecPoints.first().x() == 0.0 && vecPoints.first().y() == 0.0)
+ for (int nIndex = nStartIndex; nIndex < vecPoints.size(); ++nIndex)
{
- return false;
+ if (vecPoints[nIndex].y() != 0.0)
+ {
+ return true;
+ }
}
- return true;
+ return false;
}
// 压力/流量记录下拉项文本:优先显示计量名称,否则回退到 Gauge Code;再拼接测量时间。
@@ -153,6 +168,482 @@ void appendGaugeComboItem(QComboBox* pCombo,
}
}
}
+
+// 诊断文本统一使用约 15 位有效数字,避免浮点差异被显示精度掩盖。
+QString diagNumberText(double dValue)
+{
+ return QString::number(dValue, 'g', 15);
+}
+
+// 诊断文本中的单个数据点,括号与逗号为固定格式不参与翻译。
+QString diagPointText(const QPointF& oPoint)
+{
+ return QString("(%1, %2)")
+ .arg(diagNumberText(oPoint.x()))
+ .arg(diagNumberText(oPoint.y()));
+}
+
+// 空字符串在诊断文本中显示为“(none)”,避免与真实空白混淆。
+QString diagTextOrNone(const QString& sText)
+{
+ return sText.isEmpty()
+ ? nmWellPressureFlowPage::tr("(none)") : sText;
+}
+
+// 记录状态的诊断文本,与下拉框后缀语义一致,单独成词便于阅读。
+QString diagStatusText(NM_GAUGE_RECORD_STATUS eStatus)
+{
+ switch (eStatus)
+ {
+ case NM_GaugeRecord_Usable:
+ return nmWellPressureFlowPage::tr("Usable");
+ case NM_GaugeRecord_Empty:
+ return nmWellPressureFlowPage::tr("Empty");
+ case NM_GaugeRecord_Corrupt:
+ return nmWellPressureFlowPage::tr("Corrupt");
+ case NM_GaugeRecord_InvalidIdentity:
+ return nmWellPressureFlowPage::tr("Invalid identity");
+ default:
+ return nmWellPressureFlowPage::tr("Unknown");
+ }
+}
+
+// 井别的诊断文本。
+QString diagWellCategoryText(NM_WELL_CATEGORY eWellCategory)
+{
+ switch (eWellCategory)
+ {
+ case NM_WellCategory_Oil:
+ return nmWellPressureFlowPage::tr("Oil well");
+ case NM_WellCategory_Gas:
+ return nmWellPressureFlowPage::tr("Gas well");
+ case NM_WellCategory_Water:
+ return nmWellPressureFlowPage::tr("Water well");
+ default:
+ return nmWellPressureFlowPage::tr("Unknown");
+ }
+}
+
+// 相名的诊断文本,与显示相下拉框文本保持一致。
+QString diagPhaseText(NM_PHASE_TYPE ePhase)
+{
+ switch (ePhase)
+ {
+ case PHASE_Oil:
+ return nmWellPressureFlowPage::tr("Oil");
+ case PHASE_Gas:
+ return nmWellPressureFlowPage::tr("Gas");
+ case PHASE_Water:
+ return nmWellPressureFlowPage::tr("Water");
+ default:
+ return nmWellPressureFlowPage::tr("Unknown");
+ }
+}
+
+// 当前分析窗口的PVT相态决定实际参与分析的相,不能仅依赖多相布尔标志判断。
+QString diagFluidTypeText(PvtFluidType eFluidType)
+{
+ switch (eFluidType)
+ {
+ case WFT_Oil:
+ return nmWellPressureFlowPage::tr("Oil");
+ case WFT_Gas:
+ return nmWellPressureFlowPage::tr("Gas");
+ case WFT_Water:
+ return nmWellPressureFlowPage::tr("Water");
+ case WFT_Oil_Gas:
+ return nmWellPressureFlowPage::tr("Oil + Gas");
+ case WFT_Oil_Water:
+ return nmWellPressureFlowPage::tr("Oil + Water");
+ case WFT_Gas_Water:
+ return nmWellPressureFlowPage::tr("Gas + Water");
+ case WTF_Oil_Gas_Water:
+ return nmWellPressureFlowPage::tr("Oil + Gas + Water");
+ case WFT_Null:
+ return nmWellPressureFlowPage::tr("None");
+ default:
+ return nmWellPressureFlowPage::tr("Other (%1)")
+ .arg(static_cast(eFluidType));
+ }
+}
+
+// 判断首个点是否为 (0,0) 占位点,与 hasRealPhaseData 的占位点语义一致。
+bool hasLeadingPlaceholderPoint(const QVector& vecPoints)
+{
+ return !vecPoints.isEmpty() &&
+ vecPoints.first().x() == 0.0 &&
+ vecPoints.first().y() == 0.0;
+}
+
+// 去除可选的首个 (0,0) 占位点后返回真实流量段列表。
+QVector realPhaseSegments(const QVector& vecPoints)
+{
+ QVector vecSegments = vecPoints;
+ if (hasLeadingPlaceholderPoint(vecSegments))
+ {
+ vecSegments.remove(0);
+ }
+ return vecSegments;
+}
+
+// 生成单个相的诊断摘要行:原始点数、真实段数、累计时长、首末点与占位点标识。
+void appendPhaseDiagLines(QStringList& listLines,
+ const QString& sPhaseName,
+ const QVector& vecPoints)
+{
+ if (vecPoints.isEmpty())
+ {
+ listLines << QString(" %1: %2")
+ .arg(sPhaseName)
+ .arg(nmWellPressureFlowPage::tr("no data"));
+ return;
+ }
+
+ // 累计时长只统计真实段,流量点 X 值是段持续时间而非绝对时刻。
+ QVector vecSegments = realPhaseSegments(vecPoints);
+ double dTotalDuration = 0.0;
+ for (int nIndex = 0; nIndex < vecSegments.size(); ++nIndex)
+ {
+ dTotalDuration += vecSegments[nIndex].x();
+ }
+
+ listLines << QString(" %1: %2")
+ .arg(sPhaseName)
+ .arg(nmWellPressureFlowPage::tr(
+ "raw points = %1, real segments = %2, total duration = %3")
+ .arg(vecPoints.size())
+ .arg(vecSegments.size())
+ .arg(diagNumberText(dTotalDuration)));
+ listLines << QLatin1String(" ") +
+ nmWellPressureFlowPage::tr("First point = %1, last point = %2")
+ .arg(diagPointText(vecPoints.first()))
+ .arg(diagPointText(vecPoints.last()));
+ // 点数较少时列出全部原始点,便于核对各相中间点数值是否分相正确。
+ const int nMaxListedPoints = 16;
+ if (vecPoints.size() <= nMaxListedPoints)
+ {
+ QStringList listPointTexts;
+ for (int nIndex = 0; nIndex < vecPoints.size(); ++nIndex)
+ {
+ listPointTexts << diagPointText(vecPoints[nIndex]);
+ }
+ listLines << QLatin1String(" ") +
+ nmWellPressureFlowPage::tr("All points = %1")
+ .arg(listPointTexts.join(QLatin1String(", ")));
+ }
+ if (hasLeadingPlaceholderPoint(vecPoints))
+ {
+ listLines << QLatin1String(" ") +
+ nmWellPressureFlowPage::tr("first point is a (0,0) placeholder");
+ }
+}
+
+// 检查多相记录有效相之间段数与每段时间是否完全一致,返回结论文本。
+QString diagPhaseConsistencyText(const nmFlowGaugeRecord& oRecord)
+{
+ // 只统计有真实数据的相,与显示相下拉框的判定保持一致;比较前先去除占位点。
+ QStringList listPhaseNames;
+ QVector > listPhaseSegments;
+ if (hasRealPhaseData(oRecord.vecOilPoints))
+ {
+ listPhaseNames.append(nmWellPressureFlowPage::tr("Oil"));
+ listPhaseSegments.append(realPhaseSegments(oRecord.vecOilPoints));
+ }
+ if (hasRealPhaseData(oRecord.vecGasPoints))
+ {
+ listPhaseNames.append(nmWellPressureFlowPage::tr("Gas"));
+ listPhaseSegments.append(realPhaseSegments(oRecord.vecGasPoints));
+ }
+ if (hasRealPhaseData(oRecord.vecWaterPoints))
+ {
+ listPhaseNames.append(nmWellPressureFlowPage::tr("Water"));
+ listPhaseSegments.append(realPhaseSegments(oRecord.vecWaterPoints));
+ }
+
+ if (listPhaseSegments.size() < 2)
+ {
+ return nmWellPressureFlowPage::tr(
+ "less than two phases with real data, no comparison");
+ }
+
+ // 先比较段数,段数不同时直接列出各相段数。
+ for (int nPhase = 1; nPhase < listPhaseSegments.size(); ++nPhase)
+ {
+ if (listPhaseSegments[nPhase].size() !=
+ listPhaseSegments.first().size())
+ {
+ QStringList listCounts;
+ for (int nItem = 0; nItem < listPhaseSegments.size(); ++nItem)
+ {
+ listCounts << QString("%1 = %2")
+ .arg(listPhaseNames[nItem])
+ .arg(listPhaseSegments[nItem].size());
+ }
+ return nmWellPressureFlowPage::tr("segment counts differ: %1")
+ .arg(listCounts.join(QLatin1String(", ")));
+ }
+ }
+
+ // 段数一致后逐段严格按位比较时间,不引入容差,浮点差异按不一致处理。
+ for (int nSegment = 0;
+ nSegment < listPhaseSegments.first().size();
+ ++nSegment)
+ {
+ for (int nPhase = 1; nPhase < listPhaseSegments.size(); ++nPhase)
+ {
+ if (listPhaseSegments[nPhase][nSegment].x() !=
+ listPhaseSegments.first()[nSegment].x())
+ {
+ return nmWellPressureFlowPage::tr(
+ "segment counts equal (%1), but segment %2 time differs: %3 = %4, %5 = %6")
+ .arg(listPhaseSegments.first().size())
+ .arg(nSegment + 1)
+ .arg(listPhaseNames.first())
+ .arg(diagNumberText(
+ listPhaseSegments.first()[nSegment].x()))
+ .arg(listPhaseNames[nPhase])
+ .arg(diagNumberText(
+ listPhaseSegments[nPhase][nSegment].x()));
+ }
+ }
+ }
+ return nmWellPressureFlowPage::tr(
+ "segment counts equal (%1) and all segment times identical")
+ .arg(listPhaseSegments.first().size());
+}
+
+// 输出一个原始字节块按 VVecVariant 解码后的形状和少量样本值。
+// 诊断只读取数据,不参与正式的流量记录状态判定。
+void appendRawVVecDiagnostic(QStringList& listLines,
+ const QString& sBlockName,
+ const QByteArray& baSource)
+{
+ listLines << QString(" %1 bytes: %2")
+ .arg(sBlockName)
+ .arg(baSource.size());
+ if (baSource.isEmpty())
+ {
+ listLines << QString(" %1 is empty").arg(sBlockName);
+ return;
+ }
+
+ QByteArray baData = baSource;
+ VVecVariant vvecData;
+ const bool bDecoded = ZxBaHelper::convertBa2VVec(vvecData, baData);
+ listLines << QString(" VVec decode: %1")
+ .arg(bDecoded ? QLatin1String("success")
+ : QLatin1String("failed"));
+ if (!bDecoded)
+ {
+ return;
+ }
+
+ QStringList listSizes;
+ for (int nVectorIndex = 0;
+ nVectorIndex < vvecData.size();
+ ++nVectorIndex)
+ {
+ listSizes << QString("[%1]=%2")
+ .arg(nVectorIndex)
+ .arg(vvecData[nVectorIndex].size());
+ }
+ listLines << QString(" Vector count: %1; sizes: %2")
+ .arg(vvecData.size())
+ .arg(listSizes.isEmpty()
+ ? QLatin1String("(none)")
+ : listSizes.join(QLatin1String(", ")));
+
+ // GaugeDataEx2 在框架中的正式布局是 N 行 x 4 列,直接按业务字段展示。
+ if (sBlockName == QLatin1String("GaugeDataEx2"))
+ {
+ bool bRowLayoutValid = !vvecData.isEmpty();
+ for (int nStoredRow = 0;
+ bRowLayoutValid && nStoredRow < vvecData.size();
+ ++nStoredRow)
+ {
+ bRowLayoutValid = vvecData[nStoredRow].size() == 4;
+ }
+ listLines << QString(" N x 4 row layout valid: %1")
+ .arg(bRowLayoutValid ? QLatin1String("Yes")
+ : QLatin1String("No"));
+ if (bRowLayoutValid)
+ {
+ const int nMaxStoredRows = 8;
+ const int nStoredRowCount = qMin(
+ vvecData.size(), nMaxStoredRows);
+ for (int nStoredRow = 0;
+ nStoredRow < nStoredRowCount;
+ ++nStoredRow)
+ {
+ listLines << QString(" Stored row %1: duration=%2, oil=%3, gas=%4, water=%5")
+ .arg(nStoredRow + 1)
+ .arg(vvecData[nStoredRow][0].toString())
+ .arg(vvecData[nStoredRow][1].toString())
+ .arg(vvecData[nStoredRow][2].toString())
+ .arg(vvecData[nStoredRow][3].toString());
+ }
+ return;
+ }
+ }
+
+ // 其他未知形状的扩展块按矩阵公共范围输出样本,便于继续排查。
+ const int nMaxSampleRows = 8;
+ int nCommonSize = vvecData.isEmpty() ? 0 : vvecData.first().size();
+ for (int nVectorIndex = 1;
+ nVectorIndex < vvecData.size();
+ ++nVectorIndex)
+ {
+ nCommonSize = qMin(nCommonSize, vvecData[nVectorIndex].size());
+ }
+ const int nSampleRows = qMin(nCommonSize, nMaxSampleRows);
+ for (int nRowIndex = 0; nRowIndex < nSampleRows; ++nRowIndex)
+ {
+ QStringList listValues;
+ for (int nVectorIndex = 0;
+ nVectorIndex < vvecData.size();
+ ++nVectorIndex)
+ {
+ listValues << QString("[%1]=%2")
+ .arg(nVectorIndex)
+ .arg(vvecData[nVectorIndex][nRowIndex].toString());
+ }
+ listLines << QString(" Row %1: %2")
+ .arg(nRowIndex + 1)
+ .arg(listValues.join(QLatin1String(", ")));
+ }
+}
+
+// 直接定位当前数值井对应的框架井,检查每条流量记录的三个存储块及
+// getGaugeDataOf() 返回值,区分“数值层解析错误”和“框架原始数据形状异常”。
+void appendFrameworkFlowStorageDiagnostic(
+ QStringList& listLines,
+ const QString& sWellCode,
+ const QString& sSelectedFlowGaugeCode)
+{
+ listLines << QString();
+ listLines << QLatin1String("=== Framework raw flow storage ===");
+ listLines << QString("Requested well code: %1")
+ .arg(sWellCode.isEmpty() ? QLatin1String("(none)") : sWellCode);
+ listLines << QString("Selected flow gauge code: %1")
+ .arg(sSelectedFlowGaugeCode.isEmpty()
+ ? QLatin1String("(none)") : sSelectedFlowGaugeCode);
+
+ if (zxCurProject == nullptr)
+ {
+ listLines << QLatin1String("Current framework project: unavailable");
+ return;
+ }
+
+ const ZxDataObjectList listWells =
+ zxCurProject->getChildren(iDataModelType::sTypeWell);
+ ZxDataWell* pMatchedWell = nullptr;
+ int nMatchedWellCount = 0;
+ for (int nWellIndex = 0;
+ nWellIndex < listWells.size();
+ ++nWellIndex)
+ {
+ ZxDataWell* pFrameworkWell =
+ dynamic_cast(listWells[nWellIndex]);
+ if (pFrameworkWell != nullptr &&
+ pFrameworkWell->getCode() == sWellCode)
+ {
+ ++nMatchedWellCount;
+ if (pMatchedWell == nullptr)
+ {
+ pMatchedWell = pFrameworkWell;
+ }
+ }
+ }
+
+ listLines << QString("Framework wells matched by code: %1")
+ .arg(nMatchedWellCount);
+ if (pMatchedWell == nullptr)
+ {
+ return;
+ }
+
+ listLines << QString("Matched framework well name: %1")
+ .arg(pMatchedWell->getName());
+ listLines << QString("Framework well type index: %1")
+ .arg(pMatchedWell->getWellTypeIndex());
+
+ const ZxDataObjectList listGaugeF = pMatchedWell->getChildren(
+ iDataModelType::sTypeDataGaugeF);
+ listLines << QString("Framework flow-record count: %1")
+ .arg(listGaugeF.size());
+ bool bSelectedGaugeFound = false;
+ for (int nGaugeIndex = 0;
+ nGaugeIndex < listGaugeF.size();
+ ++nGaugeIndex)
+ {
+ ZxDataGaugeF* pGaugeF =
+ dynamic_cast(listGaugeF[nGaugeIndex]);
+ if (pGaugeF == nullptr)
+ {
+ listLines << QString("[%1] Object is not ZxDataGaugeF")
+ .arg(nGaugeIndex + 1);
+ continue;
+ }
+
+ const bool bIsSelected =
+ pGaugeF->getCode() == sSelectedFlowGaugeCode;
+ bSelectedGaugeFound = bSelectedGaugeFound || bIsSelected;
+ const QByteArray baGaugeData = pGaugeF->getGaugeData();
+ const QByteArray baGaugeDataEx2 = pGaugeF->getGaugeDataEx2();
+ const QByteArray baGaugeDataEx3 = pGaugeF->getGaugeDataEx3();
+ const QByteArray baDataOf0 = pGaugeF->getGaugeDataOf(0);
+ const QByteArray baDataOf1 = pGaugeF->getGaugeDataOf(1);
+ const QByteArray baDataOf2 = pGaugeF->getGaugeDataOf(2);
+
+ listLines << QString();
+ listLines << QString("[%1] Code: %2; name: %3; selected: %4")
+ .arg(nGaugeIndex + 1)
+ .arg(pGaugeF->getCode())
+ .arg(pGaugeF->getGaugeName())
+ .arg(bIsSelected ? QLatin1String("Yes")
+ : QLatin1String("No"));
+ listLines << QString(" Time: %1; multiphase flag: %2")
+ .arg(pGaugeF->getGaugeTime())
+ .arg(pGaugeF->getMultiPhase()
+ ? QLatin1String("Yes") : QLatin1String("No"));
+
+ // 主块按单相 XY 格式再解一次,确认单相数据是否仍然存在。
+ QByteArray baMainCopy = baGaugeData;
+ VecDouble vecMainX;
+ VecDouble vecMainY;
+ const bool bMainDecoded = !baGaugeData.isEmpty() &&
+ ZxBaHelper::convertBa2VecXY(
+ vecMainX, vecMainY, baMainCopy);
+ listLines << QString(" GaugeData bytes: %1; XY decode: %2; X/Y sizes: %3/%4")
+ .arg(baGaugeData.size())
+ .arg(bMainDecoded ? QLatin1String("success")
+ : QLatin1String("failed"))
+ .arg(static_cast(vecMainX.size()))
+ .arg(static_cast(vecMainY.size()));
+
+ appendRawVVecDiagnostic(
+ listLines, QLatin1String("GaugeDataEx2"), baGaugeDataEx2);
+ appendRawVVecDiagnostic(
+ listLines, QLatin1String("GaugeDataEx3"), baGaugeDataEx3);
+
+ listLines << QString(" getGaugeDataOf sizes: [0]=%1, [1]=%2, [2]=%3")
+ .arg(baDataOf0.size())
+ .arg(baDataOf1.size())
+ .arg(baDataOf2.size());
+ listLines << QString(" getGaugeDataOf equality: [0]=GaugeData %1, [1]=Ex2 %2, [2]=Ex3 %3")
+ .arg(baDataOf0 == baGaugeData
+ ? QLatin1String("Yes") : QLatin1String("No"))
+ .arg(baDataOf1 == baGaugeDataEx2
+ ? QLatin1String("Yes") : QLatin1String("No"))
+ .arg(baDataOf2 == baGaugeDataEx3
+ ? QLatin1String("Yes") : QLatin1String("No"));
+ }
+
+ listLines << QString();
+ listLines << QString("Selected gauge found under matched framework well: %1")
+ .arg(bSelectedGaugeFound
+ ? QLatin1String("Yes") : QLatin1String("No"));
+}
}
nmWellPressureFlowPage::nmWellPressureFlowPage(QWidget* pParent)
@@ -182,6 +673,7 @@ nmWellPressureFlowPage::nmWellPressureFlowPage(QWidget* pParent)
m_pFlowGaugeCombo(nullptr),
m_pDisplayPhaseCombo(nullptr),
m_pDisplayPhaseContainer(nullptr),
+ m_pCheckDataButton(nullptr),
m_eDisplayPhase(PHASE_UNKNOWN)
{
createUi();
@@ -383,7 +875,7 @@ void nmWellPressureFlowPage::slotFlowGaugeChanged(int nIndex)
}
// 必须调用 switchSelectedFlowGaugeCode 而不是 selectFlowGaugeCode,才能根据新记录
- // 自己的 nIndexF 恢复/校验流动段选择,否则换记录后选择器就回帰到默认 0。
+ // 自己的 nIndexF 恢复/校验流动段选择,否则换记录后选择器就回到默认 0。
m_pWell->switchSelectedFlowGaugeCode(sCode);
// 换记录后显示相重置为新记录的默认相,m_vecFlowGaugeRecords 仍为本井完整记录列表,
// 因此可在 refreshData() 重新填充下拉框前先用旧缓存安全查找新选中记录。
@@ -410,6 +902,32 @@ void nmWellPressureFlowPage::slotDisplayPhaseChanged(int nIndex)
refreshData();
}
+void nmWellPressureFlowPage::slotCheckDataClicked()
+{
+ // 诊断窗口为模态只读展示,允许缩放、滚动与复制文本,不修改任何业务数据。
+ QDialog oDialog(this);
+ oDialog.setWindowTitle(tr("Pressure/Flow Data Check"));
+ oDialog.setWindowFlags(oDialog.windowFlags() |
+ Qt::WindowMaximizeButtonHint);
+ oDialog.resize(820, 600);
+
+ QVBoxLayout* pDialogLayout = new QVBoxLayout(&oDialog);
+ QPlainTextEdit* pTextEdit = new QPlainTextEdit(&oDialog);
+ pTextEdit->setReadOnly(true);
+ pTextEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
+ pTextEdit->setPlainText(buildDiagnosticText());
+
+ QPushButton* pCloseButton = new QPushButton(tr("Close"), &oDialog);
+ QHBoxLayout* pButtonLayout = new QHBoxLayout();
+ pButtonLayout->addStretch(1);
+ pButtonLayout->addWidget(pCloseButton);
+
+ pDialogLayout->addWidget(pTextEdit, 1);
+ pDialogLayout->addLayout(pButtonLayout);
+ connect(pCloseButton, SIGNAL(clicked()), &oDialog, SLOT(accept()));
+ oDialog.exec();
+}
+
void nmWellPressureFlowPage::createUi()
{
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
@@ -569,6 +1087,10 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
pPhaseLayout->addWidget(m_pDisplayPhaseCombo);
m_pDisplayPhaseContainer->setVisible(false);
+ // 临时诊断按钮:只读展示井对象解析结果,验证完成后再决定删除或保留。
+ m_pCheckDataButton = new QPushButton(
+ tr("Check Data"), m_pRecordSelectionBar);
+
pBarLayout->addWidget(pPressureLabel);
pBarLayout->addWidget(m_pPressureGaugeCombo);
pBarLayout->addSpacing(12);
@@ -577,6 +1099,7 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
pBarLayout->addSpacing(12);
pBarLayout->addWidget(m_pDisplayPhaseContainer);
pBarLayout->addStretch(1);
+ pBarLayout->addWidget(m_pCheckDataButton);
connect(m_pPressureGaugeCombo,
SIGNAL(currentIndexChanged(int)),
@@ -590,6 +1113,10 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotDisplayPhaseChanged(int)));
+ connect(m_pCheckDataButton,
+ SIGNAL(clicked()),
+ this,
+ SLOT(slotCheckDataClicked()));
}
void nmWellPressureFlowPage::populatePressureGaugeCombo()
@@ -1224,3 +1751,216 @@ void nmWellPressureFlowPage::resetDataViews()
updateViews();
}
+
+QString nmWellPressureFlowPage::buildDiagnosticText() const
+{
+ if (m_pWell == nullptr)
+ {
+ return tr("No well is bound to this page.");
+ }
+
+ QStringList listLines;
+
+ // 井基础信息:全部即时从井对象重新读取,验证框架解析结果而非页面缓存。
+ listLines << tr("=== Well ===");
+ listLines << tr("Well name: %1")
+ .arg(diagTextOrNone(m_pWell->getWellName()));
+ listLines << tr("Well code: %1")
+ .arg(diagTextOrNone(m_pWell->getWellCode()));
+ listLines << tr("Well category: %1")
+ .arg(diagWellCategoryText(m_pWell->getWellCategory()));
+ listLines << tr("Reference flow phase: %1")
+ .arg(diagPhaseText(m_pWell->getReferenceFlowPhase()));
+ listLines << QString();
+
+ // 压力记录摘要:枚举全部记录,含损坏、空与身份无效记录。
+ QVector vecPressureRecords =
+ m_pWell->getPressureRecords();
+ listLines << tr("=== Pressure records ===");
+ listLines << tr("Total pressure records: %1")
+ .arg(vecPressureRecords.size());
+ listLines << tr("Selected pressure gauge code: %1")
+ .arg(diagTextOrNone(m_pWell->getSelectedPressureGaugeCode()));
+ for (int nIndex = 0; nIndex < vecPressureRecords.size(); ++nIndex)
+ {
+ const nmPressureGaugeRecord& oRecord = vecPressureRecords[nIndex];
+ listLines << QString();
+ listLines << tr("[%1] Name: %2")
+ .arg(nIndex + 1)
+ .arg(diagTextOrNone(oRecord.sGaugeName));
+ listLines << QLatin1String(" ") +
+ tr("Code: %1").arg(diagTextOrNone(oRecord.sGaugeCode));
+ listLines << QLatin1String(" ") +
+ tr("Time: %1").arg(diagTextOrNone(oRecord.sGaugeTime));
+ listLines << QLatin1String(" ") +
+ tr("Status: %1").arg(diagStatusText(oRecord.eStatus));
+ listLines << QLatin1String(" ") +
+ tr("Point count: %1").arg(oRecord.vecPressurePoints.size());
+ if (oRecord.vecPressurePoints.isEmpty())
+ {
+ listLines << QLatin1String(" ") + tr("No pressure points.");
+ }
+ else
+ {
+ listLines << QLatin1String(" ") +
+ tr("First point = %1, last point = %2")
+ .arg(diagPointText(oRecord.vecPressurePoints.first()))
+ .arg(diagPointText(oRecord.vecPressurePoints.last()));
+ }
+ }
+ listLines << QString();
+
+ // 流量记录摘要:油、气、水三相分别统计,多相记录额外检查相间时间一致性。
+ QVector vecFlowRecords = m_pWell->getFlowRecords();
+ listLines << tr("=== Flow records ===");
+ listLines << tr("Total flow records: %1").arg(vecFlowRecords.size());
+ listLines << tr("Selected flow gauge code: %1")
+ .arg(diagTextOrNone(m_pWell->getSelectedFlowGaugeCode()));
+ listLines << tr("Current display phase: %1")
+ .arg(diagPhaseText(m_eDisplayPhase));
+ for (int nIndex = 0; nIndex < vecFlowRecords.size(); ++nIndex)
+ {
+ const nmFlowGaugeRecord& oRecord = vecFlowRecords[nIndex];
+ listLines << QString();
+ listLines << tr("[%1] Name: %2")
+ .arg(nIndex + 1)
+ .arg(diagTextOrNone(oRecord.sGaugeName));
+ listLines << QLatin1String(" ") +
+ tr("Code: %1").arg(diagTextOrNone(oRecord.sGaugeCode));
+ listLines << QLatin1String(" ") +
+ tr("Time: %1").arg(diagTextOrNone(oRecord.sGaugeTime));
+ listLines << QLatin1String(" ") +
+ tr("Status: %1").arg(diagStatusText(oRecord.eStatus));
+ listLines << QLatin1String(" ") +
+ tr("Multiphase: %1")
+ .arg(oRecord.bMultiPhase ? tr("Yes") : tr("No"));
+ listLines << QLatin1String(" ") +
+ tr("Own flow segment index (1-based): %1")
+ .arg(oRecord.nIndexF);
+ appendPhaseDiagLines(listLines, tr("Oil"), oRecord.vecOilPoints);
+ appendPhaseDiagLines(listLines, tr("Gas"), oRecord.vecGasPoints);
+ appendPhaseDiagLines(listLines, tr("Water"), oRecord.vecWaterPoints);
+ if (oRecord.bMultiPhase)
+ {
+ listLines << QLatin1String(" ") +
+ tr("Phase time consistency: %1")
+ .arg(diagPhaseConsistencyText(oRecord));
+ }
+ }
+
+ // 数值记录显示 Corrupt 时继续向下追溯对应框架井的原始存储,尤其检查
+ // 另一口井自己的 GaugeDataEx2 是否确实为 N 行 x 4 列。
+ appendFrameworkFlowStorageDiagnostic(
+ listLines,
+ m_pWell->getWellCode(),
+ m_pWell->getSelectedFlowGaugeCode());
+
+ // 单独读取当前流动段分析快照,验证“多相数据”弹框保存的矩阵是否位于
+ // ZxSegmentInfo::m_vvecMpData。此处只展示,不覆盖工程井记录或求解输入。
+ listLines << QString();
+ listLines << tr("=== Current flow-segment multiphase data ===");
+ nmDataAnalyzeContextProvider* pContextProvider =
+ nmDataAnalyzeContext::provider();
+ iSubWndFitting* pSubWndFitting =
+ nmDataAnalyzeManager::getCurrentFitting();
+ nmDataAnalyzeManager* pCurrentManager =
+ nmDataAnalyzeManager::getCurrentInstance();
+ const QString sCurrentPrimaryWellCode = pCurrentManager != nullptr
+ ? pCurrentManager->getPrimaryWellCode() : QString();
+ const bool bSnapshotBelongsToEditedWell =
+ !sCurrentPrimaryWellCode.isEmpty() &&
+ sCurrentPrimaryWellCode == m_pWell->getWellCode();
+ listLines << tr("Current fitting primary well code: %1")
+ .arg(diagTextOrNone(sCurrentPrimaryWellCode));
+ listLines << tr("Snapshot belongs to edited well: %1")
+ .arg(bSnapshotBelongsToEditedWell ? tr("Yes") : tr("No"));
+ if (!bSnapshotBelongsToEditedWell)
+ {
+ listLines << tr("This snapshot belongs to the current fitting primary well and does not override the edited other well.");
+ }
+ bool bSegmentMultiPhase = false;
+ QString sFlowCurveName;
+ VVecDouble vvecMultiPhaseData;
+ const bool bSnapshotAvailable = pContextProvider != nullptr &&
+ pSubWndFitting != nullptr &&
+ pContextProvider->getCurrentSegmentMultiPhaseData(
+ pSubWndFitting,
+ bSegmentMultiPhase,
+ sFlowCurveName,
+ vvecMultiPhaseData);
+ listLines << tr("Snapshot available: %1")
+ .arg(bSnapshotAvailable ? tr("Yes") : tr("No"));
+ if (bSnapshotAvailable)
+ {
+ PvtFluidType eFluidType = WFT_Null;
+ const bool bFluidTypeAvailable =
+ pContextProvider->getBasicPft(pSubWndFitting, eFluidType);
+ listLines << tr("PVT phase type: %1")
+ .arg(bFluidTypeAvailable
+ ? diagFluidTypeText(eFluidType) : tr("Unknown"));
+ listLines << tr("Segment multiphase flag: %1")
+ .arg(bSegmentMultiPhase ? tr("Yes") : tr("No"));
+ listLines << tr("Flow curve name: %1")
+ .arg(diagTextOrNone(sFlowCurveName));
+ listLines << tr("Matrix vector count: %1")
+ .arg(vvecMultiPhaseData.size());
+
+ QStringList listVectorSizes;
+ for (int nVectorIndex = 0;
+ nVectorIndex < vvecMultiPhaseData.size();
+ ++nVectorIndex)
+ {
+ listVectorSizes << QString("[%1] = %2")
+ .arg(nVectorIndex)
+ .arg(vvecMultiPhaseData[nVectorIndex].size());
+ }
+ listLines << tr("Vector sizes: %1")
+ .arg(listVectorSizes.isEmpty()
+ ? tr("None")
+ : listVectorSizes.join(QLatin1String(", ")));
+
+ bool bMatrixShapeValid = vvecMultiPhaseData.size() == 4;
+ int nPointCount = bMatrixShapeValid
+ ? vvecMultiPhaseData[0].size() : 0;
+ for (int nVectorIndex = 1;
+ bMatrixShapeValid && nVectorIndex < 4;
+ ++nVectorIndex)
+ {
+ if (vvecMultiPhaseData[nVectorIndex].size() != nPointCount)
+ {
+ bMatrixShapeValid = false;
+ }
+ }
+ listLines << tr("Four-vector shape valid: %1")
+ .arg(bMatrixShapeValid ? tr("Yes") : tr("No"));
+
+ if (bMatrixShapeValid)
+ {
+ listLines << tr("Data rows (time, oil, gas, water):");
+ const int nMaxListedRows = 64;
+ const int nListedRows = qMin(nPointCount, nMaxListedRows);
+ for (int nPointIndex = 0;
+ nPointIndex < nListedRows;
+ ++nPointIndex)
+ {
+ listLines << QString(" [%1] t=%2, oil=%3, gas=%4, water=%5")
+ .arg(nPointIndex + 1)
+ .arg(diagNumberText(
+ vvecMultiPhaseData[0][nPointIndex]))
+ .arg(diagNumberText(
+ vvecMultiPhaseData[1][nPointIndex]))
+ .arg(diagNumberText(
+ vvecMultiPhaseData[2][nPointIndex]))
+ .arg(diagNumberText(
+ vvecMultiPhaseData[3][nPointIndex]));
+ }
+ if (nPointCount > nListedRows)
+ {
+ listLines << tr(" ... %1 additional rows omitted")
+ .arg(nPointCount - nListedRows);
+ }
+ }
+ }
+
+ return listLines.join(QLatin1String("\n"));
+}
diff --git a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp
index 84fc80a..fe53f83 100644
--- a/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxEditWellPlot.cpp
@@ -660,6 +660,12 @@ void nmWxEditWellPlot::onOkClicked()
pStoredWell, m_pNmDataWell);
const bool bGridInputChanged =
hasPebiGridInputChanged(pStoredWell, m_pNmDataWell);
+ const bool bCurveSelectionChanged = pStoredWell == nullptr ||
+ pStoredWell->getSelectedPressureGaugeCode() !=
+ m_pNmDataWell->getSelectedPressureGaugeCode() ||
+ pStoredWell->getSelectedFlowGaugeCode() !=
+ m_pNmDataWell->getSelectedFlowGaugeCode() ||
+ pStoredWell->getIndexF() != m_pNmDataWell->getIndexF();
// 应用时间变表皮状态到真实数据
nmDataWellBase* pRealWell = pManager->getCurWellData();
@@ -697,6 +703,25 @@ void nmWxEditWellPlot::onOkClicked()
return;
}
+ // 压力记录、流量记录或流动段切换后,旧的历史曲线缓存已经不再对应
+ // 当前选择。正式井写回完成后重新计算,保证结果快照和自动拟合读取新记录。
+ 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(bInputChanged) {
if(bGridInputChanged) {
diff --git a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
index c121296..f61de15 100644
--- a/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxIncludeOtherWells.cpp
@@ -19,6 +19,24 @@ bool isSupportedNumericalWell(nmDataWellBase* pWellData)
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
+
+// 表格中的“产量”表示该相至少出现过一个非零流量;全零相仍会保留在
+// 求解输入中,但不能在摘要里误导用户为该井实际存在该相产量。
+bool hasNonZeroRate(nmDataWellBase* pWellData, NM_PHASE_TYPE ePhase)
+{
+ if(pWellData == nullptr) {
+ return false;
+ }
+
+ const QVector vecPoints =
+ pWellData->getFlowSegmentPoints(ePhase);
+ for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
+ if(vecPoints[nIndex].y() != 0.0) {
+ return true;
+ }
+ }
+ return false;
+}
}
nmWxIncludeOtherWells::nmWxIncludeOtherWells(
@@ -124,17 +142,19 @@ void nmWxIncludeOtherWells::setupTable()
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
oWellRow.m_sWellCode);
- // 第三步:井别唯一决定产量列;不再把所有井都显示为油量。
+ // 第三步:多相井可同时具有多列产量,逐相读取实际非零流量。
oWellRow.m_sPressure = bHasValidPressure
? tr("History") : tr("None");
oWellRow.m_sOilProd = tr("None");
oWellRow.m_sGasProd = tr("None");
oWellRow.m_sWaterProd = tr("None");
- if(oWellRow.m_eWellCategory == NM_WellCategory_Oil) {
+ if(hasNonZeroRate(pWell, PHASE_Oil)) {
oWellRow.m_sOilProd = tr("Rate");
- } else if(oWellRow.m_eWellCategory == NM_WellCategory_Gas) {
+ }
+ if(hasNonZeroRate(pWell, PHASE_Gas)) {
oWellRow.m_sGasProd = tr("Rate");
- } else if(oWellRow.m_eWellCategory == NM_WellCategory_Water) {
+ }
+ if(hasNonZeroRate(pWell, PHASE_Water)) {
oWellRow.m_sWaterProd = tr("Rate");
}
oWellRow.m_eMode = NM_CaseWell_RateControlled;