feat(nmNum): 支持包含井配置压力记录与分相产量

- 在包含井对话框中增加井别、压力、分相产量和流动段选择
- 使用 Gauge Code 关联并保存各井压力流量记录
- 支持独立启用油、气、水相产量并同步求解输入
- 完善记录切换、结果失效、中文翻译及界面提示
feature/nmNum-multi-gauge-record
lh 4 days ago
parent 8356fe0b8f
commit 2be80141cd

@ -5189,26 +5189,44 @@ Please check your input coordinates.</source>
<source>Name</source>
<translation></translation>
</message>
<message>
<source>Well category</source>
<translation></translation>
</message>
<message>
<source>Included</source>
<translation></translation>
</message>
<message>
<source>Oil production</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Gas production</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Water production</source>
<translation></translation>
<translation></translation>
</message>
<message>
<source>Pressure</source>
<translation></translation>
</message>
<message><source>Flow segment</source><translation></translation></message>
<message><source>Oil well</source><translation></translation></message>
<message><source>Gas well</source><translation></translation></message>
<message><source>Water well</source><translation></translation></message>
<message><source>Unknown</source><translation></translation></message>
<message><source>None</source><translation></translation></message>
<message><source>(unnamed)</source><translation></translation></message>
<message><source> (Empty)</source><translation></translation></message>
<message><source> (Corrupt)</source><translation></translation></message>
<message><source> (Invalid)</source><translation></translation></message>
<message><source> (No data)</source><translation></translation></message>
<message><source>Segment %1</source><translation> %1 </translation></message>
<message><source>No eligible wells</source><translation></translation></message>
<message><source>Select at least one production record first.</source><translation></translation></message>
<message>
<source>OK</source>
<translation></translation>
@ -7192,6 +7210,7 @@ Average pressure in contour: %2 MPa</source>
<message><source>Pressure Record:</source><translation></translation></message>
<message><source>Flow Record:</source><translation></translation></message>
<message><source>Display Phase:</source><translation></translation></message>
<message><source>This page only displays the selected record.</source><translation></translation></message>
<message><source> (Invalid)</source><translation> ()</translation></message>
<message><source> (Empty)</source><translation> ()</translation></message>
<message><source> (Corrupt)</source><translation> ()</translation></message>

@ -28,6 +28,7 @@ struct nmPebiWellInputSnapshot
{
nmPebiWellInputSnapshot()
: m_eWellCategory(NM_WellCategory_Unknown),
m_eFlowSchedulePhase(PHASE_UNKNOWN),
m_nFlowSectionIndex(1),
m_dRadius(0.0),
m_dWellboreStorage(0.0),
@ -49,6 +50,7 @@ struct nmPebiWellInputSnapshot
QVector<double> m_vecGasRates; ///< 与公共流动段逐项对应的气相流量。
QVector<double> m_vecWaterRates; ///< 与公共流动段逐项对应的水相流量。
NM_WELL_CATEGORY m_eWellCategory; ///< 求解时冻结的油井、气井或水井井别。
NM_PHASE_TYPE m_eFlowSchedulePhase; ///< 旧单相后处理使用的当前制度参考相。
/** @brief 求解时冻结的历史压力曲线。 */
QVector<QVector<double> > m_vecHistoryPressure;
/** @brief 求解时冻结的历史双对数曲线。 */

@ -153,9 +153,13 @@ public:
bool hasFlowPhaseData(NM_PHASE_TYPE eFlowPhase) const;
/** @brief 油、气、水中是否至少有一相包含真实流动段。 */
bool hasAnyFlowData() const;
/** @brief 返回井别对应相去除可选 (0,0) 占位点后的真实流动段。 */
/** @brief 全部框架记录中是否存在至少一个可用的非零产量相。 */
bool hasAvailableFlowRate() const;
/** @brief 返回当前启用相的公共流量制度;优先取井别对应相。 */
QVector<QPointF> getFlowSegmentPoints() const;
/** @brief 返回井别对应相的真实流动段数量。 */
/** @brief 返回当前公共流量制度采用的参考相;没有启用相时返回 Unknown。 */
NM_PHASE_TYPE getFlowSchedulePhase() const;
/** @brief 返回当前启用相共用的真实流动段数量。 */
int getFlowSegmentCount() const;
/** @brief 校验当前流动段索引是否位于一基的真实流动段范围内。 */
bool hasValidFlowSectionIndex() const;
@ -183,6 +187,18 @@ public:
QVector<nmPressureGaugeRecord> getPressureRecords() const;
/** @brief 返回全部流量记录(包含损坏/空/身份无效记录)。 */
QVector<nmFlowGaugeRecord> getFlowRecords() const;
/** @brief 返回指定相是否作为当前流量制度的一部分传给求解器。 */
bool isFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase) const;
/** @brief 设置指定相是否参与当前流量制度;井别不会随此状态改变。 */
void setFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase, bool bUsed);
// 以下显式接口供现有 UI 和快照组装代码使用,语义与上面的分相接口一致。
// 三个开关只控制求解器输入,不修改框架原始记录,也不改变工程井井别。
bool getUseOilRate() const;
bool getUseGasRate() const;
bool getUseWaterRate() const;
void setUseOilRate(bool bUsed);
void setUseGasRate(bool bUsed);
void setUseWaterRate(bool bUsed);
int getIndexF() const;
void setIndexF(const int newIndex);
@ -319,6 +335,9 @@ protected:
QVector<nmFlowGaugeRecord> m_vecFlowRecords; // 全部流量测量记录(可能包含损坏/空/身份无效记录)
QString m_sSelectedPressureGaugeCode; // 当前选中的压力记录 Gauge Code为空表示未选中
QString m_sSelectedFlowGaugeCode; // 当前选中的流量记录 Gauge Code为空表示未选中
bool m_bUseOilRate; // 当前流量制度是否传入油相产量
bool m_bUseGasRate; // 当前流量制度是否传入气相产量
bool m_bUseWaterRate; // 当前流量制度是否传入水相产量
int m_nIndexF; // 当前流量段的索引

@ -59,12 +59,6 @@ private slots:
/** @brief 将流量曲线点击时间映射为表格行选择。 */
void slotFlowChartPointClicked(double dTimePoint);
/** @brief 切换压力记录下拉框,按新选中的 Gauge Code 重新读取数据。 */
void slotPressureGaugeChanged(int nIndex);
/** @brief 切换流量记录下拉框,按新选中的 Gauge Code 恢复流动段索引并重新读取数据。 */
void slotFlowGaugeChanged(int nIndex);
/** @brief 切换显示相下拉框,仅重新按新相取点刷新视图,不改变井数据。 */
void slotDisplayPhaseChanged(int nIndex);
@ -168,8 +162,8 @@ private:
int m_nSelectedFlowSegment;
QWidget* m_pRecordSelectionBar; ///< 顶部压力/流量记录与显示相下拉框所在行容器。
QComboBox* m_pPressureGaugeCombo; ///< 压力记录下拉框itemData 存 Gauge Code。
QComboBox* m_pFlowGaugeCombo; ///< 流量记录下拉框itemData 存 Gauge Code。
QComboBox* m_pPressureGaugeCombo; ///< 当前压力记录只读展示itemData 存 Gauge Code。
QComboBox* m_pFlowGaugeCombo; ///< 当前流量记录只读展示itemData 存 Gauge Code。
QComboBox* m_pDisplayPhaseCombo; ///< 显示相下拉框itemData 存 NM_PHASE_TYPE。
QWidget* m_pDisplayPhaseContainer; ///< 承载显示相下拉框与标签,整体隐藏用。
QPushButton* m_pCheckDataButton; ///< 临时“检查数据”诊断按钮,只读展示用。

