fix(numerical): 修正包含井流量相别与流动段索引

- 根据井别读取并写入对应的 qo、qg、qw 流量
- 统一流动段一基索引及可选零点占位处理
- 在求解输入和结果快照中保存并校验井别
- 升级数值项目及结果快照格式版本
- 在编辑井和包含井界面展示正确井别与流量列
feature/Adapt-Autofit-20260904
lh 2 months ago
parent bf82abbf72
commit 39733d2e71

Binary file not shown.

@ -7130,6 +7130,12 @@ Average pressure in contour: %2 MPa</source>
<message><source>Well information</source><translation>井信息</translation></message>
<message><source>Well number</source><translation>井号</translation></message>
<message><source>Well name</source><translation>井名</translation></message>
<message><source>Well category</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>Well model</source><translation>井型</translation></message>
<message><source>Well type</source><translation>井型</translation></message>
<message><source>Project-level unique well identifier</source><translation>项目级唯一井标识</translation></message>
<message><source>Vertical well</source><translation>直井</translation></message>

@ -27,7 +27,8 @@ class nmDataWellBase;
struct nmPebiWellInputSnapshot
{
nmPebiWellInputSnapshot()
: m_nFlowSectionIndex(1),
: m_eWellCategory(NM_WellCategory_Unknown),
m_nFlowSectionIndex(1),
m_dRadius(0.0),
m_dWellboreStorage(0.0),
m_dSkin(0.0),
@ -44,6 +45,7 @@ struct nmPebiWellInputSnapshot
QString m_sWellName; ///< 捕获时井名称。
QString m_sWellInstanceId; ///< 完整场结果使用的稳定井 UUID。
QVector<QPointF> m_vecFlowPoints; ///< 求解使用的流量制度。
NM_WELL_CATEGORY m_eWellCategory; ///< 求解时冻结的油井、气井或水井井别。
/** @brief 求解时冻结的历史压力曲线。 */
QVector<QVector<double> > m_vecHistoryPressure;
/** @brief 求解时冻结的历史双对数曲线。 */
@ -51,7 +53,7 @@ struct nmPebiWellInputSnapshot
/** @brief 求解时冻结的历史半对数曲线。 */
QVector<QVector<double> > m_vecHistorySemiLog;
QPointF m_oLocation; ///< 求解时井平面位置。
int m_nFlowSectionIndex; ///< 当前流动段下标。
int m_nFlowSectionIndex; ///< 当前流动段一基索引。
double m_dRadius; ///< 求解时井筒半径。
double m_dWellboreStorage; ///< 求解时井筒储集系数。
double m_dSkin; ///< 求解时表皮系数。

@ -121,6 +121,37 @@ enum NM_SOLVER_MODEL_TYPE {
SMT_Oil_Gas_Water_ThreePhase = 10
};
/** @brief 判断求解模型是否包含指定工程井别对应的流量相。 */
inline bool nmSolverModelSupportsWellCategory(
NM_SOLVER_MODEL_TYPE eSolverModelType,
NM_WELL_CATEGORY eWellCategory)
{
switch(eSolverModelType) {
case SMT_Oil_ConstPvt:
case SMT_Oil_VariablePvt:
return eWellCategory == NM_WellCategory_Oil;
case SMT_Water_ConstPvt:
case SMT_Water_VariablePvt:
return eWellCategory == NM_WellCategory_Water;
case SMT_Gas_VariablePvt:
case SMT_Gas_PseudoPressure:
return eWellCategory == NM_WellCategory_Gas;
case SMT_Oil_Gas_TwoPhase:
return eWellCategory == NM_WellCategory_Oil ||
eWellCategory == NM_WellCategory_Gas;
case SMT_Oil_Water_TwoPhase:
return eWellCategory == NM_WellCategory_Oil ||
eWellCategory == NM_WellCategory_Water;
case SMT_Gas_Water_TwoPhase:
return eWellCategory == NM_WellCategory_Gas ||
eWellCategory == NM_WellCategory_Water;
case SMT_Oil_Gas_Water_ThreePhase:
return nmIsValidWellCategory(eWellCategory);
default:
return false;
}
}
class nmDataWellBase;
class ZxDataWell;
class nmDataReservoir;
@ -704,7 +735,8 @@ public:
}
// 加载井数组里对应的压力、流量数据,这个是在井里没有这些数据的情况下调用
void loadWellPreAndFlow();
/** @brief 按保存井别重新读取工程井压力和流量,任一井映射失败时返回 false。 */
bool loadWellPreAndFlow();
// 设置当前分析下的VTK网格对象
void setUnstructuredGrid(vtkSmartPointer<vtkUnstructuredGrid> grid);

@ -61,9 +61,14 @@ public:
void setWellCode(const QString& code);
QString getWellCode() const;
/** @brief 保存工程井的油井、气井或水井井别;该值不由数值井编辑器修改。 */
void setWellCategory(NM_WELL_CATEGORY eWellCategory);
/** @brief 返回工程井井别,用于选择流量大字段及求解器相别数组。 */
NM_WELL_CATEGORY getWellCategory() const;
/** @brief 返回井对象的稳定实例标识,与可编辑井编码相互独立。 */
QString getWellInstanceId() const;
/** @brief v3 加载或井型替换时恢复已有实例标识。 */
/** @brief 项目加载或井型替换时恢复已有实例标识。 */
bool restoreWellInstanceId(const QString& sWellInstanceId);
/** @brief 为新建井生成不带花括号的 UUID。 */
static QString createWellInstanceId();
@ -133,6 +138,12 @@ public:
QVector<QPointF> getFlowPoints() const;
// 设置流量曲线点数据
void setFlowPoints(const QVector<QPointF>& points);
/** @brief 返回去除可选 (0,0) 占位点后的真实流动段。 */
QVector<QPointF> getFlowSegmentPoints() const;
/** @brief 返回真实流动段数量,不把首个 (0,0) 占位点计入。 */
int getFlowSegmentCount() const;
/** @brief 校验当前流动段索引是否位于一基的真实流动段范围内。 */
bool hasValidFlowSectionIndex() const;
int getIndexF() const;
void setIndexF(const int newIndex);
@ -237,6 +248,7 @@ protected:
QString m_sWellInstanceId;
QString m_wellName; // 井名
QString m_wellCode; // 井编码(项目级唯一标识,来自ZxDataWell::getCode())
NM_WELL_CATEGORY m_eWellCategory; // 工程井井别:油井、气井或水井
// Location---位置
nmDataAttribute m_inputWellHead; //井口坐标输入
@ -265,7 +277,7 @@ protected:
nmDataAttribute m_bottomholeMD; // 井底测量深度
QVector<QPointF> m_vecPtsP; // 压力曲线点数据
QVector<QPointF> m_vecPtsF; // 流量曲线点数据
QVector<QPointF> m_vecPtsF; // 流量曲线点数据,允许首项为 (0,0) 占位点
int m_nIndexF; // 当前流量段的索引

@ -56,6 +56,22 @@ enum NM_PHASE_TYPE {
PHASE_UNKNOWN, // 其它
PHASE_Oil_Water // 油水
};
// 工程井井别。数值模块只保存工程井井别的只读副本,流量相别由该值唯一派生。
// Oil/Gas/Water 的数值与 ZxDataWell::getWellTypeIndex() 及流量大字段索引保持一致。
enum NM_WELL_CATEGORY {
NM_WellCategory_Unknown = -1,
NM_WellCategory_Oil = 0,
NM_WellCategory_Gas = 1,
NM_WellCategory_Water = 2
};
inline bool nmIsValidWellCategory(NM_WELL_CATEGORY eWellCategory)
{
return eWellCategory == NM_WellCategory_Oil ||
eWellCategory == NM_WellCategory_Gas ||
eWellCategory == NM_WellCategory_Water;
}
// 结构树节点类型
enum NM_TREE_TYPE {
TREE_OilReservoir, // 油藏节点

@ -35,6 +35,8 @@ struct NM_DATA_EXPORT nmPebiResultSolverSlot
QString m_sWellCode;
/** @brief 求解时冻结的井模型类型。 */
NM_WELL_MODEL m_eWellType;
/** @brief 求解时冻结的油井、气井或水井井别。 */
NM_WELL_CATEGORY m_eWellCategory;
};
/** @brief 一口结果井的历史曲线和计算曲线。 */
@ -70,6 +72,8 @@ struct NM_DATA_EXPORT nmPebiResultWellSnapshot
QString m_sWellName;
/** @brief 求解时冻结的井模型类型。 */
NM_WELL_MODEL m_eWellType;
/** @brief 求解时冻结的油井、气井或水井井别。 */
NM_WELL_CATEGORY m_eWellCategory;
/** @brief 求解时冻结的井角色。 */
NM_CASE_WELL_MODE m_eWellMode;
/** @brief 求解时冻结的井平面位置。 */