@ -4,8 +4,10 @@
#include "nmSubWxs_global.h"
#include "iDlgBase.h"
#include "nmDataNumericalAnalysisCase.h"
#include "nmDataGaugeRecord.h"
#include <QTableWidget>
#include <QPushButton>
#include <QComboBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QHeaderView>
@ -25,10 +27,14 @@ struct WellDataRow {
bool m_bCanCalculate; ///< 当前井型和几何参数是否可进入 PEBI 模型。
NM_WELL_CATEGORY m_eWellCategory; ///< 工程井井别,用于单相记录归相及求解模型匹配。
NM_CASE_WELL_MODE m_eMode; ///< 本列表固定保存有产量主动井角色。
QString m_sOilProd; ///< 油产量列的摘要文本。
QString m_sGasProd; ///< 气产量列的摘要文本。
QString m_sWaterProd; ///< 水产量列的摘要文本。
QString m_sPressure; ///< 压力历史列的摘要文本。
QString m_sPressureGaugeCode; ///< 本次选择的压力记录,为空表示“无”。
QString m_sFlowGaugeCode; ///< 三个产量列共同使用的流量记录。
bool m_bUseOilRate; ///< 是否使用公共流量记录中的油相产量。
bool m_bUseGasRate; ///< 是否使用公共流量记录中的气相产量。
bool m_bUseWaterRate; ///< 是否使用公共流量记录中的水相产量。
int m_nFlowSegmentIndex; ///< 当前流量记录的一基流动段索引。
QVector<nmPressureGaugeRecord> m_vecPressureRecords; ///< 供压力列建立下拉项。
QVector<nmFlowGaugeRecord> m_vecFlowRecords; ///< 供三个产量列建立下拉项。
/** @brief 初始化未勾选、不可计算的主动井候选行。 */
WellDataRow()
@ -36,13 +42,20 @@ struct WellDataRow {
m_bIsPrimary(false),
m_bCanCalculate(false),
m_eWellCategory(NM_WellCategory_Unknown),
m_eMode(NM_CaseWell_RateControlled)
m_eMode(NM_CaseWell_RateControlled),
m_bUseOilRate(false),
m_bUseGasRate(false),
m_bUseWaterRate(false),
m_nFlowSegmentIndex(0)
{
}
/** @brief WellCode 有效且井型/几何可计算时允许用户勾选。 */
/** @brief 至少选择一个产量相时,该井才可作为主动井提交。 */
bool canCalculate() const {
return m_bCanCalculate && !m_sWellCode.isEmpty();
return m_bCanCalculate && !m_sWellCode.isEmpty() &&
!m_sFlowGaugeCode.isEmpty() &&
(m_bUseOilRate || m_bUseGasRate || m_bUseWaterRate) &&
m_nFlowSegmentIndex > 0;
}
};
@ -72,15 +85,31 @@ private:
/** @brief 将一条候选井数据写入指定表格行。 */
void addWellDataToTable(const WellDataRow& data, int row);
/** @brief 根据一行的公共流量记录重新建立三个分相下拉框和流动段。 */
void refreshRowEditors(int row);
/** @brief 校正一行的分相状态及一基流动段索引。 */
void normalizeFlowSelection(WellDataRow& data) const;
/** @brief 更新包含复选框;没有有效分相选择时自动取消包含。 */
void updateIncludeCheckBox(int row);
/** @brief 填充一列分相流量记录选项。 */
void populateRateCombo(QComboBox* pComboBox,
const WellDataRow& data, NM_PHASE_TYPE ePhase) const;
/** @brief 填充压力记录选项。 */
void populatePressureCombo(QComboBox* pComboBox,
const WellDataRow& data) const;
/** @brief 填充当前公共流量记录对应的流动段选项。 */
void populateFlowSegmentCombo(QComboBox* pComboBox,
const WellDataRow& data) const;
// 更新复选框状态
//void updateCheckBoxState(int row);
private slots:
/** @brief 响应整行点击,并切换该行包含状态。 */
void onItemClicked(QTableWidgetItem* item);
/** @brief 把复选框状态同步回对应的 WellDataRow。 */
void onCheckBoxStateChanged(int state);
private slots:
/** @brief 把复选框状态同步回对应的 WellDataRow。 */
void onCheckBoxStateChanged(int state);
/** @brief 切换某一相使用的流量记录;其他已选相同步到同一记录。 */
void onRateSourceChanged(int nIndex);
/** @brief 切换该井使用的压力记录。 */
void onPressureSourceChanged(int nIndex);
/** @brief 切换该井的一基流动段索引。 */
void onFlowSegmentChanged(int nIndex);
private:
/** @brief 当前对话框所属的数据管理器,不拥有对象。 */
@ -92,6 +121,18 @@ private:
QVBoxLayout* m_pMainLayout; ///< 对话框主布局。
QVector<WellDataRow> m_vecWellData; ///< 与表格行一一对应的候选井数据。
enum TableColumn {
ColumnName = 0,
ColumnWellCategory,
ColumnIncluded,
ColumnOilRate,
ColumnGasRate,
ColumnWaterRate,
ColumnPressure,
ColumnFlowSegment,
ColumnCount
};
};
#endif // NMWXINCLUDEOTHERWELLS_H

@ -176,17 +176,17 @@ bool isFiniteSolverNumber(double value)
#endif
}
// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时按井别取参考相。
// PEBI 主求解已经在场景中接收油、气、水三相全部流量。
// 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时取输入捕获阶段
// 冻结的制度参考相。PEBI 主求解仍在场景中接收油、气、水三相全部流量。
const QVector<double>* referenceRatesOf(
const nmPebiWellInputSnapshot& oWellInput)
{
switch(oWellInput.m_eWellCategory) {
case NM_WellCategory_Oil:
switch(oWellInput.m_eFlowSchedulePhase) {
case PHASE_Oil:
return &oWellInput.m_vecOilRates;
case NM_WellCategory_Gas:
case PHASE_Gas:
return &oWellInput.m_vecGasRates;
case NM_WellCategory_Water:
case PHASE_Water:
return &oWellInput.m_vecWaterRates;
default:
return nullptr;

@ -227,33 +227,25 @@ bool captureWellFlowSchedule(
const QVector<QPointF> vecWaterPoints =
pWellData->getFlowSegmentPoints(PHASE_Water);
const QVector<QPointF>* 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()) {
// 公共段时长取自当前至少一个启用相;井别只影响首选顺序,不裁剪相数据。
oWellInput.m_eFlowSchedulePhase =
pWellData->getFlowSchedulePhase();
const QVector<QPointF> vecTimePoints =
pWellData->getFlowSegmentPoints();
if(oWellInput.m_eFlowSchedulePhase == PHASE_UNKNOWN ||
vecTimePoints.isEmpty()) {
return false;
}
const int nSegmentCount = pTimePoints->size();
if((!vecOilPoints.isEmpty() && vecOilPoints.size() != nSegmentCount) ||
(!vecGasPoints.isEmpty() && vecGasPoints.size() != nSegmentCount) ||
(!vecWaterPoints.isEmpty() && vecWaterPoints.size() != nSegmentCount)) {
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;
}
@ -261,14 +253,19 @@ bool captureWellFlowSchedule(
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((*pTimePoints)[nIndex].x());
oWellInput.m_vecFlowDurations.append(vecTimePoints[nIndex].x());
oWellInput.m_vecOilRates.append(
vecOilPoints.isEmpty() ? 0.0 : vecOilPoints[nIndex].y());
!pWellData->getUseOilRate() || vecOilPoints.isEmpty()
? 0.0 : vecOilPoints[nIndex].y());
oWellInput.m_vecGasRates.append(
vecGasPoints.isEmpty() ? 0.0 : vecGasPoints[nIndex].y());
!pWellData->getUseGasRate() || vecGasPoints.isEmpty()
? 0.0 : vecGasPoints[nIndex].y());
oWellInput.m_vecWaterRates.append(
vecWaterPoints.isEmpty() ? 0.0 : vecWaterPoints[nIndex].y());
!pWellData->getUseWaterRate() || vecWaterPoints.isEmpty()
? 0.0 : vecWaterPoints[nIndex].y());
}
return true;
}

@ -4150,16 +4150,17 @@ nmDataAnalyzeManager::getEffectiveCalculationWells() const
continue;
}
const bool bHasValidRate =
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->getFlowSegmentCount() > 0;
if(!bHasValidRate) {
const bool bHasAvailableRate =
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->hasAvailableFlowRate();
if(!bHasAvailableRate) {
vecEffectiveWells.append(nmCalculationWellRef(
pWellData->getWellCode(),
NM_CaseWell_Observation));
setEffectiveWellCodes.insert(pWellData->getWellCode());
} else if(setIncludedRateWellCodes.contains(
pWellData->getWellCode())) {
} else if(setIncludedRateWellCodes.contains(
pWellData->getWellCode()) &&
pWellData->getFlowSegmentCount() > 0) {
vecEffectiveWells.append(nmCalculationWellRef(
pWellData->getWellCode(),
NM_CaseWell_RateControlled));

@ -48,8 +48,22 @@ static const TRecord* findGaugeRecordByCode(const QVector<TRecord>& vecRecords,
static const QString kManualPressureGaugeCode = QString::fromLatin1("__ManualPressureRecord__");
static const QString kManualFlowGaugeCode = QString::fromLatin1("__ManualFlowRecord__");
// 判断一组流动段中是否存在实际产量;公共 (0,0) 占位点和全零相均不算产量。
static bool hasNonZeroFlowSegment(const QVector<QPointF>& vecPoints)
{
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
if(vecPoints[nIndex].y() != 0.0) {
return true;
}
}
return false;
}
nmDataWellBase::nmDataWellBase()
: m_eWellCategory(NM_WellCategory_Unknown)
, m_bUseOilRate(true)
, m_bUseGasRate(true)
, m_bUseWaterRate(true)
, m_nIndexF(0)
, m_bPlotVisible(true)
, m_bTimeDependentSkin(false)
@ -165,6 +179,9 @@ nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
m_vecFlowRecords = other.m_vecFlowRecords;
m_sSelectedPressureGaugeCode = other.m_sSelectedPressureGaugeCode;
m_sSelectedFlowGaugeCode = other.m_sSelectedFlowGaugeCode;
m_bUseOilRate = other.m_bUseOilRate;
m_bUseGasRate = other.m_bUseGasRate;
m_bUseWaterRate = other.m_bUseWaterRate;
m_nIndexF = other.m_nIndexF;
m_finalWellboreStorage = other.m_finalWellboreStorage;
m_inputWellHead = other.m_inputWellHead;
@ -285,6 +302,10 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType&
rapidjson::Value(baFlowGaugeCode.constData(),
static_cast<rapidjson::SizeType>(baFlowGaugeCode.size()),
allocator).Move(), allocator);
// 井别只描述工程井属性,三相是否参与求解必须独立保存。
wellObject.AddMember("UseOilRate", m_bUseOilRate, allocator);
wellObject.AddMember("UseGasRate", m_bUseGasRate, allocator);
wellObject.AddMember("UseWaterRate", m_bUseWaterRate, allocator);
// 序列化射孔段集合 (现在处理指针)
rapidjson::Value perforationsArray(rapidjson::kArrayType);
@ -455,6 +476,18 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_sSelectedFlowGaugeCode = QString::fromUtf8(
jsonValue["SelectedFlowGaugeCode"].GetString());
}
if(jsonValue.HasMember("UseOilRate") &&
jsonValue["UseOilRate"].IsBool()) {
m_bUseOilRate = jsonValue["UseOilRate"].GetBool();
}
if(jsonValue.HasMember("UseGasRate") &&
jsonValue["UseGasRate"].IsBool()) {
m_bUseGasRate = jsonValue["UseGasRate"].GetBool();
}
if(jsonValue.HasMember("UseWaterRate") &&
jsonValue["UseWaterRate"].IsBool()) {
m_bUseWaterRate = jsonValue["UseWaterRate"].GetBool();
}
// 反序列化射孔段集合 (现在处理指针)
if(jsonValue.HasMember("Perforations") && jsonValue["Perforations"].IsArray()) {
@ -909,6 +942,72 @@ QVector<nmFlowGaugeRecord> nmDataWellBase::getFlowRecords() const
return m_vecFlowRecords;
}
bool nmDataWellBase::isFlowPhaseUsed(NM_PHASE_TYPE eFlowPhase) const
{
// 分相开关是数值模块自己的求解配置,不反向写入框架 GaugeDataEx2。
switch(eFlowPhase) {
case PHASE_Oil:
return m_bUseOilRate;
case PHASE_Gas:
return m_bUseGasRate;
case PHASE_Water:
return m_bUseWaterRate;
default:
return false;
}
}
void nmDataWellBase::setFlowPhaseUsed(
NM_PHASE_TYPE eFlowPhase, bool bUsed)
{
// 未知相和组合相不是独立的求解数组,不能映射到任一分相开关。
switch(eFlowPhase) {
case PHASE_Oil:
m_bUseOilRate = bUsed;
break;
case PHASE_Gas:
m_bUseGasRate = bUsed;
break;
case PHASE_Water:
m_bUseWaterRate = bUsed;
break;
default:
break;
}
}
bool nmDataWellBase::getUseOilRate() const
{
// 保留显式 getter便于 Qt 4.8 时代的调用代码直接读取三个固定相。
return m_bUseOilRate;
}
bool nmDataWellBase::getUseGasRate() const
{
return m_bUseGasRate;
}
bool nmDataWellBase::getUseWaterRate() const
{
return m_bUseWaterRate;
}
void nmDataWellBase::setUseOilRate(bool bUsed)
{
// 这里只保存选择状态;记录存在性和流动段合法性由提交及快照阶段统一处理。
m_bUseOilRate = bUsed;
}
void nmDataWellBase::setUseGasRate(bool bUsed)
{
m_bUseGasRate = bUsed;
}
void nmDataWellBase::setUseWaterRate(bool bUsed)
{
m_bUseWaterRate = bUsed;
}
QVector<QPointF> nmDataWellBase::getFlowSegmentPoints(
NM_PHASE_TYPE eFlowPhase) const
{
@ -958,14 +1057,61 @@ bool nmDataWellBase::hasAnyFlowData() const
hasFlowPhaseData(PHASE_Water);
}
bool nmDataWellBase::hasAvailableFlowRate() const
{
// 这里判断框架事实,不受当前 Gauge Code 和三个分相开关影响。
// 数据管理器用它区分“未包含的产量井”和“自动观察井”。
for(int nRecordIndex = 0;
nRecordIndex < m_vecFlowRecords.size();
++nRecordIndex) {
const nmFlowGaugeRecord& oRecord = m_vecFlowRecords[nRecordIndex];
if(oRecord.eStatus != NM_GaugeRecord_Usable) {
continue;
}
if(hasNonZeroFlowSegment(oRecord.vecOilPoints) ||
hasNonZeroFlowSegment(oRecord.vecGasPoints) ||
hasNonZeroFlowSegment(oRecord.vecWaterPoints)) {
return true;
}
}
return false;
}
QVector<QPointF> nmDataWellBase::getFlowSegmentPoints() const
{
return getFlowSegmentPoints(getReferenceFlowPhase());
const NM_PHASE_TYPE eSchedulePhase = getFlowSchedulePhase();
return eSchedulePhase == PHASE_UNKNOWN
? QVector<QPointF>() : getFlowSegmentPoints(eSchedulePhase);
}
NM_PHASE_TYPE nmDataWellBase::getFlowSchedulePhase() const
{
// 公共流量制度优先使用井别对应且已启用的相;该相被设为“无”时,
// 再按油、气、水顺序选择其他已启用且有数据的相作为参考流量。
const NM_PHASE_TYPE eReferencePhase = getReferenceFlowPhase();
if(isFlowPhaseUsed(eReferencePhase) && hasNonZeroFlowSegment(
getFlowSegmentPoints(eReferencePhase))) {
return eReferencePhase;
}
const NM_PHASE_TYPE arrPhases[] = {
PHASE_Oil, PHASE_Gas, PHASE_Water
};
for(int nIndex = 0; nIndex < 3; ++nIndex) {
if(!isFlowPhaseUsed(arrPhases[nIndex])) {
continue;
}
const QVector<QPointF> vecPoints =
getFlowSegmentPoints(arrPhases[nIndex]);
if(hasNonZeroFlowSegment(vecPoints)) {
return arrPhases[nIndex];
}
}
return PHASE_UNKNOWN;
}
int nmDataWellBase::getFlowSegmentCount() const
{
return getFlowSegmentCount(getReferenceFlowPhase());
return getFlowSegmentPoints().size();
}
bool nmDataWellBase::hasValidFlowSectionIndex() const
@ -1762,10 +1908,10 @@ bool nmDataWellBase::exportHistoryRateToCsv(const QString& outDir)
QTextStream out(&file);
// 4) 表头与井别保持一致,便于离线排查实际进入求解器的相别
const char* pRateColumn = m_eWellCategory == NM_WellCategory_Gas
? "qg" : (m_eWellCategory == NM_WellCategory_Water
? "qw" : "qo");
// 4) 表头跟随当前制度参考相,避免井别相被设为“无”后误标流量列
const NM_PHASE_TYPE eSchedulePhase = getFlowSchedulePhase();
const char* pRateColumn = eSchedulePhase == PHASE_Gas
? "qg" : (eSchedulePhase == PHASE_Water ? "qw" : "qo");
out << "t," << pRateColumn << ",sectionIndex\n";
// 5) 写入每个点