@ -21,7 +21,7 @@ public:
/** @brief 销毁尚未被 takeCandidate() 转交的候选。 */
~nmPebiResultSnapshotBuilder();
/** @brief v3 加载时恢复已保存的快照 UUID;求解候选可使用自动生成值。 */
/** @brief v4 加载时恢复已保存的快照 UUID;求解候选可使用自动生成值。 */
bool setSnapshotId(const QString& sSnapshotId);
/** @brief 设置候选创建时对应的网格输入版本和求解输入版本。 */
bool setInputRevisions(quint64 nGridInputRevision,

@ -10,7 +10,7 @@
class nmPebiResultSnapshot;
/**
* @brief 负责 v3 快照三文件载荷及 Snapshot.json 的流式保存和加载。
* @brief 负责 v4 快照三文件载荷及 Snapshot.json 的流式保存和加载。
*
* 序列化器是快照的受限友元,只在保存时读取内部 VTK 对象;加载仍通过
* nmPebiResultSnapshotBuilder 构造和封闭候选。

@ -23,6 +23,7 @@ struct WellDataRow {
bool m_bIsIncluded; ///< 是否被用户勾选为其他主动井。
bool m_bIsPrimary; ///< 主井防御标记;主井正常情况下不会进入本表。
bool m_bCanCalculate; ///< 当前井型和几何参数是否可进入 PEBI 模型。
NM_WELL_CATEGORY m_eWellCategory; ///< 工程井井别,决定哪一列显示流量。
NM_CASE_WELL_MODE m_eMode; ///< 本列表固定保存有产量主动井角色。
QString m_sOilProd; ///< 油产量列的摘要文本。
QString m_sGasProd; ///< 气产量列的摘要文本。
@ -34,6 +35,7 @@ struct WellDataRow {
: m_bIsIncluded(false),
m_bIsPrimary(false),
m_bCanCalculate(false),
m_eWellCategory(NM_WellCategory_Unknown),
m_eMode(NM_CaseWell_RateControlled)
{
}
@ -89,7 +91,7 @@ private:
QHBoxLayout* m_pButtonLayout; ///< 底部操作按钮布局。
QVBoxLayout* m_pMainLayout; ///< 对话框主布局。
QVector<WellDataRow> m_wellData; ///< 与表格行一一对应的候选井数据。
QVector<WellDataRow> m_vecWellData; ///< 与表格行一一对应的候选井数据。
};
#endif // NMWXINCLUDEOTHERWELLS_H

@ -173,6 +173,7 @@ private:
nmWellEditorController* m_pWellEditorController; ///< 轨迹交互控制器。
QLineEdit* m_pWellCodeLineEdit; ///< 只读项目级井号。
QLineEdit* m_pWellNameLineEdit; ///< 井名输入框。
QLineEdit* m_pWellCategoryLineEdit; ///< 只读工程井井别。
QComboBox* m_pWellTypeComboBox; ///< 井型选择器。
QCheckBox* m_pTimeDependentCheckBox; ///< 时间变表皮开关。
QToolButton* m_pTimeDependentButton; ///< 时间变表皮详情入口。

@ -195,6 +195,8 @@ bool captureResultWellMetadata(
{
if(pWellData == NULL || !oWellInput.m_bRealWell ||
pWellData->getWellCode() != oWellInput.m_sWellCode ||
pWellData->getWellCategory() != oWellInput.m_eWellCategory ||
!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
pWellData->getWellInstanceId().isEmpty() ||
QUuid(pWellData->getWellInstanceId()).isNull()) {
return false;
@ -213,7 +215,7 @@ bool captureResultWellMetadata(
// 无产量井仍作为观察井保留在求解槽位和快照元数据中,但不进入结果下拉框。
oWellInput.m_bDisplayResultWell = bDisplayResultWell &&
oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() >= 2;
!oWellInput.m_vecFlowPoints.isEmpty();
nmDataVerticalFracturedWell* pVerticalFracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWellData);
@ -1512,7 +1514,10 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
const bool bRateControlled =
oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() >= 2;
!oWellInput.m_vecFlowPoints.isEmpty() &&
oWellInput.m_nFlowSectionIndex >= 1 &&
oWellInput.m_nFlowSectionIndex <=
oWellInput.m_vecFlowPoints.size();
vvecLogLog.clear();
vvecSemiLog.clear();
@ -1549,14 +1554,14 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
// 准备流量段数据
const QVector<QPointF>& vecTimeQ =
oWellInput.m_vecFlowPoints;
// 防御空流量数据,避免 -1 转成 std::vector 的巨大无符号长度。
int nTimeNumQ = qMax(0, vecTimeQ.size() - 1); // 移除第一个 0 点
// 输入快照已经移除可选 (0,0) 占位点,此处逐段传给曲线 DLL。
const int nTimeNumQ = vecTimeQ.size();
std::vector<double> timeQ(nTimeNumQ);
std::vector<double> q(nTimeNumQ);
for(int i = 0; i < nTimeNumQ; ++i) {
timeQ[i] = vecTimeQ[i + 1].x();
q[i] = vecTimeQ[i + 1].y();
timeQ[i] = vecTimeQ[i].x();
q[i] = vecTimeQ[i].y();
}
// 调用外部 DLL 计算双对数曲线
@ -1913,6 +1918,7 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate()
oSlot.m_eEntryKind = oWellRef.m_eEntryKind;
oSlot.m_sWellCode = oWellRef.m_sWellCode;
oSlot.m_eWellType = oWellRef.m_eWellType;
oSlot.m_eWellCategory = oWellInput.m_eWellCategory;
if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) {
oSlot.m_sWellInstanceId = oWellInput.m_sWellInstanceId;
}
@ -1941,6 +1947,7 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate()
oWell.m_sWellCode = oWellInput.m_sWellCode;
oWell.m_sWellName = oWellInput.m_sWellName;
oWell.m_eWellType = oWellRef.m_eWellType;
oWell.m_eWellCategory = oWellInput.m_eWellCategory;
oWell.m_eWellMode = oWellInput.m_bRateControlled
? NM_CaseWell_RateControlled
: NM_CaseWell_Observation;

@ -1103,7 +1103,8 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
oWellInput.m_bRealWell = true;
oWellInput.m_sWellCode = pWellData->getWellCode();
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_vecFlowPoints = pWellData->getFlowPoints();
oWellInput.m_eWellCategory = pWellData->getWellCategory();
oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
@ -1115,8 +1116,10 @@ bool nmCalculationPebiGrid::appendManualWellInputSnapshot(
: 0.0;
oWellInput.m_bRateControlled =
nWellMode == static_cast<int>(NM_CaseWell_RateControlled);
if(oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() < 2) {
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
(oWellInput.m_bRateControlled &&
(oWellInput.m_vecFlowPoints.isEmpty() ||
!pWellData->hasValidFlowSectionIndex()))) {
return false;
}
@ -1378,7 +1381,8 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oWellInput.m_bRealWell = true;
oWellInput.m_sWellName = pWellData->getWellName();
oWellInput.m_vecFlowPoints = pWellData->getFlowPoints();
oWellInput.m_eWellCategory = pWellData->getWellCategory();
oWellInput.m_vecFlowPoints = pWellData->getFlowSegmentPoints();
oWellInput.m_nFlowSectionIndex = pWellData->getIndexF();
oWellInput.m_oLocation = QPointF(
pWellData->getX().getValue().toDouble(),
@ -1392,8 +1396,14 @@ bool nmCalculationPebiGrid::captureInputSnapshot(
oWellInput.m_bRateControlled =
mapWellModes.value(oWellRef.m_sWellCode) ==
static_cast<int>(NM_CaseWell_RateControlled);
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory)) {
qWarning() << "Solver well has an invalid category:"
<< oWellRef.m_sWellCode;
return false;
}
if(oWellInput.m_bRateControlled &&
oWellInput.m_vecFlowPoints.size() < 2) {
(oWellInput.m_vecFlowPoints.isEmpty() ||
!pWellData->hasValidFlowSectionIndex())) {
qWarning() << "Rate-controlled well has no valid rate schedule:"
<< oWellRef.m_sWellCode;
return false;
@ -1519,7 +1529,7 @@ bool nmCalculationPebiGrid::buildPebiScene(
oScene.wellName.push_back(vecWellInputs[nIndex].m_sWellName);
}
// 第二步:按求解器模型填充每个槽位的产量制度。
// 第二步:按每口井冻结的井别填充产量制度;求解模型只负责相容性校验。
const NM_SOLVER_MODEL_TYPE eSolverModelType =
static_cast<NM_SOLVER_MODEL_TYPE>(oScene.solverType);
oScene.Rate.t.resize(vecSolverWellOrder.size());
@ -1545,26 +1555,31 @@ bool nmCalculationPebiGrid::buildPebiScene(
continue;
}
QVector<QPointF> vecTimeQ = oWellInput.m_vecFlowPoints;
if(!vecTimeQ.isEmpty() &&
vecTimeQ[0].x() == 0.0 &&
vecTimeQ[0].y() == 0.0) {
vecTimeQ.remove(0);
if(!nmIsValidWellCategory(oWellInput.m_eWellCategory) ||
!nmSolverModelSupportsWellCategory(
eSolverModelType, oWellInput.m_eWellCategory) ||
oWellInput.m_vecFlowPoints.isEmpty() ||
oWellInput.m_nFlowSectionIndex < 1 ||
oWellInput.m_nFlowSectionIndex >
oWellInput.m_vecFlowPoints.size()) {
qWarning() << "Rate-controlled well input is incompatible with solver model:"
<< oWellInput.m_sWellCode;
return false;
}
std::vector<double> vecTime;
std::vector<double> vecRate;
vecTime.reserve(vecTimeQ.size());
vecRate.reserve(vecTimeQ.size());
vecTime.reserve(oWellInput.m_vecFlowPoints.size());
vecRate.reserve(oWellInput.m_vecFlowPoints.size());
for(int nPointIndex = 0;
nPointIndex < vecTimeQ.size();
nPointIndex < oWellInput.m_vecFlowPoints.size();
++nPointIndex) {
if((nPointIndex % 256) == 0 &&
isCancellationRequested(pCancelRequested)) {
return false;
}
vecTime.push_back(vecTimeQ[nPointIndex].x());
vecRate.push_back(vecTimeQ[nPointIndex].y());
vecTime.push_back(oWellInput.m_vecFlowPoints[nPointIndex].x());
vecRate.push_back(oWellInput.m_vecFlowPoints[nPointIndex].y());
}
oScene.Rate.t[nWellIndex] = vecTime;
@ -1572,32 +1587,15 @@ bool nmCalculationPebiGrid::buildPebiScene(
oScene.Rate.qg[nWellIndex].assign(vecRate.size(), 0.0);
oScene.Rate.qw[nWellIndex].assign(vecRate.size(), 0.0);
if(eSolverModelType == SMT_Gas_VariablePvt ||
eSolverModelType == SMT_Gas_PseudoPressure) {
if(oWellInput.m_eWellCategory == NM_WellCategory_Gas) {
oScene.Rate.qg[nWellIndex] = vecRate;
} else if(eSolverModelType == SMT_Water_ConstPvt ||
eSolverModelType == SMT_Water_VariablePvt) {
} else if(oWellInput.m_eWellCategory == NM_WellCategory_Water) {
oScene.Rate.qw[nWellIndex] = vecRate;
} else {
oScene.Rate.qo[nWellIndex] = vecRate;
}
}
// 油水两相当前按定产油量处理,气、水产量数组保持等长零值。
if(eSolverModelType == SMT_Oil_Water_TwoPhase) {
for(size_t nWellIndex = 0;
nWellIndex < oScene.Rate.t.size();
++nWellIndex) {
if(isCancellationRequested(pCancelRequested)) {
return false;
}
oScene.Rate.qg[nWellIndex].assign(
oScene.Rate.t[nWellIndex].size(), 0.0);
oScene.Rate.qw[nWellIndex].assign(
oScene.Rate.t[nWellIndex].size(), 0.0);
}
}
// 第三步:复制井筒储集、表皮和流量段索引。
oScene.CS.C.resize(vecSolverWellOrder.size());
oScene.CS.S.resize(vecSolverWellOrder.size());
@ -1622,8 +1620,10 @@ bool nmCalculationPebiGrid::buildPebiScene(
oScene.CS.C[nWellIndex] = oWellInput.m_dWellboreStorage;
oScene.CS.S[nWellIndex] = oWellInput.m_dSkin;
// 观察井没有流量段时该字段不参与物理计算,仍写入合法的一基占位值。
oScene.wellFlowSectionIndex[nWellIndex] =
oWellInput.m_nFlowSectionIndex;
oWellInput.m_nFlowSectionIndex >= 1
? oWellInput.m_nFlowSectionIndex : 1;
}
oSnapshot.m_bValid = !isCancellationRequested(pCancelRequested);

@ -39,6 +39,7 @@
#include "nmDataPvtParaForPebi.h"
#include "ZxDataWell.h"
#include "ZxDataGaugeBase.h"
#include "ZxDataGaugeP.h"
#include "ZxDataGaugeF.h"
#include "zxSysUtils.h"
@ -124,7 +125,8 @@ static bool isWellParaOf(const WellParaDesc& desc, NM_WELL_MODEL eWellType)
// 当前 PEBI 网格只支持这三类数值井。Map 中其他类型的图元不能进入求解井集合。
static bool isSupportedNumericalWell(const nmDataWellBase* pWellData)
{
if(pWellData == nullptr || pWellData->getWellCode().isEmpty()) {
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
!nmIsValidWellCategory(pWellData->getWellCategory())) {
return false;
}
@ -134,6 +136,85 @@ static bool isSupportedNumericalWell(const nmDataWellBase* pWellData)
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
// 将工程井井别转换为数值模块的稳定枚举。枚举值同时就是流量大字段索引。
static NM_WELL_CATEGORY frameworkWellCategory(const ZxDataWell* pWellData)
{
if(pWellData == nullptr) {
return NM_WellCategory_Unknown;
}
const NM_WELL_CATEGORY eWellCategory =
static_cast<NM_WELL_CATEGORY>(pWellData->getWellTypeIndex());
return nmIsValidWellCategory(eWellCategory)
? eWellCategory : NM_WellCategory_Unknown;
}
// 从一条测量数据中复制二维点。流量调用方必须显式传入油、气、水井别索引。
static bool readGaugePoints(ZxDataGaugeBase* pGaugeData,
int nObjectIndex,
QVector<QPointF>& vecPoints)
{
vecPoints.clear();
if(pGaugeData == nullptr) {
return false;
}
VecDouble vecX;
VecDouble vecY;
if(!pGaugeData->getDataVecXY(vecX, vecY, nObjectIndex)) {
return false;
}
const int nPointCount = qMin(
static_cast<int>(vecX.size()),
static_cast<int>(vecY.size()));
vecPoints.reserve(nPointCount);
for(int nIndex = 0; nIndex < nPointCount; ++nIndex) {
vecPoints.append(QPointF(vecX[nIndex], vecY[nIndex]));
}
return true;
}
// 读取工程井的第一条压力和流量数据;井别是流量相别的唯一事实来源。
static bool readFrameworkWellCurves(
ZxDataWell* pWellData,
QVector<QPointF>& vecPressurePoints,
QVector<QPointF>& vecFlowPoints,
NM_WELL_CATEGORY& eWellCategory)
{
vecPressurePoints.clear();
vecFlowPoints.clear();
eWellCategory = frameworkWellCategory(pWellData);
if(pWellData == nullptr || !nmIsValidWellCategory(eWellCategory)) {
return false;
}
ZxDataGaugeP* pGaugeP = nullptr;
const ZxDataObjectList listGaugeP =
pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
for(int nIndex = 0; nIndex < listGaugeP.size(); ++nIndex) {
pGaugeP = dynamic_cast<ZxDataGaugeP*>(listGaugeP[nIndex]);
if(pGaugeP != nullptr) {
break;
}
}
ZxDataGaugeF* pGaugeF = nullptr;
const ZxDataObjectList listGaugeF =
pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
for(int nIndex = 0; nIndex < listGaugeF.size(); ++nIndex) {
pGaugeF = dynamic_cast<ZxDataGaugeF*>(listGaugeF[nIndex]);
if(pGaugeF != nullptr) {
break;
}
}
// 压力数据仍读取二维主对象;流量必须使用井别对应的 0/1/2 大字段索引。
readGaugePoints(pGaugeP, 0, vecPressurePoints);
readGaugePoints(pGaugeF, static_cast<int>(eWellCategory), vecFlowPoints);
return true;
}
static bool isWellParaAttrName(const QString& name)
{
for (int i = 0; i < WELL_PARA_DESC_COUNT; ++i) {
@ -1363,61 +1444,14 @@ void nmDataAnalyzeManager::initCurWellData()
// 判断是哪一种井类型,初始化对应的参数
QString wellClass = pWellData->getWellClassEn();
// 获取当前井的压力、流量数据
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
ZxDataGaugeP* pGaugeP = nullptr;
ZxDataGaugeF* pGaugeF = nullptr;
// 遍历压力数据列表
for(int i = 0; i < m_listGaugeP.size(); ++i) {
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
break;
}
}
// 遍历流量数据列表
for(int i = 0; i < m_listGaugeF.size(); ++i) {
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
break;
}
}
// 获取的压力、流量数据
QVector<QPointF> vecPtsP, vecPtsF;
// 临时存储x,y坐标
VecDouble vecX, vecY;
if(pGaugeP != nullptr) {
// 获取压力数据
if(pGaugeP->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsP.append(pt);
}
}
}
if(pGaugeF != nullptr) {
vecX.clear();
vecY.clear();
// 获取流量数据
if(pGaugeF->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsF.append(pt);
}
}
QVector<QPointF> vecPtsP;
QVector<QPointF> vecPtsF;
NM_WELL_CATEGORY eWellCategory = NM_WellCategory_Unknown;
if(!readFrameworkWellCurves(pWellData, vecPtsP, vecPtsF,
eWellCategory)) {
qWarning() << "Cannot import numerical well with invalid category:"
<< pWellData->getCode();
return;
}
if(ZxBaseUtil::isSameStr(wellClass, "VerticalWell")) {
@ -1430,6 +1464,7 @@ void nmDataAnalyzeManager::initCurWellData()
pVerticalWell->setWellName(pWellData->getName());
pVerticalWell->setWellCode(pWellData->getCode());
pVerticalWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pVerticalWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pVerticalWell->setX(tempAttr);
@ -1468,6 +1503,7 @@ void nmDataAnalyzeManager::initCurWellData()
pVFracturedWell->setWellName(pWellData->getName());
pVFracturedWell->setWellCode(pWellData->getCode());
pVFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pVFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pVFracturedWell->setX(tempAttr);
@ -1509,6 +1545,7 @@ void nmDataAnalyzeManager::initCurWellData()
pHFracturedWell->setWellName(pWellData->getName());
pHFracturedWell->setWellCode(pWellData->getCode());
pHFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pHFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pHFracturedWell->setX(tempAttr);
@ -1549,7 +1586,7 @@ void nmDataAnalyzeManager::initCurWellData()
const QString sPrimaryWellCode = m_pCurDataWell->getWellCode();
m_oNumericalAnalysisCase.setPrimaryWellCode(sPrimaryWellCode);
m_oNumericalAnalysisCase.setPrimaryWellMode(
m_pCurDataWell->getFlowPoints().size() >= 2
m_pCurDataWell->getFlowSegmentCount() > 0
? NM_CaseWell_RateControlled
: NM_CaseWell_Observation);
}
@ -1593,23 +1630,20 @@ void nmDataAnalyzeManager::calculationLogData(
vvecHistoryData[1].append(qpoint.y()); // y
}
// Prepare flow-rate segment data.
QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
if(vecPressure.isEmpty() || vecTimeQ.size() < 2) {
// 流动段索引采用一基;这里只使用去除可选 (0,0) 占位点后的真实段。
const QVector<QPointF> vecTimeQ = pWellData->getFlowSegmentPoints();
if(vecPressure.isEmpty() || vecTimeQ.isEmpty() ||
!pWellData->hasValidFlowSectionIndex()) {
return;
}
int nTimeNumQ = vecTimeQ.size() - 1;
if (nTimeNumQ <= 0) {
// 没有流量段数据,无法计算双对数/半对数曲线
return;
}
const int nTimeNumQ = vecTimeQ.size();
std::vector<double> timeQ(nTimeNumQ);
std::vector<double> q(nTimeNumQ);
for(int i = 0; i < nTimeNumQ; ++i) {
timeQ[i] = vecTimeQ[i + 1].x();
q[i] = vecTimeQ[i + 1].y();
timeQ[i] = vecTimeQ[i].x();
q[i] = vecTimeQ[i].y();
}
// 调用 DLL 计算双对数曲线
@ -1666,71 +1700,18 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
return pExistingWell;
}
// 判断是哪一种井类型,初始化对应的参数
QString sWellClass = pWellData->getWellClassEn();
// 获取当前井的压力、流量数据
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
ZxDataGaugeP* pGaugeP = nullptr;
ZxDataGaugeF* pGaugeF = nullptr;
// 遍历压力数据列表
for(int i = 0; i < m_listGaugeP.size(); ++i) {
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
break;
}
}
// 遍历流量数据列表
for(int i = 0; i < m_listGaugeF.size(); ++i) {
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
break;
}
}
// 获取的压力、流量数据
QVector<QPointF> vecPtsP, vecPtsF;
// 临时存储x,y坐标
VecDouble vecX, vecY;
if(pGaugeP != nullptr) {
// 获取压力数据
if(pGaugeP->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsP.append(pt);
}
}
}
if(pGaugeF != nullptr) {
vecX.clear();
vecY.clear();
// 获取流量数据
if(pGaugeF->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsF.append(pt);
}
}
// 井别决定流量大字段;压力仍读取第一条压力数据的主对象。
const QString sWellClass = pWellData->getWellClassEn();
QVector<QPointF> vecPtsP;
QVector<QPointF> vecPtsF;
NM_WELL_CATEGORY eWellCategory = NM_WellCategory_Unknown;
if(!readFrameworkWellCurves(pWellData, vecPtsP, vecPtsF,
eWellCategory)) {
qWarning() << "Cannot append numerical well with invalid category:"
<< pWellData->getCode();
return nullptr;
}
int nIndexF; //当前井的流动段索引
// 更新当前井的流动段的索引,默认为最后一段
nIndexF = vecPtsF.count() - 1;
nmDataWellBase* pAddedWell = nullptr;
if(ZxBaseUtil::isSameStr(sWellClass, "VerticalWell")) {
// 初始化直井默认参数
@ -1742,7 +1723,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
pAddedWell = pVerticalWell;
pVerticalWell->setWellName(pWellData->getName());
pVerticalWell->setWellCode(pWellData->getCode());
pVerticalWell->setWellCode(pWellData->getCode());
pVerticalWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pVerticalWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pVerticalWell->setX(tempAttr);
@ -1757,8 +1739,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
pVerticalWell->setPressurePoints(vecPtsP);
pVerticalWell->setFlowPoints(vecPtsF);
// 设置流量段索引
pVerticalWell->setIndexF(nIndexF);
// 其他井默认选择最后一个真实流动段,索引保持一基。
pVerticalWell->setIndexF(pVerticalWell->getFlowSegmentCount());
} else if(ZxBaseUtil::isSameStr(sWellClass, "VerticalFracturedWell")) {
// 初始化垂直裂缝井默认参数
@ -1770,7 +1752,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
pAddedWell = pVFracturedWell;
pVFracturedWell->setWellName(pWellData->getName());
pVFracturedWell->setWellCode(pWellData->getCode());
pVFracturedWell->setWellCode(pWellData->getCode());
pVFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pVFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pVFracturedWell->setX(tempAttr);
@ -1787,8 +1770,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
// 更新裂缝位置信息
pVFracturedWell->setFracs();
// 设置流量段索引
pVFracturedWell->setIndexF(nIndexF);
// 其他井默认选择最后一个真实流动段,索引保持一基。
pVFracturedWell->setIndexF(pVFracturedWell->getFlowSegmentCount());
} else if(ZxBaseUtil::isSameStr(sWellClass, "HorizontalMultiFracturedWell")) {
// 初始化多段压裂水平井默认参数
@ -1800,7 +1783,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
pAddedWell = pHFracturedWell;
pHFracturedWell->setWellName(pWellData->getName());
pHFracturedWell->setWellCode(pWellData->getCode());
pHFracturedWell->setWellCode(pWellData->getCode());
pHFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pHFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
pHFracturedWell->setX(tempAttr);
@ -1818,8 +1802,8 @@ nmDataWellBase* nmDataAnalyzeManager::appendWellData(ZxDataWell* pWellData)
// 计算裂缝数据
pHFracturedWell->setFracs();
// 设置流量段索引
pHFracturedWell->setIndexF(nIndexF);
// 其他井默认选择最后一个真实流动段,索引保持一基。
pHFracturedWell->setIndexF(pHFracturedWell->getFlowSegmentCount());
}
// 第二步:不支持的项目井型不进入 Map,也不发送伪造的新增井通知。
@ -3655,7 +3639,8 @@ void nmDataAnalyzeManager::setIncludedCalculationWells(
nmDataWellBase* pWellData = findWellByCode(oWellRef.m_sWellCode);
if(isSupportedNumericalWell(pWellData) &&
oWellRef.m_sWellCode != getPrimaryWellCode() &&
pWellData->getFlowPoints().size() >= 2) {
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->getFlowSegmentCount() > 0) {
vecValidWells.append(nmCalculationWellRef(
oWellRef.m_sWellCode,
NM_CaseWell_RateControlled));
@ -3725,7 +3710,9 @@ nmDataAnalyzeManager::getEffectiveCalculationWells() const
continue;
}
const bool bHasValidRate = pWellData->getFlowPoints().size() >= 2;
const bool bHasValidRate =
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->getFlowSegmentCount() > 0;
if(!bHasValidRate) {
vecEffectiveWells.append(nmCalculationWellRef(
pWellData->getWellCode(),
@ -3998,6 +3985,7 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot(
findWellByCode(pWell->m_sWellCode);
if(pWell == NULL || pLiveWell == NULL ||
pLiveWell->getWellInstanceId() != pWell->m_sWellInstanceId ||
pLiveWell->getWellCategory() != pWell->m_eWellCategory ||
setWellInstanceIds.contains(pWell->m_sWellInstanceId))
{
sError = "PEBI result well identity is stale.";
@ -4470,7 +4458,8 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
// 新数据模型不兼容旧项目。明确校验版本,避免按井名或对象顺序猜测关联关系。
const int nSupportedProjectVersion = 3;
const int nSupportedProjectVersion =
nmNumericalResultPersistence::projectVersion();
if(!doc.HasMember("NumericalProjectVersion") ||
!doc["NumericalProjectVersion"].IsInt() ||
doc["NumericalProjectVersion"].GetInt() != nSupportedProjectVersion) {
@ -4483,7 +4472,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
quint64 nSavedResultInputRevision = 0;
if(!doc.HasMember("InputRevisions") ||
!doc["InputRevisions"].IsObject()) {
qWarning() << "Numerical v3 project has no input revisions:" << filePath;
qWarning() << "Numerical v4 project has no input revisions:" << filePath;
return false;
}
const rapidjson::Value& oRevisionJson = doc["InputRevisions"];
@ -4493,7 +4482,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
!oRevisionJson["BuiltGridRevision"].IsUint64() ||
!oRevisionJson.HasMember("ResultInputRevision") ||
!oRevisionJson["ResultInputRevision"].IsUint64()) {
qWarning() << "Numerical v3 project contains invalid input revisions:"
qWarning() << "Numerical v4 project contains invalid input revisions:"
<< filePath;
return false;
}
@ -4506,7 +4495,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
if(nSavedGridInputRevision == 0 || nSavedResultInputRevision == 0 ||
(nSavedBuiltGridRevision != 0 &&
nSavedBuiltGridRevision != nSavedGridInputRevision)) {
qWarning() << "Numerical v3 project input revision values are invalid:"
qWarning() << "Numerical v4 project input revision values are invalid:"
<< filePath;
return false;
}
@ -4518,7 +4507,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
QVector<nmCalculationWellRef> vecSavedIncludedWells;
QVector<nmSolverWellRef> vecSavedSolverOrder;
// 第一步:v3 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。
// 第一步:v4 必须完整保存当前分析方案,缺字段时不按旧项目规则猜测。
if(!doc.HasMember("NumericalAnalysisCase") ||
!doc["NumericalAnalysisCase"].IsObject() ||
!doc.HasMember("Wells") ||
@ -4574,7 +4563,9 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
!oWellJson.HasMember("WellInstanceId") ||
!oWellJson["WellInstanceId"].IsString() ||
!oWellJson.HasMember("WellType") ||
!oWellJson["WellType"].IsInt()) {
!oWellJson["WellType"].IsInt() ||
!oWellJson.HasMember("WellCategory") ||
!oWellJson["WellCategory"].IsInt()) {
qWarning() << "Numerical project contains an invalid well entry:" << filePath;
return false;
}
@ -4588,11 +4579,15 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
oWellInstanceUuid.toString().remove('{').remove('}');
const NM_WELL_MODEL eWellType = static_cast<NM_WELL_MODEL>(
oWellJson["WellType"].GetInt());
const NM_WELL_CATEGORY eWellCategory =
static_cast<NM_WELL_CATEGORY>(
oWellJson["WellCategory"].GetInt());
const bool bSupportedWellType =
eWellType == NM_WELL_MODEL::Vertical_Well ||
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
if(sWellCode.isEmpty() || !bSupportedWellType ||
!nmIsValidWellCategory(eWellCategory) ||
mapSavedWellTypes.contains(sWellCode) ||
oWellInstanceUuid.isNull() ||
sNormalizedWellInstanceId != sWellInstanceId ||
@ -4713,7 +4708,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
}
}
// 前置校验已完成,从这里开始替换当前输入。旧快照要到全部 v3 载荷
// 前置校验已完成,从这里开始替换当前输入。旧快照要到全部当前版本载荷
// 成功后才替换,加载失败或内存不足时仍保留上一套已发布快照。
// 清空现有数据,确保从头加载新数据
foreach(nmDataFracture* pFractureData, m_vFractureData) {
@ -5134,7 +5129,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
nSavedGridInputRevision,
nSavedBuiltGridRevision,
nSavedResultInputRevision)) {
qWarning() << "Failed to restore numerical v3 input revisions:"
qWarning() << "Failed to restore numerical v4 input revisions:"
<< filePath;
return false;
}
@ -5148,7 +5143,7 @@ bool nmDataAnalyzeManager::WriteProjectData(
const nmNumericalWindowPayloadReferences* pPayloadReferences)
{
if(pPayloadReferences == NULL) {
qWarning() << "Numerical v3 project requires explicit payload references.";
qWarning() << "Numerical v4 project requires explicit payload references.";
return false;
}
// 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。
@ -5163,6 +5158,7 @@ bool nmDataAnalyzeManager::WriteProjectData(
const QString sNormalizedWellInstanceId =
oWellInstanceUuid.toString().remove('{').remove('}');
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
!nmIsValidWellCategory(pWellData->getWellCategory()) ||
setWellCodes.contains(pWellData->getWellCode()) ||
oWellInstanceUuid.isNull() ||
sNormalizedWellInstanceId != sWellInstanceId ||
@ -5238,7 +5234,8 @@ bool nmDataAnalyzeManager::WriteProjectData(
doc.SetObject(); // 根节点是对象
rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); // 获取内存分配器
doc.AddMember("NumericalProjectVersion", 3, allocator);
doc.AddMember("NumericalProjectVersion",
nmNumericalResultPersistence::projectVersion(), allocator);
rapidjson::Value oRevisionJson(rapidjson::kObjectType);
oRevisionJson.AddMember("GridInputRevision",
m_oNumericalAnalysisCase.getGridInputRevision(), allocator);
@ -5879,18 +5876,40 @@ bool nmDataAnalyzeManager::loadNmResultV3InPlace(
pWell->setHistorySemiLog(oHistory.m_vecSemiLog);
}
if(!mapLoadedHistories.isEmpty()) {
qWarning() << "Numerical v3 project contains unreferenced well histories.";
qWarning() << "Numerical v4 project contains unreferenced well histories.";
return false;
}
loadWellPreAndFlow();
if(!loadWellPreAndFlow()) {
qWarning() << "Failed to reload numerical well pressure/rate data.";
return false;
}
// 结果快照保存的是求解时井别;加载时必须与当前工程井保持一致。
if(!pLoadedCandidate.isNull()) {
for(int nIndex = 0;
nIndex < pLoadedCandidate->getWellCount(); ++nIndex) {
const nmPebiResultWellSnapshot* pSavedWell =
pLoadedCandidate->getWellAt(nIndex);
nmDataWellBase* pLiveWell = pSavedWell == nullptr
? nullptr : findWellByCode(pSavedWell->m_sWellCode);
if(pSavedWell == nullptr || pLiveWell == nullptr ||
pLiveWell->getWellInstanceId() !=
pSavedWell->m_sWellInstanceId ||
pLiveWell->getWellCategory() !=
pSavedWell->m_eWellCategory) {
qWarning() << "Loaded PEBI result well category is stale.";
return false;
}
}
}
const QVector<nmCalculationWellRef> vecIncludedWells =
getIncludedCalculationWells();
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
nmDataWellBase* pWell = findWellByCode(
vecIncludedWells[nIndex].m_sWellCode);
if(!isSupportedNumericalWell(pWell) ||
pWell->getFlowPoints().size() < 2) {
!nmIsValidWellCategory(pWell->getWellCategory()) ||
pWell->getFlowSegmentCount() <= 0) {
qWarning() << "Failed to restore included numerical well:"
<< vecIncludedWells[nIndex].m_sWellCode;
return false;
@ -6026,98 +6045,51 @@ bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath)
return true;
}
void nmDataAnalyzeManager::loadWellPreAndFlow()
bool nmDataAnalyzeManager::loadWellPreAndFlow()
{
// 获取当前工区里所有的ZxDataWell井
// 当前项目文件只保存井别和井身份,压力/流量仍从工程井节点重新读取。
if(zxCurProject == nullptr) {
return;
return false;
}
ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
foreach(nmDataWellBase* pWell, m_vWellData) {
if(pWell == nullptr) {
const ZxDataObjectList listWells =
zxCurProject->getChildren(iDataModelType::sTypeWell);
QHash<QString, ZxDataWell*> mapFrameworkWells;
for(int nIndex = 0; nIndex < listWells.size(); ++nIndex) {
ZxDataWell* pFrameworkWell =
dynamic_cast<ZxDataWell*>(listWells[nIndex]);
if(pFrameworkWell == nullptr ||
pFrameworkWell->getCode().isEmpty() ||
mapFrameworkWells.contains(pFrameworkWell->getCode())) {
continue;
}
mapFrameworkWells.insert(pFrameworkWell->getCode(), pFrameworkWell);
}
QString sWellCode = pWell->getWellCode();
ZxDataWell* pWellData = nullptr;
for(int i = 0; i < wellList.size(); i++) {
ZxDataWell* pCandidateWell = dynamic_cast<ZxDataWell*>(wellList[i]);
if(pCandidateWell != nullptr &&
pCandidateWell->getCode() == sWellCode) {
pWellData = pCandidateWell;
break;
}
foreach(nmDataWellBase* pWell, m_vWellData) {
if(pWell == nullptr || pWell->getWellCode().isEmpty()) {
return false;
}
// 拿到了井的数据
if(pWellData) {
// 获取当前井的压力、流量数据
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
ZxDataObjectList m_listGaugeF = pWellData->getChildren(iDataModelType::sTypeDataGaugeF);
ZxDataGaugeP* pGaugeP = nullptr;
ZxDataGaugeF* pGaugeF = nullptr;
// 遍历压力数据列表
for(int i = 0; i < m_listGaugeP.size(); ++i) {
if(pGaugeP = dynamic_cast<ZxDataGaugeP * >(m_listGaugeP[i])) { // 拿到第一条压力数据
break;
}
}
// 遍历流量数据列表
for(int i = 0; i < m_listGaugeF.size(); ++i) {
if(pGaugeF = dynamic_cast<ZxDataGaugeF * >(m_listGaugeF[i])) { // 拿到第一条流量数据
break;
}
}
// 获取的压力、流量数据
QVector<QPointF> vecPtsP, vecPtsF;
// 临时存储x,y坐标
VecDouble vecX, vecY;
if(pGaugeP != nullptr) {
// 获取压力数据
if(pGaugeP->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsP.append(pt);
}
}
}
if(pGaugeF != nullptr) {
vecX.clear();
vecY.clear();
// 获取流量数据
if(pGaugeF->getDataVecXY(vecX, vecY)) {
int pointCount = vecX.size();
if(pointCount > vecY.size()) {
pointCount = vecY.size();
}
for(int i = 0; i < pointCount; ++i) {
QPointF pt(vecX[i], vecY[i]);
vecPtsF.append(pt);
}
}
}
// 设置井的压力数据、流量数据
pWell->setPressurePoints(vecPtsP);
pWell->setFlowPoints(vecPtsF);
ZxDataWell* pFrameworkWell =
mapFrameworkWells.value(pWell->getWellCode(), nullptr);
QVector<QPointF> vecPressurePoints;
QVector<QPointF> vecFlowPoints;
NM_WELL_CATEGORY eFrameworkCategory = NM_WellCategory_Unknown;
if(!readFrameworkWellCurves(pFrameworkWell,
vecPressurePoints,
vecFlowPoints,
eFrameworkCategory) ||
pWell->getWellCategory() != eFrameworkCategory) {
qWarning() << "Numerical well category does not match framework well:"
<< pWell->getWellCode();
return false;
}
pWell->setPressurePoints(vecPressurePoints);
pWell->setFlowPoints(vecFlowPoints);
}
return true;
}
vtkSmartPointer<vtkUnstructuredGrid> nmDataAnalyzeManager::getUnstructuredGrid() const

@ -12,7 +12,8 @@
#include <QUuid>
nmDataWellBase::nmDataWellBase()
: m_nIndexF(0)
: m_eWellCategory(NM_WellCategory_Unknown)
, m_nIndexF(0)
, m_bPlotVisible(true)
, m_bTimeDependentSkin(false)
, m_eWellType(Vertical_Well)
@ -111,6 +112,7 @@ nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
m_sWellInstanceId = other.m_sWellInstanceId;
m_wellName = other.m_wellName;
m_wellCode = other.m_wellCode;
m_eWellCategory = other.m_eWellCategory;
m_x = other.m_x;
m_y = other.m_y;
m_radius = other.m_radius;
@ -190,6 +192,9 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType&
// 序列化井类型
wellObject.AddMember("WellType", rapidjson::Value(static_cast<int>(m_eWellType)), allocator);
// 井别决定流量大字段的油、气、水索引,v4 项目必须显式保存。
wellObject.AddMember("WellCategory",
rapidjson::Value(static_cast<int>(m_eWellCategory)), allocator);
// 序列化 nmDataAttribute 类型的成员
// 调用 nmDataAttribute 自身的 ToJsonValue 方法进行递归序列化
@ -256,7 +261,7 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_wellCode = QString::fromUtf8(jsonValue["WellCode"].GetString());
}
// v3 已在 Manager 中完成格式校验,这里恢复求解时保存的稳定井身份。
// 项目格式已在 Manager 中完成校验,这里恢复求解时保存的稳定井身份。
if(jsonValue.HasMember("WellInstanceId") &&
jsonValue["WellInstanceId"].IsString()) {
restoreWellInstanceId(QString::fromUtf8(
@ -268,6 +273,13 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_eWellType = static_cast<NM_WELL_MODEL>(jsonValue["WellType"].GetInt());
}
// v4 不兼容旧项目,缺失或非法井别由 Manager 的严格结构校验直接拒绝。
if(jsonValue.HasMember("WellCategory") &&
jsonValue["WellCategory"].IsInt()) {
m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
jsonValue["WellCategory"].GetInt());
}
// 反序列化 nmDataAttribute 类型的成员
// 调用 nmDataAttribute 自身的 FromJsonValue 方法进行递归反序列化
if(jsonValue.HasMember("X") && jsonValue["X"].IsObject()) {
@ -495,6 +507,17 @@ QString nmDataWellBase::getWellCode() const
return m_wellCode;
}
void nmDataWellBase::setWellCategory(NM_WELL_CATEGORY eWellCategory)
{
m_eWellCategory = nmIsValidWellCategory(eWellCategory)
? eWellCategory : NM_WellCategory_Unknown;
}
NM_WELL_CATEGORY nmDataWellBase::getWellCategory() const
{
return m_eWellCategory;
}
QString nmDataWellBase::getWellInstanceId() const
{
return m_sWellInstanceId;
@ -668,6 +691,33 @@ void nmDataWellBase::setFlowPoints(const QVector<QPointF>& points)
m_vecPtsF = points;
}
QVector<QPointF> nmDataWellBase::getFlowSegmentPoints() const
{
QVector<QPointF> vecSegments = m_vecPtsF;
// 工程流量数据通常以 (0,0) 作为非业务占位点;真实数据没有占位点时不得误删首段。
if(!vecSegments.isEmpty() &&
vecSegments.first().x() == 0.0 &&
vecSegments.first().y() == 0.0) {
vecSegments.remove(0);
}
return vecSegments;
}
int nmDataWellBase::getFlowSegmentCount() const
{
if(m_vecPtsF.isEmpty()) {
return 0;
}
return m_vecPtsF.first().x() == 0.0 &&
m_vecPtsF.first().y() == 0.0
? m_vecPtsF.size() - 1 : m_vecPtsF.size();
}
bool nmDataWellBase::hasValidFlowSectionIndex() const
{
return m_nIndexF >= 1 && m_nIndexF <= getFlowSegmentCount();
}
int nmDataWellBase::getIndexF() const
{
return m_nIndexF;
@ -1433,29 +1483,18 @@ bool nmDataWellBase::exportHistoryLogLogToCsv(const QString& outDir)
bool nmDataWellBase::exportHistoryRateToCsv(const QString& outDir)
{
// 1) 获取流量点 (t, q)
QVector<QPointF> vecTimeQ = this->getFlowPoints();
// 1) 获取真实流量段 (t, q),不把可选 (0,0) 占位点写入文件。
const QVector<QPointF> vecTimeQ = this->getFlowSegmentPoints();
if(vecTimeQ.size() < 2) {
if(vecTimeQ.isEmpty() || !hasValidFlowSectionIndex() ||
!nmIsValidWellCategory(m_eWellCategory)) {
return false;
}
// 2) 移除第一个点 (0,0)
if(!vecTimeQ.isEmpty()
&& vecTimeQ[0].x() == 0.0
&& vecTimeQ[0].y() == 0.0)
{
vecTimeQ.remove(0);
}
if(vecTimeQ.size() < 2) {
return false;
}
// 3) sectionIndex(流动段索引)
// 2) sectionIndex(流动段一基索引)
int sectionIndex = this->getIndexF();
// 4) 输出文件名
// 3) 输出文件名
QString wellName = this->getWellName();
if(wellName.isEmpty()) wellName = "UnknownWell";
@ -1468,10 +1507,13 @@ bool nmDataWellBase::exportHistoryRateToCsv(const QString& outDir)
QTextStream out(&file);
// 5) 写表头
out << "t,qo,sectionIndex\n";
// 4) 表头与井别保持一致,便于离线排查实际进入求解器的相别。
const char* pRateColumn = m_eWellCategory == NM_WellCategory_Gas
? "qg" : (m_eWellCategory == NM_WellCategory_Water
? "qw" : "qo");
out << "t," << pRateColumn << ",sectionIndex\n";
// 6) 写入每个点
// 5) 写入每个点
for(int i = 0; i < vecTimeQ.size(); ++i)
{
double ti = vecTimeQ[i].x();

@ -204,7 +204,8 @@ nmNumericalWindowPayloadReferences::nmNumericalWindowPayloadReferences()
int nmNumericalResultPersistence::projectVersion()
{
return 3;
// v4 强制保存井别,旧版本不再按默认油相解释流量字段。
return 4;
}
quint64 nmNumericalResultPersistence::maximumBinaryPayloadBytes()

@ -10,12 +10,14 @@
nmPebiResultSolverSlot::nmPebiResultSolverSlot()
: m_nSolverIndex(-1),
m_eEntryKind(NM_SolverEntry_Well),
m_eWellType(Unknow_Well)
m_eWellType(Unknow_Well),
m_eWellCategory(NM_WellCategory_Unknown)
{
}
nmPebiResultWellSnapshot::nmPebiResultWellSnapshot()
: m_eWellType(Unknow_Well),
m_eWellCategory(NM_WellCategory_Unknown),
m_eWellMode(NM_CaseWell_Observation),
m_bHasPerforation(false),
m_bHasSkin(false),

@ -523,6 +523,7 @@ bool nmPebiResultSnapshotBuilder::takeWell(
qSwap(oTarget.m_sWellCode, oWell.m_sWellCode);
qSwap(oTarget.m_sWellName, oWell.m_sWellName);
oTarget.m_eWellType = oWell.m_eWellType;
oTarget.m_eWellCategory = oWell.m_eWellCategory;
oTarget.m_eWellMode = oWell.m_eWellMode;
oTarget.m_oLocation = oWell.m_oLocation;
oTarget.m_bHasPerforation = oWell.m_bHasPerforation;
@ -547,6 +548,7 @@ bool nmPebiResultSnapshotBuilder::takeWell(
// 清除剩余值,保证调用方不再保留候选井的可写语义。
oWell.m_eWellType = Unknow_Well;
oWell.m_eWellCategory = NM_WellCategory_Unknown;
oWell.m_eWellMode = NM_CaseWell_Observation;
oWell.m_oLocation = QPointF();
oWell.m_bHasPerforation = false;
@ -720,6 +722,7 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
if(!isValidInstanceId(oWell.m_sWellInstanceId) ||
oWell.m_sWellCode.isEmpty() || oWell.m_sWellName.isEmpty() ||
oWell.m_eWellType == Unknow_Well ||
!nmIsValidWellCategory(oWell.m_eWellCategory) ||
(oWell.m_eWellMode != NM_CaseWell_RateControlled &&
oWell.m_eWellMode != NM_CaseWell_Observation) ||
!isFiniteValue(oWell.m_oLocation.x()) ||
@ -759,7 +762,8 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
}
if(oSlot.m_eEntryKind == NM_SolverEntry_ManualFracture)
{
if(!oSlot.m_sWellInstanceId.isEmpty())
if(!oSlot.m_sWellInstanceId.isEmpty() ||
oSlot.m_eWellCategory != NM_WellCategory_Unknown)
{
return fail("Manual fracture slot has a well identity.", pError);
}
@ -770,6 +774,8 @@ bool nmPebiResultSnapshotBuilder::validate(QString* pError)
m_pCandidate->findWell(oSlot.m_sWellInstanceId);
if(pWell == NULL || pWell->m_sWellCode != oSlot.m_sWellCode ||
pWell->m_eWellType != oSlot.m_eWellType ||
pWell->m_eWellCategory != oSlot.m_eWellCategory ||
!nmIsValidWellCategory(oSlot.m_eWellCategory) ||
setSlotWellIds.contains(oSlot.m_sWellInstanceId))
{
return fail("PEBI solver slot well mapping is invalid.", pError);

@ -528,7 +528,7 @@ bool parseSnapshotJson(
if(!oDocument.IsObject() ||
!oDocument.HasMember("SnapshotFormatVersion") ||
!oDocument["SnapshotFormatVersion"].IsInt() ||
oDocument["SnapshotFormatVersion"].GetInt() != 1 ||
oDocument["SnapshotFormatVersion"].GetInt() != 2 ||
!oDocument.HasMember("ResultCellCount") ||
!oDocument["ResultCellCount"].IsUint64() ||
!oDocument.HasMember("PressureFrameCount") ||
@ -692,7 +692,8 @@ bool nmPebiResultSnapshotSerializer::save(
oDocument.SetObject();
rapidjson::Document::AllocatorType& oAllocator =
oDocument.GetAllocator();
oDocument.AddMember("SnapshotFormatVersion", 1, oAllocator);
// v2 增加井别元数据;压力帧和井曲线二进制格式保持不变。
oDocument.AddMember("SnapshotFormatVersion", 2, oAllocator);
oDocument.AddMember("SnapshotId",
toJsonString(pSnapshot->m_sSnapshotId, oAllocator), oAllocator);
oDocument.AddMember("GridInputRevision",
@ -726,6 +727,8 @@ bool nmPebiResultSnapshotSerializer::save(
oJson.AddMember("WellCode",
toJsonString(oSlot.m_sWellCode, oAllocator), oAllocator);
oJson.AddMember("WellType", static_cast<int>(oSlot.m_eWellType), oAllocator);
oJson.AddMember("WellCategory", static_cast<int>(
oSlot.m_eWellCategory), oAllocator);
oSlots.PushBack(oJson, oAllocator);
}
oDocument.AddMember("SolverSlots", oSlots, oAllocator);
@ -742,6 +745,8 @@ bool nmPebiResultSnapshotSerializer::save(
oJson.AddMember("WellName", toJsonString(
oWell.m_sWellName, oAllocator), oAllocator);
oJson.AddMember("WellType", static_cast<int>(oWell.m_eWellType), oAllocator);
oJson.AddMember("WellCategory", static_cast<int>(
oWell.m_eWellCategory), oAllocator);
oJson.AddMember("WellMode", static_cast<int>(oWell.m_eWellMode), oAllocator);
oJson.AddMember("X", oWell.m_oLocation.x(), oAllocator);
oJson.AddMember("Y", oWell.m_oLocation.y(), oAllocator);
@ -979,9 +984,11 @@ bool nmPebiResultSnapshotSerializer::load(
nmPebiResultSolverSlot oSlot;
int nEntryKind = 0;
int nWellType = 0;
int nWellCategory = 0;
if(!readRequiredInt(oJson, "SolverIndex", oSlot.m_nSolverIndex) ||
!readRequiredInt(oJson, "EntryKind", nEntryKind) ||
!readRequiredInt(oJson, "WellType", nWellType) ||
!readRequiredInt(oJson, "WellCategory", nWellCategory) ||
!oJson.HasMember("WellInstanceId") ||
!oJson["WellInstanceId"].IsString() ||
!oJson.HasMember("WellCode") || !oJson["WellCode"].IsString())
@ -990,6 +997,8 @@ bool nmPebiResultSnapshotSerializer::load(
}
oSlot.m_eEntryKind = static_cast<NM_SOLVER_ENTRY_KIND>(nEntryKind);
oSlot.m_eWellType = static_cast<NM_WELL_MODEL>(nWellType);
oSlot.m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
nWellCategory);
oSlot.m_sWellInstanceId = QString::fromUtf8(
oJson["WellInstanceId"].GetString());
oSlot.m_sWellCode = QString::fromUtf8(oJson["WellCode"].GetString());
@ -1011,6 +1020,7 @@ bool nmPebiResultSnapshotSerializer::load(
const rapidjson::Value& oJson = oWells[nIndex];
nmPebiResultWellSnapshot oWell;
int nWellType = 0;
int nWellCategory = 0;
int nWellMode = 0;
if(!oJson.IsObject() ||
!oJson.HasMember("WellInstanceId") ||
@ -1018,6 +1028,7 @@ bool nmPebiResultSnapshotSerializer::load(
!oJson.HasMember("WellCode") || !oJson["WellCode"].IsString() ||
!oJson.HasMember("WellName") || !oJson["WellName"].IsString() ||
!readRequiredInt(oJson, "WellType", nWellType) ||
!readRequiredInt(oJson, "WellCategory", nWellCategory) ||
!readRequiredInt(oJson, "WellMode", nWellMode) ||
!readRequiredDouble(oJson, "X", oWell.m_oLocation.rx()) ||
!readRequiredDouble(oJson, "Y", oWell.m_oLocation.ry()) ||
@ -1036,6 +1047,8 @@ bool nmPebiResultSnapshotSerializer::load(
oWell.m_sWellCode = QString::fromUtf8(oJson["WellCode"].GetString());
oWell.m_sWellName = QString::fromUtf8(oJson["WellName"].GetString());
oWell.m_eWellType = static_cast<NM_WELL_MODEL>(nWellType);
oWell.m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
nWellCategory);
oWell.m_eWellMode = static_cast<NM_CASE_WELL_MODE>(nWellMode);
if(setJsonWellIds.contains(oWell.m_sWellInstanceId) ||
!mapCurves.contains(oWell.m_sWellInstanceId))

@ -1,4 +1,4 @@
#include "ZxBaseUtil.h"
#include "ZxBaseUtil.h"
#include "zxLogInstance.h"
#include <QDebug>
@ -254,6 +254,14 @@ void nmGuiPlot::initDefultGeoObj()
// 判断是哪一种井类型,初始化对应的参数
QString wellClass = pWellData->getWellClassEn();
const NM_WELL_CATEGORY eWellCategory =
static_cast<NM_WELL_CATEGORY>(
pWellData->getWellTypeIndex());
if(!nmIsValidWellCategory(eWellCategory)) {
this->setPlotsByDataManger(
nmDataAnalyzeManager::getCurrentInstance());
return;
}
// 获取当前井的压力、流量数据
ZxDataObjectList m_listGaugeP = pWellData->getChildren(iDataModelType::sTypeDataGaugeP);
@ -285,7 +293,7 @@ void nmGuiPlot::initDefultGeoObj()
if(pGaugeP != nullptr) {
// 获取压力数据
if(pGaugeP->getDataVecXY(vecX, vecY)) {
if(pGaugeP->getDataVecXY(vecX, vecY, 0)) {
for(int i = 0; i < vecX.size(); ++i) {
if(i < vecY.size()) {
QPointF pt(vecX[i], vecY[i]);
@ -300,7 +308,9 @@ void nmGuiPlot::initDefultGeoObj()
vecY.clear();
// 获取流量数据
if(pGaugeF->getDataVecXY(vecX, vecY)) {
// 井别是流量大字段索引,单相井也不能固定读取默认的油相字段。
if(pGaugeF->getDataVecXY(
vecX, vecY, static_cast<int>(eWellCategory))) {
for(int i = 0; i < vecX.size(); ++i) {
if(i < vecY.size()) {
QPointF pt(vecX[i], vecY[i]);
@ -327,6 +337,7 @@ void nmGuiPlot::initDefultGeoObj()
m_VerticalWell->setWellName(pWellData->getName());
m_VerticalWell->setWellCode(pWellData->getCode());
m_VerticalWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = m_VerticalWell->getX();
tempAttr.setValue(pWellData->getLocationX());
m_VerticalWell->setX(tempAttr);
@ -364,6 +375,7 @@ void nmGuiPlot::initDefultGeoObj()
m_VFracturedWell->setWellName(pWellData->getName());
m_VFracturedWell->setWellCode(pWellData->getCode());
m_VFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = m_VFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
m_VFracturedWell->setX(tempAttr);
@ -404,6 +416,7 @@ void nmGuiPlot::initDefultGeoObj()
m_HFracturedWell->setWellName(pWellData->getName());
m_HFracturedWell->setWellCode(pWellData->getCode());
m_HFracturedWell->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = m_HFracturedWell->getX();
tempAttr.setValue(pWellData->getLocationX());
m_HFracturedWell->setX(tempAttr);

@ -1747,6 +1747,12 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic,
return false;
}
const NM_WELL_CATEGORY eWellCategory =
static_cast<NM_WELL_CATEGORY>(pWellData->getWellTypeIndex());
if(!nmIsValidWellCategory(eWellCategory)) {
return false;
}
pData = createWellForGraphic(pGraphic, wellType);
if (pData == nullptr) {
return false;
@ -1755,6 +1761,7 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic,
// 重新勾选井时在这里补建nmDataWell,并将工程树中的基础属性同步进去。
pData->setWellName(pWellData->getName());
pData->setWellCode(pWellData->getCode());
pData->setWellCategory(eWellCategory);
nmDataAttribute tempAttr = pData->getX();
tempAttr.setValue(pWellData->getLocationX());
pData->setX(tempAttr);
@ -1785,7 +1792,7 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic,
QVector<QPointF> pressurePoints, flowPoints;
VecDouble vecX, vecY;
if(pGaugeP != nullptr && pGaugeP->getDataVecXY(vecX, vecY)) {
if(pGaugeP != nullptr && pGaugeP->getDataVecXY(vecX, vecY, 0)) {
for(int i = 0; i < vecX.size(); ++i) {
if(i < vecY.size()) {
pressurePoints.append(QPointF(vecX[i], vecY[i]));
@ -1795,7 +1802,10 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic,
vecX.clear();
vecY.clear();
if(pGaugeF != nullptr && pGaugeF->getDataVecXY(vecX, vecY)) {
// 流量大字段的 0/1/2 分别对应油井、气井和水井,禁止使用默认索引猜测。
if(pGaugeF != nullptr &&
pGaugeF->getDataVecXY(vecX, vecY,
static_cast<int>(eWellCategory))) {
for(int i = 0; i < vecX.size(); ++i) {
if(i < vecY.size()) {
flowPoints.append(QPointF(vecX[i], vecY[i]));
@ -1803,17 +1813,21 @@ bool nmPlotGraphicBinder::ensureSelectedWellData(nmObjPointWell* pGraphic,
}
}
int indexF = -2;
int nFlowSectionIndex = -2;
if(pWellData != zxCurWell) {
// 非当前查看井默认使用最后一段流量段。
indexF = flowPoints.count() - 1;
// 非当前查看井在设置流量点后选择最后一个真实段,索引保持一基。
nFlowSectionIndex = flowPoints.size();
if(!flowPoints.isEmpty() && flowPoints.first().x() == 0.0 &&
flowPoints.first().y() == 0.0) {
--nFlowSectionIndex;
}
}
pData->setPressurePoints(pressurePoints);
pData->setFlowPoints(flowPoints);
if(indexF >= -1) {
pData->setIndexF(indexF);
if(nFlowSectionIndex >= -1) {
pData->setIndexF(nFlowSectionIndex);
}
if (wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {

@ -1611,7 +1611,9 @@ void nmSubWndMain::solveAndAnalyze()
}
bHasRateControlledWell = true;
if(pWellData->getFlowPoints().size() < 2) {
if(!nmIsValidWellCategory(pWellData->getWellCategory()) ||
pWellData->getFlowSegmentCount() <= 0 ||
!pWellData->hasValidFlowSectionIndex()) {
QMessageBox::warning(
this,
tr("solver error"),
@ -1619,6 +1621,16 @@ 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(

@ -354,6 +354,8 @@ bool nmWellEditorSession::copySharedWellData(
}
pTarget->setWellName(pSource->getWellName());
pTarget->setWellCode(pSource->getWellCode());
// 井型切换只改变几何模型,工程井井别和流量相别必须保持不变。
pTarget->setWellCategory(pSource->getWellCategory());
pTarget->setX(pSource->getX());
pTarget->setY(pSource->getY());
pTarget->setRadius(pSource->getRadius());

@ -1,4 +1,4 @@
#include "nmWxEditWellPlot.h"
#include "nmWxEditWellPlot.h"
#include "nmWxNumericalDesign.h"
#include "nmWxParameterProperty.h"
@ -787,6 +787,7 @@ void nmWxEditWellPlot::onWellTypeChanged(int index)
// 历史数据
pNewWellData->setPressurePoints(pOldWellData->getPressurePoints());
pNewWellData->setFlowPoints(pOldWellData->getFlowPoints());
pNewWellData->setWellCategory(pOldWellData->getWellCategory());
pNewWellData->setIndexF(pOldWellData->getIndexF());
// 结果数据

@ -93,7 +93,7 @@ void nmWxIncludeOtherWells::setupTable()
QString sPrimaryWellCode = m_pDataManager != nullptr
? m_pDataManager->getPrimaryWellCode() : QString();
int displayRow = 0;
int nDisplayRow = 0;
// 第一步:候选来源是 Map,而不是整个项目井库。该对话框只管理
// 其他有产量的生产/注入井;主井和无产量观察井都不在这里显示。
for(int i = 0; i < vecMapWells.size(); i++) {
@ -103,40 +103,49 @@ void nmWxIncludeOtherWells::setupTable()
}
const bool bIsPrimary = pWell->getWellCode() == sPrimaryWellCode;
const bool bHasValidRate = pWell->getFlowPoints().size() >= 2;
const bool bHasValidRate =
nmIsValidWellCategory(pWell->getWellCategory()) &&
pWell->getFlowSegmentCount() > 0;
const bool bHasValidPressure = pWell->getPressurePoints().size() >= 2;
if(bIsPrimary || !bHasValidRate) {
continue;
}
WellDataRow wellRow;
wellRow.m_sWellCode = pWell->getWellCode();
wellRow.m_sName = pWell->getWellName();
wellRow.m_bIsPrimary = bIsPrimary;
WellDataRow oWellRow;
oWellRow.m_sWellCode = pWell->getWellCode();
oWellRow.m_sName = pWell->getWellName();
oWellRow.m_bIsPrimary = bIsPrimary;
oWellRow.m_eWellCategory = pWell->getWellCategory();
// 第二步:读取这口有产量干扰井上次确认的包含状态。
wellRow.m_bCanCalculate = true;
wellRow.m_bIsIncluded =
oWellRow.m_bCanCalculate = true;
oWellRow.m_bIsIncluded =
m_pDataManager != nullptr &&
m_pDataManager->getNumericalAnalysisCase()->isIncludedWell(
wellRow.m_sWellCode);
// 第三步:IncludedWells 中的候选固定是有产量主动井。
wellRow.m_sPressure = bHasValidPressure ? "History" : "None";
wellRow.m_sOilProd = "Rate";
wellRow.m_eMode = NM_CaseWell_RateControlled;
// 气相和水相暂时设为None
wellRow.m_sGasProd = "None";
wellRow.m_sWaterProd = "None";
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) {
oWellRow.m_sOilProd = tr("Rate");
} else if(oWellRow.m_eWellCategory == NM_WellCategory_Gas) {
oWellRow.m_sGasProd = tr("Rate");
} else if(oWellRow.m_eWellCategory == NM_WellCategory_Water) {
oWellRow.m_sWaterProd = tr("Rate");
}
oWellRow.m_eMode = NM_CaseWell_RateControlled;
// 添加到数据列表和表格
m_wellData.append(wellRow);
addWellDataToTable(wellRow, displayRow);
displayRow++;
m_vecWellData.append(oWellRow);
addWellDataToTable(oWellRow, nDisplayRow);
++nDisplayRow;
}
m_pTableWidget->setRowCount(displayRow); // 调整实际显示的行数
m_pTableWidget->setRowCount(nDisplayRow); // 调整实际显示的行数
m_pTableWidget->horizontalHeader()->setStretchLastSection(true);
m_pTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
}
@ -147,6 +156,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
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);
// Included列 - 使用自定义复选框组件
@ -180,6 +190,7 @@ void nmWxIncludeOtherWells::addWellDataToTable(const WellDataRow& data, int row)
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);
}
}
@ -192,18 +203,18 @@ void nmWxIncludeOtherWells::onCheckBoxStateChanged(int state)
}
const int nRow = pCheckBox->property("row").toInt();
if(nRow < 0 || nRow >= m_wellData.size()) {
if(nRow < 0 || nRow >= m_vecWellData.size()) {
return;
}
// 主井正常不会进入本表;保留该判断作为防御,避免未来改动误取消主井。
if(m_wellData[nRow].m_bIsPrimary) {
m_wellData[nRow].m_bIsIncluded = true;
if(m_vecWellData[nRow].m_bIsPrimary) {
m_vecWellData[nRow].m_bIsIncluded = true;
return;
}
// 其他候选井只记录选择状态,绝不在该对话框内创建或删除 Map 井。
m_wellData[nRow].m_bIsIncluded = (state == Qt::Checked);
m_vecWellData[nRow].m_bIsIncluded = (state == Qt::Checked);
m_pTableWidget->selectRow(nRow);
}
@ -213,7 +224,7 @@ void nmWxIncludeOtherWells::onItemClicked(QTableWidgetItem* item)
QVector<WellDataRow> nmWxIncludeOtherWells::getWellData() const
{
return m_wellData;
return m_vecWellData;
}
void nmWxIncludeOtherWells::setupButtons()

@ -1098,7 +1098,8 @@ void nmWxNumericalDesign::onOptionsIconClicked()
continue;
}
if(pWellData->getFlowPoints().size() < 2) {
if(!nmIsValidWellCategory(pWellData->getWellCategory()) ||
pWellData->getFlowSegmentCount() <= 0) {
// 对话框打开后若产量数据发生变化,提交时仍要阻止它混入包含列表。
continue;
}
@ -1214,7 +1215,9 @@ void nmWxNumericalDesign::updateIncludeOtherWellsAvailability()
continue;
}
const bool bHasRateTarget = pWellData->getFlowPoints().size() >= 2;
const bool bHasRateTarget =
nmIsValidWellCategory(pWellData->getWellCategory()) &&
pWellData->getFlowSegmentCount() > 0;
if(bHasRateTarget) {
bHasEligibleOtherWell = true;
break;

@ -51,6 +51,7 @@ nmWxWellEditor::nmWxWellEditor(const nmWellEditContext& oContext,
m_pWellEditorController(nullptr),
m_pWellCodeLineEdit(nullptr),
m_pWellNameLineEdit(nullptr),
m_pWellCategoryLineEdit(nullptr),
m_pWellTypeComboBox(nullptr),
m_pTimeDependentCheckBox(nullptr),
m_pTimeDependentButton(nullptr),
@ -105,6 +106,22 @@ bool nmWxWellEditor::initialize(QString& sError)
// 第一步:同步井信息和时间变表皮控件。
m_pWellCodeLineEdit->setText(pWell->getWellCode());
m_pWellNameLineEdit->setText(pWell->getWellName());
QString sWellCategory;
switch(pWell->getWellCategory()) {
case NM_WellCategory_Oil:
sWellCategory = tr("Oil well");
break;
case NM_WellCategory_Gas:
sWellCategory = tr("Gas well");
break;
case NM_WellCategory_Water:
sWellCategory = tr("Water well");
break;
default:
sWellCategory = tr("Unknown");
break;
}
m_pWellCategoryLineEdit->setText(sWellCategory);
int nWellTypeIndex = m_pWellTypeComboBox->findData(
static_cast<int>(pWell->getWellType()));
m_bChangingWellType = true;
@ -645,25 +662,31 @@ QWidget* nmWxWellEditor::createWellInformationWidget(QWidget* pParent)
QLabel* pCodeLabel = new QLabel(tr("Well number"), pInformationGroup);
QLabel* pNameLabel = new QLabel(tr("Well name"), pInformationGroup);
QLabel* pTypeLabel = new QLabel(tr("Well type"), pInformationGroup);
QLabel* pCategoryLabel = new QLabel(
tr("Well category"), pInformationGroup);
QLabel* pTypeLabel = new QLabel(tr("Well model"), pInformationGroup);
QLabel* pTimeDependentLabel = new QLabel(
tr("Time-dependent skin"), pInformationGroup);
pCodeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pNameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pCategoryLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pTypeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
pTimeDependentLabel->setAlignment(
Qt::AlignRight | Qt::AlignVCenter);
m_pWellCodeLineEdit = new QLineEdit(pInformationGroup);
m_pWellNameLineEdit = new QLineEdit(pInformationGroup);
m_pWellCategoryLineEdit = new QLineEdit(pInformationGroup);
m_pWellTypeComboBox = new QComboBox(pInformationGroup);
m_pWellCodeLineEdit->setReadOnly(true);
m_pWellCategoryLineEdit->setReadOnly(true);
m_pWellCodeLineEdit->setToolTip(
tr("Project-level unique well identifier"));
QList<QWidget*> listEditors;
listEditors << m_pWellCodeLineEdit
<< m_pWellNameLineEdit
<< m_pWellCategoryLineEdit
<< m_pWellTypeComboBox;
for (int nIndex = 0; nIndex < listEditors.size(); ++nIndex)
{
@ -701,10 +724,12 @@ QWidget* nmWxWellEditor::createWellInformationWidget(QWidget* pParent)
pInformationLayout->addWidget(m_pWellCodeLineEdit, 0, 1);
pInformationLayout->addWidget(pNameLabel, 1, 0);
pInformationLayout->addWidget(m_pWellNameLineEdit, 1, 1);
pInformationLayout->addWidget(pTypeLabel, 2, 0);
pInformationLayout->addWidget(m_pWellTypeComboBox, 2, 1);
pInformationLayout->addWidget(pTimeDependentLabel, 3, 0);
pInformationLayout->addWidget(pTimeDependentEditor, 3, 1);
pInformationLayout->addWidget(pCategoryLabel, 2, 0);
pInformationLayout->addWidget(m_pWellCategoryLineEdit, 2, 1);
pInformationLayout->addWidget(pTypeLabel, 3, 0);
pInformationLayout->addWidget(m_pWellTypeComboBox, 3, 1);
pInformationLayout->addWidget(pTimeDependentLabel, 4, 0);
pInformationLayout->addWidget(pTimeDependentEditor, 4, 1);
connect(m_pTimeDependentCheckBox, SIGNAL(toggled(bool)),
this, SLOT(slotTimeDependentToggled(bool)));

Loading…
Cancel
Save