@ -117,17 +117,14 @@ bool hasRealPhaseData(const QVector<QPointF>& vecPoints)
return false;
}
// 压力/流量记录下拉项文本:优先显示计量名称,否则回退到 Gauge Code再拼接测量时间。
// 压力/流量记录下拉框只显示稳定的 Gauge Code避免顶部工具栏过宽。
// 异常旧数据没有 Code 时,才回退到框架名称。
QString formatGaugeRecordLabel(const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime)
{
QString sBaseLabel = sGaugeName.isEmpty() ? sGaugeCode : sGaugeName;
if (sGaugeTime.isEmpty())
{
return sBaseLabel;
}
return sBaseLabel + QLatin1String(" | ") + sGaugeTime;
Q_UNUSED(sGaugeTime);
return sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
}
// 统一向压力/流量记录下拉框追加一项:身份无效的记录禁用不可选,空/损坏记录仍可选但追加提示后缀。
@ -844,45 +841,6 @@ void nmWellPressureFlowPage::slotFlowChartPointClicked(double dTimePoint)
findFlowSegmentByTableTime(dTableTimePoint));
}
void nmWellPressureFlowPage::slotPressureGaugeChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
const QString sCode = m_pPressureGaugeCombo->itemData(nIndex).toString();
if (sCode == m_pWell->getSelectedPressureGaugeCode())
{
return;
}
m_pWell->selectPressureGaugeCode(sCode);
refreshData();
}
void nmWellPressureFlowPage::slotFlowGaugeChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
const QString sCode = m_pFlowGaugeCombo->itemData(nIndex).toString();
if (sCode == m_pWell->getSelectedFlowGaugeCode())
{
return;
}
// 必须调用 switchSelectedFlowGaugeCode 而不是 selectFlowGaugeCode才能根据新记录
// 自己的 nIndexF 恢复/校验流动段选择,否则换记录后选择器就回到默认 0。
m_pWell->switchSelectedFlowGaugeCode(sCode);
// 换记录后显示相重置为新记录的默认相m_vecFlowGaugeRecords 仍为本井完整记录列表,
// 因此可在 refreshData() 重新填充下拉框前先用旧缓存安全查找新选中记录。
m_eDisplayPhase = defaultDisplayPhase(currentFlowRecord());
refreshData();
}
void nmWellPressureFlowPage::slotDisplayPhaseChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
@ -1067,12 +1025,16 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
m_pPressureGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->setMinimumWidth(160);
m_pPressureGaugeCombo->setToolTip(
tr("This page only displays the selected record."));
QLabel* pFlowLabel = new QLabel(
tr("Flow Record:"), m_pRecordSelectionBar);
m_pFlowGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->setMinimumWidth(160);
m_pFlowGaugeCombo->setToolTip(
tr("This page only displays the selected record."));
// 显示相是纯展示状态,整体容器随记录是否为多相含数据而隐藏/显示。
m_pDisplayPhaseContainer = new QWidget(m_pRecordSelectionBar);
@ -1101,14 +1063,6 @@ void nmWellPressureFlowPage::createRecordSelectionBar()
pBarLayout->addStretch(1);
pBarLayout->addWidget(m_pCheckDataButton);
connect(m_pPressureGaugeCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotPressureGaugeChanged(int)));
connect(m_pFlowGaugeCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotFlowGaugeChanged(int)));
connect(m_pDisplayPhaseCombo,
SIGNAL(currentIndexChanged(int)),
this,
@ -1138,10 +1092,11 @@ void nmWellPressureFlowPage::populatePressureGaugeCombo()
QVector<nmPressureGaugeRecord> vecRecords =
m_pWell->getPressureRecords();
m_pPressureGaugeCombo->setEnabled(!vecRecords.isEmpty());
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedPressureGaugeCode();
int nCurrentIndex = -1;
int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < vecRecords.size(); ++nIndex)
{
const nmPressureGaugeRecord& oRecord = vecRecords[nIndex];
@ -1150,13 +1105,10 @@ void nmWellPressureFlowPage::populatePressureGaugeCombo()
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
nCurrentIndex = nIndex;
nCurrentIndex = nIndex + 1;
}
}
if (nCurrentIndex >= 0)
{
m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
}
m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
@ -1180,10 +1132,11 @@ void nmWellPressureFlowPage::populateFlowGaugeCombo()
// 缓存整份记录列表供 currentFlowRecord() 安全返回内部指针,记录内容切换记录时不变。
m_vecFlowGaugeRecords = m_pWell->getFlowRecords();
m_pFlowGaugeCombo->setEnabled(!m_vecFlowGaugeRecords.isEmpty());
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedFlowGaugeCode();
int nCurrentIndex = -1;
int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
const nmFlowGaugeRecord& oRecord = m_vecFlowGaugeRecords[nIndex];
@ -1192,13 +1145,10 @@ void nmWellPressureFlowPage::populateFlowGaugeCombo()
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
nCurrentIndex = nIndex;
nCurrentIndex = nIndex + 1;
}
}
if (nCurrentIndex >= 0)
{
m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
}
m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}

@ -1,9 +1,13 @@
#include "nmWxIncludeOtherWells.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include <QAbstractItemView>
#include <QCheckBox>
#include <QWidget>
#include <QHBoxLayout>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QWidget>
namespace
{
@ -20,16 +24,26 @@ bool isSupportedNumericalWell(nmDataWellBase* pWellData)
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
// 表格中的“产量”表示该相至少出现过一个非零流量;全零相仍会保留在
// 求解输入中,但不能在摘要里误导用户为该井实际存在该相产量。
bool hasNonZeroRate(nmDataWellBase* pWellData, NM_PHASE_TYPE ePhase)
// 从同一条流量记录中取得指定相的只读点集,避免三处分支判断各自漂移。
const QVector<QPointF>& phasePoints(
const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
{
if(pWellData == nullptr) {
return false;
switch(ePhase) {
case PHASE_Gas:
return oRecord.vecGasPoints;
case PHASE_Water:
return oRecord.vecWaterPoints;
case PHASE_Oil:
default:
return oRecord.vecOilPoints;
}
}
const QVector<QPointF> vecPoints =
pWellData->getFlowSegmentPoints(ePhase);
// “有该相产量”按至少一个非零值判断;只有公共零占位行不算真实产量。
bool hasNonZeroPhaseRate(
const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
{
const QVector<QPointF>& vecPoints = phasePoints(oRecord, ePhase);
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
if(vecPoints[nIndex].y() != 0.0) {
return true;
@ -37,6 +51,182 @@ bool hasNonZeroRate(nmDataWellBase* pWellData, NM_PHASE_TYPE ePhase)
}
return false;
}
// 下拉项只有在记录可用且该相确有非零产量时才允许选择。
bool isSelectableFlowPhase(
const nmFlowGaugeRecord& oRecord, NM_PHASE_TYPE ePhase)
{
return oRecord.eStatus == NM_GaugeRecord_Usable &&
hasNonZeroPhaseRate(oRecord, ePhase);
}
// 候选井至少要有一条可选分相记录;压力记录不是主动井候选的必要条件。
bool hasSelectableRateRecord(const QVector<nmFlowGaugeRecord>& vecRecords)
{
for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
const nmFlowGaugeRecord& oRecord = vecRecords[nIndex];
if(isSelectableFlowPhase(oRecord, PHASE_Oil) ||
isSelectableFlowPhase(oRecord, PHASE_Gas) ||
isSelectableFlowPhase(oRecord, PHASE_Water)) {
return true;
}
}
return false;
}
// Gauge Code 是记录的稳定身份,显示名称和测量时间都不能替代它参与关联。
const nmFlowGaugeRecord* findFlowRecord(
const QVector<nmFlowGaugeRecord>& vecRecords,
const QString& sGaugeCode)
{
if(sGaugeCode.isEmpty()) {
return nullptr;
}
for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
if(vecRecords[nIndex].sGaugeCode == sGaugeCode) {
return &vecRecords[nIndex];
}
}
return nullptr;
}
// 压力曲线至少需要两个点才能用于历史曲线计算。
bool isSelectablePressureRecord(const nmPressureGaugeRecord& oRecord)
{
return oRecord.eStatus == NM_GaugeRecord_Usable &&
oRecord.vecPressurePoints.size() >= 2;
}
// 打开对话框时只恢复仍存在且仍可用的压力记录 Code。
bool containsSelectablePressureCode(
const QVector<nmPressureGaugeRecord>& vecRecords,
const QString& sGaugeCode)
{
for(int nIndex = 0; nIndex < vecRecords.size(); ++nIndex) {
if(vecRecords[nIndex].sGaugeCode == sGaugeCode &&
isSelectablePressureRecord(vecRecords[nIndex])) {
return true;
}
}
return false;
}
// 下拉框只显示稳定的 Gauge Code避免名称和测量时间挤占表格列宽。
// 异常旧数据没有 Code 时,才回退到框架名称或“未命名”。
QString gaugeRecordLabel(const QString& sGaugeName,
const QString& sGaugeCode, const QString& sGaugeTime)
{
Q_UNUSED(sGaugeTime);
QString sLabel = sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
if(sLabel.isEmpty()) {
sLabel = nmWxIncludeOtherWells::tr("(unnamed)");
}
return sLabel;
}
// 异常记录仍显示在下拉列表中,后缀用于说明其不可用于当前选择的原因。
QString recordStatusSuffix(NM_GAUGE_RECORD_STATUS eStatus)
{
switch(eStatus) {
case NM_GaugeRecord_Empty:
return nmWxIncludeOtherWells::tr(" (Empty)");
case NM_GaugeRecord_Corrupt:
return nmWxIncludeOtherWells::tr(" (Corrupt)");
case NM_GaugeRecord_InvalidIdentity:
return nmWxIncludeOtherWells::tr(" (Invalid)");
default:
return QString();
}
}
// Qt 4.8 的 QComboBox 没有直接禁用单项的接口,通过其标准项模型设置标志。
void setComboItemEnabled(QComboBox* pComboBox, int nIndex, bool bEnabled)
{
QStandardItemModel* pModel = qobject_cast<QStandardItemModel*>(
pComboBox == nullptr ? nullptr : pComboBox->model());
QStandardItem* pItem = pModel == nullptr ? nullptr : pModel->item(nIndex);
if(pItem == nullptr) {
return;
}
if(bEnabled) {
pItem->setFlags(pItem->flags() | Qt::ItemIsEnabled);
} else {
pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
}
}
// 流动段索引是一基索引;统计段数时必须先排除框架可能写入的公共占位行。
int flowRecordSegmentCount(const nmFlowGaugeRecord& oRecord)
{
const QVector<QPointF>* pPoints = !oRecord.vecOilPoints.isEmpty()
? &oRecord.vecOilPoints : (!oRecord.vecGasPoints.isEmpty()
? &oRecord.vecGasPoints : (!oRecord.vecWaterPoints.isEmpty()
? &oRecord.vecWaterPoints : nullptr));
if(pPoints == nullptr) {
return 0;
}
bool bHasPlaceholder = false;
if(oRecord.bMultiPhase) {
bHasPlaceholder = !oRecord.vecOilPoints.isEmpty() &&
!oRecord.vecGasPoints.isEmpty() &&
!oRecord.vecWaterPoints.isEmpty() &&
oRecord.vecOilPoints.first() == QPointF(0.0, 0.0) &&
oRecord.vecGasPoints.first() == QPointF(0.0, 0.0) &&
oRecord.vecWaterPoints.first() == QPointF(0.0, 0.0);
} else {
bHasPlaceholder = pPoints->first() == QPointF(0.0, 0.0);
}
return pPoints->size() - (bHasPlaceholder ? 1 : 0);
}
// 井别只用于只读展示和默认参考相判断,不由油、气、水下拉选择反推。
QString wellCategoryText(NM_WELL_CATEGORY eWellCategory)
{
switch(eWellCategory) {
case NM_WellCategory_Oil:
return nmWxIncludeOtherWells::tr("Oil well");
case NM_WellCategory_Gas:
return nmWxIncludeOtherWells::tr("Gas well");
case NM_WellCategory_Water:
return nmWxIncludeOtherWells::tr("Water well");
default:
return nmWxIncludeOtherWells::tr("Unknown");
}
}
// WellDataRow 将三个固定相保存为独立开关,这里统一完成枚举到成员的映射。
bool rowPhaseUsed(const WellDataRow& oData, NM_PHASE_TYPE ePhase)
{
switch(ePhase) {
case PHASE_Oil:
return oData.m_bUseOilRate;
case PHASE_Gas:
return oData.m_bUseGasRate;
case PHASE_Water:
return oData.m_bUseWaterRate;
default:
return false;
}
}
void setRowPhaseUsed(
WellDataRow& oData, NM_PHASE_TYPE ePhase, bool bUsed)
{
switch(ePhase) {
case PHASE_Oil:
oData.m_bUseOilRate = bUsed;
break;
case PHASE_Gas:
oData.m_bUseGasRate = bUsed;
break;
case PHASE_Water:
oData.m_bUseWaterRate = bUsed;
break;
default:
break;
}
}
}
nmWxIncludeOtherWells::nmWxIncludeOtherWells(
@ -51,7 +241,7 @@ nmWxIncludeOtherWells::nmWxIncludeOtherWells(
nmWxIncludeOtherWells::~nmWxIncludeOtherWells()
{
// Qt的对象父子关系会自动处理内存释放
// Qt 的对象父子关系会自动处理控件释放。
}
void nmWxIncludeOtherWells::initUI()
@ -62,157 +252,389 @@ void nmWxIncludeOtherWells::initUI()
this->setupConnections();
this->setWindowTitle(tr("Include Other Wells"));
this->resize(772, 339);
// 缩小时依靠横向滚动保留稳定列宽,不让中英文标题挤压下拉内容。
this->setMinimumSize(760, 280);
this->resize(1240, 420);
}
void nmWxIncludeOtherWells::setupTable()
{
m_pTableWidget = new QTableWidget(this);
QVector<nmDataWellBase*> vecMapWells;
if(m_pDataManager != nullptr) {
vecMapWells = m_pDataManager->getWellDataList();
}
// 设置行列数
m_pTableWidget->setColumnCount(6);
m_pTableWidget->setRowCount(vecMapWells.size());
m_pTableWidget->setColumnCount(ColumnCount);
m_pTableWidget->setRowCount(0);
m_pTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pTableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
m_pTableWidget->setStyleSheet(
"QTableWidget::item:focus {"
" background: transparent;"
" color: black;"
"}"
);
// 设置水平表头(列标题)
QStringList headers;
headers << tr("Name") << tr("Included") << tr("Oil production")
<< tr("Gas production") << tr("Water production") << tr("Pressure");
m_pTableWidget->setHorizontalHeaderLabels(headers);
// 确保水平表头可见
m_pTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pTableWidget->setAlternatingRowColors(true);
m_pTableWidget->setWordWrap(false);
m_pTableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
m_pTableWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
QStringList listHeaders;
listHeaders << tr("Name") << tr("Well category") << tr("Included")
<< tr("Oil production") << tr("Gas production")
<< tr("Water production") << tr("Pressure")
<< tr("Flow segment");
m_pTableWidget->setHorizontalHeaderLabels(listHeaders);
m_pTableWidget->horizontalHeader()->setVisible(true);
m_pTableWidget->horizontalHeader()->setMinimumSectionSize(64);
m_pTableWidget->horizontalHeader()->setStretchLastSection(false);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnName, QHeaderView::Stretch);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnWellCategory, QHeaderView::ResizeToContents);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnIncluded, QHeaderView::Fixed);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnOilRate, QHeaderView::Interactive);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnGasRate, QHeaderView::Interactive);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnWaterRate, QHeaderView::Interactive);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnPressure, QHeaderView::Interactive);
m_pTableWidget->horizontalHeader()->setResizeMode(
ColumnFlowSegment, QHeaderView::Fixed);
m_pTableWidget->setColumnWidth(ColumnIncluded, 76);
m_pTableWidget->setColumnWidth(ColumnOilRate, 180);
m_pTableWidget->setColumnWidth(ColumnGasRate, 180);
m_pTableWidget->setColumnWidth(ColumnWaterRate, 180);
m_pTableWidget->setColumnWidth(ColumnPressure, 190);
m_pTableWidget->setColumnWidth(ColumnFlowSegment, 96);
m_pTableWidget->verticalHeader()->setVisible(false);
m_pTableWidget->verticalHeader()->setDefaultSectionSize(32);
m_pTableWidget->verticalHeader()->setMinimumSectionSize(28);
// 设置垂直表头(行标题)属性
m_pTableWidget->verticalHeader()->setVisible(true); // 保持垂直表头可见(用于行选择视觉反馈)
m_pTableWidget->verticalHeader()->setMinimumWidth(16); // 最小宽度
// 隐藏垂直表头的数字(设置为空文本)
QStringList emptyRowHeaders;
for(int i = 0; i < m_pTableWidget->rowCount(); ++i) {
emptyRowHeaders << "";
QVector<nmDataWellBase*> vecMapWells;
if(m_pDataManager != nullptr) {
vecMapWells = m_pDataManager->getWellDataList();
}
m_pTableWidget->setVerticalHeaderLabels(emptyRowHeaders);
// 加载数据
QString sPrimaryWellCode = m_pDataManager != nullptr
const QString sPrimaryWellCode = m_pDataManager != nullptr
? m_pDataManager->getPrimaryWellCode() : QString();
int nDisplayRow = 0;
// 第一步:候选来源是 Map而不是整个项目井库。该对话框只管理
// 其他有产量的生产/注入井;主井和无产量观察井都不在这里显示。
for(int i = 0; i < vecMapWells.size(); i++) {
nmDataWellBase* pWell = vecMapWells[i];
if(pWell == nullptr || !isSupportedNumericalWell(pWell)) {
// 候选来源仍严格限定为 Map 中的其他主动井;只把选择界面迁移到这里。
for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) {
nmDataWellBase* pWellData = vecMapWells[nIndex];
if(!isSupportedNumericalWell(pWellData) ||
pWellData->getWellCode() == sPrimaryWellCode ||
!nmIsValidWellCategory(pWellData->getWellCategory())) {
continue;
}
const bool bIsPrimary = pWell->getWellCode() == sPrimaryWellCode;
const bool bHasValidRate =
nmIsValidWellCategory(pWell->getWellCategory()) &&
pWell->getFlowSegmentCount() > 0;
const bool bHasValidPressure = pWell->getPressurePoints().size() >= 2;
if(bIsPrimary || !bHasValidRate) {
WellDataRow oWellRow;
oWellRow.m_sWellCode = pWellData->getWellCode();
oWellRow.m_sName = pWellData->getWellName();
oWellRow.m_bIsPrimary = false;
oWellRow.m_eWellCategory = pWellData->getWellCategory();
oWellRow.m_eMode = NM_CaseWell_RateControlled;
oWellRow.m_vecPressureRecords = pWellData->getPressureRecords();
oWellRow.m_vecFlowRecords = pWellData->getFlowRecords();
oWellRow.m_bCanCalculate =
hasSelectableRateRecord(oWellRow.m_vecFlowRecords);
if(!oWellRow.m_bCanCalculate) {
continue;
}
WellDataRow oWellRow;
oWellRow.m_sWellCode = pWell->getWellCode();
oWellRow.m_sName = pWell->getWellName();
oWellRow.m_bIsPrimary = bIsPrimary;
oWellRow.m_eWellCategory = pWell->getWellCategory();
// 第二步:读取这口有产量干扰井上次确认的包含状态。
oWellRow.m_bCanCalculate = true;
oWellRow.m_bIsIncluded =
m_pDataManager != nullptr &&
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(hasNonZeroRate(pWell, PHASE_Oil)) {
oWellRow.m_sOilProd = tr("Rate");
}
if(hasNonZeroRate(pWell, PHASE_Gas)) {
oWellRow.m_sGasProd = tr("Rate");
oWellRow.m_sPressureGaugeCode =
pWellData->getSelectedPressureGaugeCode();
if(!containsSelectablePressureCode(
oWellRow.m_vecPressureRecords,
oWellRow.m_sPressureGaugeCode)) {
oWellRow.m_sPressureGaugeCode.clear();
}
if(hasNonZeroRate(pWell, PHASE_Water)) {
oWellRow.m_sWaterProd = tr("Rate");
oWellRow.m_sFlowGaugeCode =
pWellData->getSelectedFlowGaugeCode();
oWellRow.m_bUseOilRate = pWellData->getUseOilRate();
oWellRow.m_bUseGasRate = pWellData->getUseGasRate();
oWellRow.m_bUseWaterRate = pWellData->getUseWaterRate();
oWellRow.m_nFlowSegmentIndex = pWellData->getIndexF();
const bool bHadRequestedPhase = oWellRow.m_bUseOilRate ||
oWellRow.m_bUseGasRate || oWellRow.m_bUseWaterRate;
normalizeFlowSelection(oWellRow);
// 新井默认启用三相;若框架当前选择失效,回退到第一条有产量记录。
// 用户已明确把三相都设为“无”时不自动恢复。
if(!oWellRow.canCalculate() && bHadRequestedPhase) {
for(int nRecordIndex = 0;
nRecordIndex < oWellRow.m_vecFlowRecords.size();
++nRecordIndex) {
const nmFlowGaugeRecord& oRecord =
oWellRow.m_vecFlowRecords[nRecordIndex];
if(oRecord.eStatus != NM_GaugeRecord_Usable) {
continue;
}
oWellRow.m_sFlowGaugeCode = oRecord.sGaugeCode;
oWellRow.m_bUseOilRate =
isSelectableFlowPhase(oRecord, PHASE_Oil);
oWellRow.m_bUseGasRate =
isSelectableFlowPhase(oRecord, PHASE_Gas);
oWellRow.m_bUseWaterRate =
isSelectableFlowPhase(oRecord, PHASE_Water);
normalizeFlowSelection(oWellRow);
if(oWellRow.canCalculate()) {
break;
}
}
}
oWellRow.m_eMode = NM_CaseWell_RateControlled;
// 添加到数据列表和表格
oWellRow.m_bIsIncluded = oWellRow.canCalculate() &&
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
oWellRow.m_sWellCode);
const int nRow = m_vecWellData.size();
m_vecWellData.append(oWellRow);
addWellDataToTable(oWellRow, nDisplayRow);
++nDisplayRow;
m_pTableWidget->setRowCount(nRow + 1);
addWellDataToTable(oWellRow, nRow);
}
m_pTableWidget->setRowCount(nDisplayRow); // 调整实际显示的行数
m_pTableWidget->horizontalHeader()->setStretchLastSection(true);
m_pTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
if(m_vecWellData.isEmpty()) {
m_pTableWidget->setRowCount(1);
m_pTableWidget->setSpan(0, 0, 1, ColumnCount);
QTableWidgetItem* pEmptyItem = new QTableWidgetItem(
tr("No eligible wells"));
pEmptyItem->setFlags(Qt::NoItemFlags);
pEmptyItem->setTextAlignment(Qt::AlignCenter);
m_pTableWidget->setItem(0, 0, pEmptyItem);
}
}
void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
void nmWxIncludeOtherWells::addWellDataToTable(
const WellDataRow& data, int row)
{
// Name列 - 不可编辑
QTableWidgetItem* nameItem = new QTableWidgetItem(data.m_sName);
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
nameItem->setData(Qt::UserRole, data.m_sWellCode); // 表格内部始终携带 WellCode
nameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_pTableWidget->setItem(row, 0, nameItem);
QTableWidgetItem* pNameItem = new QTableWidgetItem(data.m_sName);
pNameItem->setData(Qt::UserRole, data.m_sWellCode);
pNameItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_pTableWidget->setItem(row, ColumnName, pNameItem);
QTableWidgetItem* pCategoryItem = new QTableWidgetItem(
wellCategoryText(data.m_eWellCategory));
pCategoryItem->setTextAlignment(Qt::AlignCenter);
m_pTableWidget->setItem(row, ColumnWellCategory, pCategoryItem);
QWidget* pCheckContainer = new QWidget(m_pTableWidget);
QCheckBox* pCheckBox = new QCheckBox(pCheckContainer);
QHBoxLayout* pCheckLayout = new QHBoxLayout(pCheckContainer);
pCheckLayout->setContentsMargins(0, 0, 0, 0);
pCheckLayout->setAlignment(Qt::AlignCenter);
pCheckLayout->addWidget(pCheckBox);
pCheckBox->setProperty("row", row);
connect(pCheckBox, SIGNAL(stateChanged(int)),
this, SLOT(onCheckBoxStateChanged(int)));
m_pTableWidget->setCellWidget(row, ColumnIncluded, pCheckContainer);
const int arrRateColumns[] = {
ColumnOilRate, ColumnGasRate, ColumnWaterRate
};
const NM_PHASE_TYPE arrPhases[] = {
PHASE_Oil, PHASE_Gas, PHASE_Water
};
for(int nIndex = 0; nIndex < 3; ++nIndex) {
QComboBox* pRateCombo = new QComboBox(m_pTableWidget);
pRateCombo->setProperty("row", row);
pRateCombo->setProperty("phase", static_cast<int>(arrPhases[nIndex]));
pRateCombo->setMaxVisibleItems(16);
connect(pRateCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onRateSourceChanged(int)));
m_pTableWidget->setCellWidget(row, arrRateColumns[nIndex], pRateCombo);
}
// Included列 - 使用自定义复选框组件
QWidget* pWidget = new QWidget();
QCheckBox* pCheckBox = new QCheckBox();
QHBoxLayout* pLayout = new QHBoxLayout(pWidget);
pLayout->addWidget(pCheckBox);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0, 0, 0, 0);
pWidget->setLayout(pLayout);
QComboBox* pPressureCombo = new QComboBox(m_pTableWidget);
pPressureCombo->setProperty("row", row);
pPressureCombo->setMaxVisibleItems(16);
connect(pPressureCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onPressureSourceChanged(int)));
m_pTableWidget->setCellWidget(row, ColumnPressure, pPressureCombo);
pCheckBox->setChecked(data.m_bIsIncluded);
// 表内只有其他有产量井,用户可以决定它是否作为干扰井参与计算。
pCheckBox->setEnabled(data.canCalculate());
pCheckBox->setProperty("row", row); // 存储行索引
QComboBox* pSegmentCombo = new QComboBox(m_pTableWidget);
pSegmentCombo->setProperty("row", row);
connect(pSegmentCombo, SIGNAL(currentIndexChanged(int)),
this, SLOT(onFlowSegmentChanged(int)));
m_pTableWidget->setCellWidget(row, ColumnFlowSegment, pSegmentCombo);
// 连接复选框信号
connect(pCheckBox, SIGNAL(stateChanged(int)), this, SLOT(onCheckBoxStateChanged(int)));
refreshRowEditors(row);
}
// 设置单元格控件
m_pTableWidget->setCellWidget(row, 1, pWidget);
void nmWxIncludeOtherWells::normalizeFlowSelection(WellDataRow& data) const
{
const nmFlowGaugeRecord* pRecord = findFlowRecord(
data.m_vecFlowRecords, data.m_sFlowGaugeCode);
if(pRecord == nullptr ||
pRecord->eStatus != NM_GaugeRecord_Usable) {
data.m_sFlowGaugeCode.clear();
data.m_bUseOilRate = false;
data.m_bUseGasRate = false;
data.m_bUseWaterRate = false;
data.m_nFlowSegmentIndex = 0;
return;
}
data.m_bUseOilRate = data.m_bUseOilRate &&
isSelectableFlowPhase(*pRecord, PHASE_Oil);
data.m_bUseGasRate = data.m_bUseGasRate &&
isSelectableFlowPhase(*pRecord, PHASE_Gas);
data.m_bUseWaterRate = data.m_bUseWaterRate &&
isSelectableFlowPhase(*pRecord, PHASE_Water);
if(!data.m_bUseOilRate && !data.m_bUseGasRate &&
!data.m_bUseWaterRate) {
data.m_sFlowGaugeCode.clear();
data.m_nFlowSegmentIndex = 0;
return;
}
const int nSegmentCount = flowRecordSegmentCount(*pRecord);
if(nSegmentCount <= 0) {
data.m_sFlowGaugeCode.clear();
data.m_bUseOilRate = false;
data.m_bUseGasRate = false;
data.m_bUseWaterRate = false;
data.m_nFlowSegmentIndex = 0;
return;
}
if(data.m_nFlowSegmentIndex < 1 ||
data.m_nFlowSegmentIndex > nSegmentCount) {
data.m_nFlowSegmentIndex = pRecord->nIndexF;
if(data.m_nFlowSegmentIndex < 1 ||
data.m_nFlowSegmentIndex > nSegmentCount) {
data.m_nFlowSegmentIndex = nSegmentCount;
}
}
}
void nmWxIncludeOtherWells::populateRateCombo(
QComboBox* pComboBox, const WellDataRow& data,
NM_PHASE_TYPE ePhase) const
{
if(pComboBox == nullptr) {
return;
}
const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
pComboBox->clear();
pComboBox->addItem(tr("None"), QString());
// 三个分相下拉框都列出完整记录集合,但当前相无数据的记录保持可见且禁用。
for(int nIndex = 0; nIndex < data.m_vecFlowRecords.size(); ++nIndex) {
const nmFlowGaugeRecord& oRecord = data.m_vecFlowRecords[nIndex];
const bool bSelectable = isSelectableFlowPhase(oRecord, ePhase);
QString sLabel = gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
sLabel += recordStatusSuffix(oRecord.eStatus);
if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
sLabel += tr(" (No data)");
}
pComboBox->addItem(sLabel, oRecord.sGaugeCode);
setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
}
int nCurrentIndex = 0;
if(rowPhaseUsed(data, ePhase)) {
const int nFoundIndex = pComboBox->findData(data.m_sFlowGaugeCode);
if(nFoundIndex > 0) {
nCurrentIndex = nFoundIndex;
}
}
pComboBox->setCurrentIndex(nCurrentIndex);
pComboBox->setToolTip(pComboBox->currentText());
pComboBox->blockSignals(bSignalsWereBlocked);
}
void nmWxIncludeOtherWells::populatePressureCombo(
QComboBox* pComboBox, const WellDataRow& data) const
{
if(pComboBox == nullptr) {
return;
}
const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
pComboBox->clear();
pComboBox->addItem(tr("None"), QString());
for(int nIndex = 0; nIndex < data.m_vecPressureRecords.size(); ++nIndex) {
const nmPressureGaugeRecord& oRecord = data.m_vecPressureRecords[nIndex];
const bool bSelectable = isSelectablePressureRecord(oRecord);
QString sLabel = gaugeRecordLabel(
oRecord.sGaugeName, oRecord.sGaugeCode, oRecord.sGaugeTime);
sLabel += recordStatusSuffix(oRecord.eStatus);
if(oRecord.eStatus == NM_GaugeRecord_Usable && !bSelectable) {
sLabel += tr(" (No data)");
}
pComboBox->addItem(sLabel, oRecord.sGaugeCode);
setComboItemEnabled(pComboBox, pComboBox->count() - 1, bSelectable);
}
const int nCurrentIndex = pComboBox->findData(
data.m_sPressureGaugeCode);
pComboBox->setCurrentIndex(nCurrentIndex < 0 ? 0 : nCurrentIndex);
pComboBox->setToolTip(pComboBox->currentText());
pComboBox->blockSignals(bSignalsWereBlocked);
}
void nmWxIncludeOtherWells::populateFlowSegmentCombo(
QComboBox* pComboBox, const WellDataRow& data) const
{
if(pComboBox == nullptr) {
return;
}
const bool bSignalsWereBlocked = pComboBox->blockSignals(true);
pComboBox->clear();
const nmFlowGaugeRecord* pRecord = findFlowRecord(
data.m_vecFlowRecords, data.m_sFlowGaugeCode);
const int nSegmentCount = pRecord == nullptr
? 0 : flowRecordSegmentCount(*pRecord);
for(int nIndex = 1; nIndex <= nSegmentCount; ++nIndex) {
pComboBox->addItem(tr("Segment %1").arg(nIndex), nIndex);
}
const int nCurrentIndex = pComboBox->findData(
data.m_nFlowSegmentIndex);
if(nCurrentIndex >= 0) {
pComboBox->setCurrentIndex(nCurrentIndex);
}
pComboBox->setEnabled(data.canCalculate());
pComboBox->setToolTip(pComboBox->currentText());
pComboBox->blockSignals(bSignalsWereBlocked);
}
// 其他列只显示各类 simulation target与 Saphir 对话框保持一致。
QStringList listOtherData;
listOtherData.append(data.m_sOilProd);
listOtherData.append(data.m_sGasProd);
listOtherData.append(data.m_sWaterProd);
listOtherData.append(data.m_sPressure);
void nmWxIncludeOtherWells::refreshRowEditors(int row)
{
if(row < 0 || row >= m_vecWellData.size()) {
return;
}
const WellDataRow& oData = m_vecWellData[row];
populateRateCombo(qobject_cast<QComboBox*>(
m_pTableWidget->cellWidget(row, ColumnOilRate)),
oData, PHASE_Oil);
populateRateCombo(qobject_cast<QComboBox*>(
m_pTableWidget->cellWidget(row, ColumnGasRate)),
oData, PHASE_Gas);
populateRateCombo(qobject_cast<QComboBox*>(
m_pTableWidget->cellWidget(row, ColumnWaterRate)),
oData, PHASE_Water);
populatePressureCombo(qobject_cast<QComboBox*>(
m_pTableWidget->cellWidget(row, ColumnPressure)), oData);
populateFlowSegmentCombo(qobject_cast<QComboBox*>(
m_pTableWidget->cellWidget(row, ColumnFlowSegment)), oData);
updateIncludeCheckBox(row);
}
for(int col = 2; col < 6; col++) {
QTableWidgetItem* item = new QTableWidgetItem(listOtherData[col-2]);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
item->setData(Qt::UserRole, row);
item->setTextAlignment(Qt::AlignCenter);
m_pTableWidget->setItem(row, col, item);
void nmWxIncludeOtherWells::updateIncludeCheckBox(int row)
{
if(row < 0 || row >= m_vecWellData.size()) {
return;
}
WellDataRow& oData = m_vecWellData[row];
if(!oData.canCalculate()) {
oData.m_bIsIncluded = false;
}
QWidget* pContainer = m_pTableWidget->cellWidget(row, ColumnIncluded);
QCheckBox* pCheckBox = pContainer == nullptr
? nullptr : pContainer->findChild<QCheckBox*>();
if(pCheckBox == nullptr) {
return;
}
const bool bSignalsWereBlocked = pCheckBox->blockSignals(true);
pCheckBox->setEnabled(oData.m_bCanCalculate);
pCheckBox->setChecked(oData.m_bIsIncluded);
pCheckBox->setToolTip(oData.canCalculate()
? QString() : tr("Select at least one production record first."));
pCheckBox->blockSignals(bSignalsWereBlocked);
}
void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
@ -221,25 +643,91 @@ void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
if(pCheckBox == nullptr) {
return;
}
const int nRow = pCheckBox->property("row").toInt();
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
// 主井正常不会进入本表;保留该判断作为防御,避免未来改动误取消主井。
if(m_vecWellData[nRow].m_bIsPrimary) {
m_vecWellData[nRow].m_bIsIncluded = true;
WellDataRow& oData = m_vecWellData[nRow];
if(oData.m_bIsPrimary ||
(state == Qt::Checked && !oData.canCalculate())) {
oData.m_bIsIncluded = false;
updateIncludeCheckBox(nRow);
return;
}
oData.m_bIsIncluded = state == Qt::Checked;
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onRateSourceChanged(int nIndex)
{
QComboBox* pComboBox = qobject_cast<QComboBox*>(sender());
if(pComboBox == nullptr || nIndex < 0) {
return;
}
const int nRow = pComboBox->property("row").toInt();
const NM_PHASE_TYPE ePhase = static_cast<NM_PHASE_TYPE>(
pComboBox->property("phase").toInt());
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
// 其他候选井只记录选择状态,绝不在该对话框内创建或删除 Map 井。
m_vecWellData[nRow].m_bIsIncluded = (state == Qt::Checked);
WellDataRow& oData = m_vecWellData[nRow];
// 可见文本只用于展示,真正写回始终读取 itemData 中的 Gauge Code。
const QString sGaugeCode = pComboBox->itemData(nIndex).toString();
if(sGaugeCode.isEmpty()) {
setRowPhaseUsed(oData, ePhase, false);
} else {
const nmFlowGaugeRecord* pRecord = findFlowRecord(
oData.m_vecFlowRecords, sGaugeCode);
if(pRecord == nullptr || !isSelectableFlowPhase(*pRecord, ePhase)) {
refreshRowEditors(nRow);
return;
}
if(oData.m_sFlowGaugeCode != sGaugeCode) {
oData.m_sFlowGaugeCode = sGaugeCode;
oData.m_nFlowSegmentIndex = pRecord->nIndexF;
}
setRowPhaseUsed(oData, ePhase, true);
}
// PEBI 每口井只有一条 Rate.t。切换任一相的记录后其他已启用相
// 自动使用同一记录;新记录没有的相自动回到“无”。
normalizeFlowSelection(oData);
refreshRowEditors(nRow);
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onPressureSourceChanged(int nIndex)
{
QComboBox* pComboBox = qobject_cast<QComboBox*>(sender());
if(pComboBox == nullptr || nIndex < 0) {
return;
}
const int nRow = pComboBox->property("row").toInt();
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
m_vecWellData[nRow].m_sPressureGaugeCode =
pComboBox->itemData(nIndex).toString();
pComboBox->setToolTip(pComboBox->currentText());
m_pTableWidget->selectRow(nRow);
}
void nmWxIncludeOtherWells::onItemClicked(QTableWidgetItem* item)
void nmWxIncludeOtherWells::onFlowSegmentChanged(int nIndex)
{
QComboBox* pComboBox = qobject_cast<QComboBox*>(sender());
if(pComboBox == nullptr || nIndex < 0) {
return;
}
const int nRow = pComboBox->property("row").toInt();
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
m_vecWellData[nRow].m_nFlowSegmentIndex =
pComboBox->itemData(nIndex).toInt();
pComboBox->setToolTip(pComboBox->currentText());
m_pTableWidget->selectRow(nRow);
}
QVector<WellDataRow> nmWxIncludeOtherWells::getWellData() const
@ -249,24 +737,20 @@ QVector<WellDataRow> nmWxIncludeOtherWells::getWellData() const
void nmWxIncludeOtherWells::setupButtons()
{
// 创建确定和取消按钮
m_pOkButton = new QPushButton(tr("OK"), this);
m_pCancelButton = new QPushButton(tr("Cancel"), this);
}
void nmWxIncludeOtherWells::setupLayouts()
{
// 创建按钮布局并添加按钮
m_pButtonLayout = new QHBoxLayout();
m_pButtonLayout->addStretch(); // 添加伸缩项使按钮靠右
m_pButtonLayout->addStretch();
m_pButtonLayout->addWidget(m_pOkButton);
m_pButtonLayout->addWidget(m_pCancelButton);
// 创建主布局并添加控件
m_pMainLayout = new QVBoxLayout(this);
m_pMainLayout->addWidget(m_pTableWidget);
m_pMainLayout->addLayout(m_pButtonLayout);
setLayout(m_pMainLayout);
}
@ -274,6 +758,4 @@ void nmWxIncludeOtherWells::setupConnections()
{
connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(reject()));
connect(m_pTableWidget, SIGNAL(itemClicked(QTableWidgetItem*)),
this, SLOT(onItemClicked(QTableWidgetItem*)));
}

@ -64,6 +64,17 @@ bool isSupportedAdditionalWell(nmDataWellBase* pWellData)
eWellType == Horizontal_Fractured_Well;
}
// 可配置性看框架记录本身,不能看当前是否已把三个相都设为“无”,
// 否则用户清空分相后将无法再次打开对话框恢复选择。
bool hasAvailableRateTarget(nmDataWellBase* pWellData)
{
if(pWellData == nullptr ||
!nmIsValidWellCategory(pWellData->getWellCategory())) {
return false;
}
return pWellData->hasAvailableFlowRate();
}
}
nmWxNumericalDesign* nmWxNumericalDesign::s_pCurrentInstance = nullptr;
@ -1080,51 +1091,88 @@ void nmWxNumericalDesign::onOptionsIconClicked()
if(dlg.exec() == QDialog::Accepted) {
QVector<WellDataRow> vecWellRows = dlg.getWellData();
QVector<nmCalculationWellRef> vecIncludedWells;
bool bIncludedMembershipChanged = false;
bool bIncludedCurveSelectionChanged = false;
// 第一步:这里只收集 Map 中被勾选的有产量干扰井。主井由分析方案
// 单独保存;无产量观察井由数据管理层自动加入,与本对话框无关
// 第一步:先把每一行的压力、公共流量记录、分相开关和流动段写回。
// 这些状态属于井本身;未勾选井也保留配置,方便下次直接启用
for(int nRowIndex = 0; nRowIndex < vecWellRows.size(); ++nRowIndex) {
const WellDataRow& oWellRow = vecWellRows[nRowIndex];
if(oWellRow.m_bIsPrimary ||
!oWellRow.m_bIsIncluded ||
!oWellRow.canCalculate()) {
if(oWellRow.m_bIsPrimary) {
continue;
}
nmDataWellBase* pWellData =
m_pDataManager->findWellByCode(oWellRow.m_sWellCode);
if(pWellData == nullptr) {
// Map 中已经移除的井不能被 Include 对话框重新创建。
continue;
}
if(!nmIsValidWellCategory(pWellData->getWellCategory()) ||
pWellData->getFlowSegmentCount() <= 0) {
// 对话框打开后若产量数据发生变化,提交时仍要阻止它混入包含列表。
continue;
}
vecIncludedWells.append(
nmCalculationWellRef(oWellRow.m_sWellCode,
NM_CaseWell_RateControlled));
// 第二步:为被包含的有产量井同步历史压力、双对数和半对数数据。
QVector<QVector<double> > vecHistoryPressure;
QVector<QVector<double> > vecHistoryLogLog;
QVector<QVector<double> > vecHistorySemiLog;
m_pDataManager->calculationLogData(pWellData,
vecHistoryPressure,
vecHistoryLogLog,
vecHistorySemiLog);
pWellData->setHistoryPressure(vecHistoryPressure);
pWellData->setHistoryLogLog(vecHistoryLogLog);
pWellData->setHistorySemiLog(vecHistorySemiLog);
const bool bWasIncluded =
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
oWellRow.m_sWellCode);
const bool bWillBeIncluded = oWellRow.m_bIsIncluded &&
oWellRow.canCalculate();
bIncludedMembershipChanged = bIncludedMembershipChanged ||
bWasIncluded != bWillBeIncluded;
// 必须在写回前冻结差异;写回后井对象已无法提供旧的记录和分相状态。
const bool bCurveSelectionChanged =
pWellData->getSelectedPressureGaugeCode() !=
oWellRow.m_sPressureGaugeCode ||
pWellData->getSelectedFlowGaugeCode() !=
oWellRow.m_sFlowGaugeCode ||
pWellData->getUseOilRate() != oWellRow.m_bUseOilRate ||
pWellData->getUseGasRate() != oWellRow.m_bUseGasRate ||
pWellData->getUseWaterRate() != oWellRow.m_bUseWaterRate ||
pWellData->getIndexF() != oWellRow.m_nFlowSegmentIndex;
// 先切换公共流量记录,再写入分相开关和该记录的一基流动段索引。
// 这样 switchSelectedFlowGaugeCode() 可以先保存旧记录自己的段索引。
pWellData->selectPressureGaugeCode(
oWellRow.m_sPressureGaugeCode);
if(pWellData->getSelectedFlowGaugeCode() !=
oWellRow.m_sFlowGaugeCode) {
pWellData->switchSelectedFlowGaugeCode(
oWellRow.m_sFlowGaugeCode);
}
pWellData->setUseOilRate(oWellRow.m_bUseOilRate);
pWellData->setUseGasRate(oWellRow.m_bUseGasRate);
pWellData->setUseWaterRate(oWellRow.m_bUseWaterRate);
pWellData->setIndexF(oWellRow.m_nFlowSegmentIndex);
if(bCurveSelectionChanged) {
QVector<QVector<double> > vecHistoryPressure;
QVector<QVector<double> > vecHistoryLogLog;
QVector<QVector<double> > vecHistorySemiLog;
m_pDataManager->calculationLogData(pWellData,
vecHistoryPressure,
vecHistoryLogLog,
vecHistorySemiLog);
pWellData->setHistoryPressure(vecHistoryPressure);
pWellData->setHistoryLogLog(vecHistoryLogLog);
pWellData->setHistorySemiLog(vecHistorySemiLog);
if(bWasIncluded || bWillBeIncluded) {
bIncludedCurveSelectionChanged = true;
}
}
if(bWillBeIncluded) {
vecIncludedWells.append(
nmCalculationWellRef(oWellRow.m_sWellCode,
NM_CaseWell_RateControlled));
}
}
// 第三步:一次性提交选择,内部会使旧网格和旧结果失效。
// 第二步:井集合改变会使网格和结果一起失效;仅更换曲线或分相时
// 保留现有网格,只使旧求解结果失效。
m_pDataManager->setIncludedCalculationWells(vecIncludedWells);
m_pDataManager->setIncludeOtherWells(
m_pIncludeOtherWellsCheck->isChecked());
if(bIncludedCurveSelectionChanged &&
!bIncludedMembershipChanged) {
m_pDataManager->invalidatePebiResults();
}
nmWxParameterProperty::notifyUpdateTable();
fillResultWellCombo();
@ -1215,9 +1263,7 @@ void nmWxNumericalDesign::updateIncludeOtherWellsAvailability()
continue;
}
const bool bHasRateTarget =
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->getFlowSegmentCount() > 0;
const bool bHasRateTarget = hasAvailableRateTarget(pWellData);
if(bHasRateTarget) {
bHasEligibleOtherWell = true;
break;

Loading…
Cancel
Save