You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmData/nmDataWellBase.cpp

2267 lines
68 KiB
C++

#include "nmDataWellBase.h"
#include "nmDataReservoir.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSnapshot.h"
#include <QDir>
#include <QByteArray>
#include <QFile>
#include <QTextStream>
#include <QCoreApplication>
#include <QDateTime>
#include <QCryptographicHash>
#include <QUuid>
#include <string.h>
/**
* @brief Gauge Code Code nullptr
* @note 使 Code
*/
template <typename TRecord>
static TRecord* findGaugeRecordByCode(QVector<TRecord>& vecRecords, const QString& sGaugeCode)
{
if(sGaugeCode.isEmpty()) {
return nullptr;
}
for(int i = 0; i < vecRecords.size(); ++i) {
if(vecRecords[i].sGaugeCode == sGaugeCode) {
return &vecRecords[i];
}
}
return nullptr;
}
template <typename TRecord>
static const TRecord* findGaugeRecordByCode(const QVector<TRecord>& vecRecords, const QString& sGaugeCode)
{
if(sGaugeCode.isEmpty()) {
return nullptr;
}
for(int i = 0; i < vecRecords.size(); ++i) {
if(vecRecords[i].sGaugeCode == sGaugeCode) {
return &vecRecords[i];
}
}
return nullptr;
}
// setPressurePoints/setFlowPoints 等旧接口在没有选中记录时用来创建内部占位记录的 Code。
// 使用非空占位符,避免与“空 Code = 未选中”的约定冲突;阶段二引入选择 UI 前不会对外暴露该记录。
static const QString kManualPressureGaugeCode = QString::fromLatin1("__ManualPressureRecord__");
static const QString kManualFlowGaugeCode = QString::fromLatin1("__ManualFlowRecord__");
static void appendGaugeUInt32(QByteArray& baData, quint32 nValue)
{
for(int nByte = 0; nByte < 4; ++nByte) {
baData.append(static_cast<char>((nValue >> (nByte * 8)) & 0xffu));
}
}
static void appendGaugeUInt64(QByteArray& baData, quint64 nValue)
{
for(int nByte = 0; nByte < 8; ++nByte) {
baData.append(static_cast<char>((nValue >> (nByte * 8)) & 0xffu));
}
}
static bool appendGaugeString(QByteArray& baData, const QString& sValue)
{
const QByteArray baValue = sValue.toUtf8();
if(baValue.size() < 0) {
return false;
}
appendGaugeUInt32(baData, static_cast<quint32>(baValue.size()));
baData.append(baValue);
return true;
}
static void appendGaugeDouble(QByteArray& baData, double dValue)
{
quint64 nBits = 0;
memcpy(&nBits, &dValue, sizeof(double));
appendGaugeUInt64(baData, nBits);
}
static void appendGaugeDoubleVector(
QByteArray& baData,
const QVector<double>& vecValues)
{
appendGaugeUInt32(baData, static_cast<quint32>(vecValues.size()));
for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) {
appendGaugeDouble(baData, vecValues[nIndex]);
}
}
static void appendGaugePointVector(
QByteArray& baData,
const QVector<QPointF>& vecPoints)
{
appendGaugeUInt32(baData, static_cast<quint32>(vecPoints.size()));
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
appendGaugeDouble(baData, vecPoints[nIndex].x());
appendGaugeDouble(baData, vecPoints[nIndex].y());
}
}
static void buildNormalizedPhaseRates(
const QVector<QPointF>& vecPoints,
bool bUsed,
int nScheduleCount,
QVector<double>& vecRates)
{
vecRates.clear();
if(!bUsed || vecPoints.isEmpty()) {
vecRates.fill(0.0, nScheduleCount);
return;
}
vecRates.reserve(vecPoints.size());
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
vecRates.append(vecPoints[nIndex].y());
}
}
nmDataWellBase::nmDataWellBase()
: m_eWellCategory(NM_WellCategory_Unknown)
, m_bUseOilRate(true)
, m_bUseGasRate(true)
, m_bUseWaterRate(true)
, m_nIndexF(0)
, m_bPlotVisible(true)
, m_bTimeDependentSkin(false)
, m_eWellType(Vertical_Well)
, m_dLastWellLength(0.0)
, m_pReservoir(nullptr)
{
m_sWellInstanceId = createWellInstanceId();
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
m_pReservoir = pDataManager == nullptr
? nullptr : pDataManager->getReservoirData();
m_wellName = "";
m_x = nmDataAttribute("X", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_y = nmDataAttribute("Y", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_radius = nmDataAttribute("Radius", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_drillFloorElevation = nmDataAttribute("Drill floor elevation", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_zw = nmDataAttribute("Zw", 22.5, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
// 候选加载阶段尚未发布储层对象,井长随后会由 JSON 完整恢复。
const QVariant oInitialWellLength = m_pReservoir == nullptr
? QVariant(0.0) : m_pReservoir->getThickness().getValue();
m_wellLength = nmDataAttribute("Well length", oInitialWellLength, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_rateDependentSkin = nmDataAttribute("Rate dependent skin", false, "");
m_dSdQ = nmDataAttribute("dS/dQ", 0.0, "1/B/D", UNIT_TYPE_FLOW_RATE_RECIPROCAL, QStringList(), QStringList() << "1/B/D" << "1/MMm^3/D" << "1/Mcf/D" << "1/Mm^3/D" << "1/Mm^3/hr"
<< "1/U.K. gal/hr" << "1/U.K. gal/min" << "1/U.S. gal/hr" << "1/U.S. gal/min" << "1/cf/D" << "1/cf/s" << "1/cm^3/sec" << "1/l/min" << "1/m^3/D" << "1/m^3/hr" << "1/m^3/min" << "1/m^3/sec");
m_wellboreModel = nmDataAttribute("Wellbore model", "Constant", "", UNIT_TYPE_DIMENSIONLESS, QStringList() << "None" << "Constant" << "Changing hegeman" << "Changing fair"
<< "Changing spivey packer" << "Changing spivey fissures", QStringList());
m_wellboreStorage = nmDataAttribute("Wellbore storage", 0.01, "m^3/MPa", UNIT_TYPE_COMPRESSIBILITY, QStringList(), QStringList() << "bbl/psi" << "m^3/bar" << "m^3/kPa" << "m^3/Pa" << "m^3.cm^2/kg" << "m^2" << "m^3/MPa");
m_bottomholeMD = nmDataAttribute("Bottomhole MD", 6000.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
// 初始化新添加的成员
m_inputWellHead = nmDataAttribute("Input well head", "", "");
m_wellHeadX = nmDataAttribute("Well head X", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_wellHeadY = nmDataAttribute("Well head Y", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_finalWellboreStorage = nmDataAttribute("Final wellbore storage", 0.01, "bbl/psi", UNIT_TYPE_COMPRESSIBILITY, QStringList(), QStringList() << "bbl/psi" << "m^3/bar" << "m^3/kPa" << "m^3/Pa" << "m^3.cm^2/kg" << "m^2" << "m^3/MPa");
m_cInitialCFinal = nmDataAttribute("C[initial]/C[final]", 10.0, "");
m_dtChangingStorage = nmDataAttribute("Dt changing storage", 1.0, "hr", UNIT_TYPE_TIME, QStringList(), QStringList() << "ms" << "sec" << "min" << "hr" << "day" << "Week" << "Month" << "Year");
m_leakSkin = nmDataAttribute("Leak Skin", 0.0, "");
// 图元可见性默认为true
m_bPlotVisible = true;
// 图元外观属性默认值(与 nmObjPointWell::init 中的 m_oDot 默认值一致)
m_nDotStyle = 3; // DTS_Circle
m_nDotColorR = 0; // QColor(0, 255, 0)
m_nDotColorG = 255;
m_nDotColorB = 0;
m_dDotRadius = 1.5;
m_bDotFilling = true;
m_bShowSubObjs = true;
m_bTimeDependentSkin = false;
// 添加一段射孔,默认与井身长度相同 (现在创建对象并添加指针)
nmDataPerforation* defaultPerforation = new nmDataPerforation();
defaultPerforation->getMdStart().setValue(m_bottomholeMD.getValue().toDouble());
defaultPerforation->getMdEnd().setValue(m_bottomholeMD.getValue().toDouble() + m_wellLength.getValue().toDouble());
m_vecPerforations.append(defaultPerforation); // 添加指针
// 默认与当前井身长度一致
m_dLastWellLength = m_wellLength.getValue().toDouble();
this->connectAttributeSignals();
}
void nmDataWellBase::resetToDefaults()
{
m_wellboreStorage.setValue(0.01);
// 重置第一段射孔的皮损系数
if(getPerforationCount() > 0) {
nmDataPerforation* firstPerforation = getPerforation(0);
if(firstPerforation) {
firstPerforation->getSkin().setValue(0.0); // 皮损系数
}
}
}
nmDataWellBase::nmDataWellBase(const nmDataWellBase& other)
{
*this = other; // 使用赋值运算符实现
}
nmDataWellBase::~nmDataWellBase()
{
// 释放所有射孔段对象
qDeleteAll(m_vecPerforations);
m_vecPerforations.clear();
}
nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other)
{
if(this != &other) {
// 工作副本不能继承 Manager 维护的结果绑定;提交替换后统一重绑。
m_pPebiResultSnapshot.clear();
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;
m_drillFloorElevation = other.m_drillFloorElevation;
m_zw = other.m_zw;
m_wellLength = other.m_wellLength;
m_rateDependentSkin = other.m_rateDependentSkin;
m_dSdQ = other.m_dSdQ;
m_wellboreModel = other.m_wellboreModel;
m_wellboreStorage = other.m_wellboreStorage;
m_bottomholeMD = other.m_bottomholeMD;
m_vecPressureRecords = other.m_vecPressureRecords;
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;
m_wellHeadX = other.m_wellHeadX;
m_wellHeadY = other.m_wellHeadY;
m_cInitialCFinal = other.m_cInitialCFinal;
m_dtChangingStorage = other.m_dtChangingStorage;
m_leakSkin = other.m_leakSkin;
m_bPlotVisible = other.m_bPlotVisible;
// 复制图元外观属性
m_nDotStyle = other.m_nDotStyle;
m_nDotColorR = other.m_nDotColorR;
m_nDotColorG = other.m_nDotColorG;
m_nDotColorB = other.m_nDotColorB;
m_dDotRadius = other.m_dDotRadius;
m_bDotFilling = other.m_bDotFilling;
m_bShowSubObjs = other.m_bShowSubObjs;
m_bTimeDependentSkin = other.m_bTimeDependentSkin;
m_eWellType = other.m_eWellType;
// 先释放当前的射孔段对象
qDeleteAll(m_vecPerforations);
m_vecPerforations.clear();
// 深拷贝新的射孔段对象
foreach(nmDataPerforation* perf, other.m_vecPerforations) {
if(perf) {
m_vecPerforations.append(new nmDataPerforation(*perf));
}
}
// 复制历史数据
m_vvecHsyPressure = other.m_vvecHsyPressure;
m_vvecHsyLogLog = other.m_vvecHsyLogLog;
m_vvecHsySemiLog = other.m_vvecHsySemiLog;
m_sHistoryGaugeInputSha1 = other.m_sHistoryGaugeInputSha1;
// 默认与当前井身长度一致
m_dLastWellLength = m_wellLength.getValue().toDouble();
}
return *this;
}
// 序列化 nmDataWellBase 为 RapidJSON Value
rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType& allocator) const
{
// 创建一个 RapidJSON 对象类型的值
rapidjson::Value wellObject(rapidjson::kObjectType);
// 序列化名称
wellObject.AddMember("WellName", rapidjson::Value(m_wellName.toStdString().c_str(), allocator).Move(), allocator);
// 序列化井编码
wellObject.AddMember("WellCode", rapidjson::Value(m_wellCode.toStdString().c_str(), allocator).Move(), allocator);
// UUID 是实时井、井历史和结果快照之间唯一允许的关联键。
const QByteArray baWellInstanceId = m_sWellInstanceId.toUtf8();
wellObject.AddMember("WellInstanceId",
rapidjson::Value(baWellInstanceId.constData(),
static_cast<rapidjson::SizeType>(baWellInstanceId.size()),
allocator).Move(), allocator);
// 序列化井类型
wellObject.AddMember("WellType", rapidjson::Value(static_cast<int>(m_eWellType)), allocator);
// 井别决定流量大字段的油、气、水索引v5 项目必须显式保存。
wellObject.AddMember("WellCategory",
rapidjson::Value(static_cast<int>(m_eWellCategory)), allocator);
// 序列化 nmDataAttribute 类型的成员
// 调用 nmDataAttribute 自身的 ToJsonValue 方法进行递归序列化
wellObject.AddMember("X", m_x.ToJsonValue(allocator), allocator);
wellObject.AddMember("Y", m_y.ToJsonValue(allocator), allocator);
wellObject.AddMember("Radius", m_radius.ToJsonValue(allocator), allocator);
wellObject.AddMember("DrillFloorElevation", m_drillFloorElevation.ToJsonValue(allocator), allocator);
wellObject.AddMember("Zw", m_zw.ToJsonValue(allocator), allocator);
wellObject.AddMember("WellLength", m_wellLength.ToJsonValue(allocator), allocator);
wellObject.AddMember("RateDependentSkin", m_rateDependentSkin.ToJsonValue(allocator), allocator);
wellObject.AddMember("dSdQ", m_dSdQ.ToJsonValue(allocator), allocator);
wellObject.AddMember("WellboreModel", m_wellboreModel.ToJsonValue(allocator), allocator);
wellObject.AddMember("WellboreStorage", m_wellboreStorage.ToJsonValue(allocator), allocator);
wellObject.AddMember("BottomholeMD", m_bottomholeMD.ToJsonValue(allocator), allocator);
wellObject.AddMember("InputWellHead", m_inputWellHead.ToJsonValue(allocator), allocator);
wellObject.AddMember("WellHeadX", m_wellHeadX.ToJsonValue(allocator), allocator);
wellObject.AddMember("WellHeadY", m_wellHeadY.ToJsonValue(allocator), allocator);
wellObject.AddMember("FinalWellboreStorage", m_finalWellboreStorage.ToJsonValue(allocator), allocator);
wellObject.AddMember("CInitialCFinal", m_cInitialCFinal.ToJsonValue(allocator), allocator);
wellObject.AddMember("DtChangingStorage", m_dtChangingStorage.ToJsonValue(allocator), allocator);
wellObject.AddMember("LeakSkin", m_leakSkin.ToJsonValue(allocator), allocator);
// 序列化时间变表皮状态
wellObject.AddMember("TimeDependentSkin", m_bTimeDependentSkin, allocator);
// 序列化图元可见性 (m_bPlotVisible)
wellObject.AddMember("PlotVisible", m_bPlotVisible, allocator);
// 序列化图元外观属性
wellObject.AddMember("DotStyle", m_nDotStyle, allocator);
wellObject.AddMember("DotColorR", m_nDotColorR, allocator);
wellObject.AddMember("DotColorG", m_nDotColorG, allocator);
wellObject.AddMember("DotColorB", m_nDotColorB, allocator);
wellObject.AddMember("DotRadius", m_dDotRadius, allocator);
wellObject.AddMember("DotFilling", m_bDotFilling, allocator);
wellObject.AddMember("ShowSubObjs", m_bShowSubObjs, allocator);
// 序列化井的流动段索引 (m_nIndexF)
wellObject.AddMember("IndexFlow", m_nIndexF, allocator);
// 曲线内容由框架保存,这里只持久化当前选择的稳定 Gauge Code。
const QByteArray baPressureGaugeCode =
m_sSelectedPressureGaugeCode.toUtf8();
wellObject.AddMember("SelectedPressureGaugeCode",
rapidjson::Value(baPressureGaugeCode.constData(),
static_cast<rapidjson::SizeType>(baPressureGaugeCode.size()),
allocator).Move(), allocator);
const QByteArray baFlowGaugeCode = m_sSelectedFlowGaugeCode.toUtf8();
wellObject.AddMember("SelectedFlowGaugeCode",
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);
foreach(const nmDataPerforation* perfPtr, m_vecPerforations) {
if(perfPtr) { // 检查指针是否有效
perforationsArray.PushBack(perfPtr->ToJsonValue(allocator), allocator);
}
}
wellObject.AddMember("Perforations", perforationsArray, allocator);
return wellObject; // 返回序列化后的 RapidJSON Value
}
// 从 RapidJSON Value 反序列化数据到 nmDataWellBase
void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
{
// 反序列化名称
if(jsonValue.HasMember("WellName") && jsonValue["WellName"].IsString()) {
m_wellName = QString::fromUtf8(jsonValue["WellName"].GetString());
}
// 反序列化井编码
if(jsonValue.HasMember("WellCode") && jsonValue["WellCode"].IsString()) {
m_wellCode = QString::fromUtf8(jsonValue["WellCode"].GetString());
}
// 项目格式已在 Manager 中完成校验,这里恢复求解时保存的稳定井身份。
if(jsonValue.HasMember("WellInstanceId") &&
jsonValue["WellInstanceId"].IsString()) {
restoreWellInstanceId(QString::fromUtf8(
jsonValue["WellInstanceId"].GetString()));
}
// 反序列化井类型 (m_eWellType)
if(jsonValue.HasMember("WellType") && jsonValue["WellType"].IsInt()) {
m_eWellType = static_cast<NM_WELL_MODEL>(jsonValue["WellType"].GetInt());
}
// v5 不兼容旧项目,缺失或非法井别由 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()) {
m_x.FromJsonValue(jsonValue["X"]);
}
if(jsonValue.HasMember("Y") && jsonValue["Y"].IsObject()) {
m_y.FromJsonValue(jsonValue["Y"]);
}
if(jsonValue.HasMember("Radius") && jsonValue["Radius"].IsObject()) {
m_radius.FromJsonValue(jsonValue["Radius"]);
}
if(jsonValue.HasMember("DrillFloorElevation") && jsonValue["DrillFloorElevation"].IsObject()) {
m_drillFloorElevation.FromJsonValue(jsonValue["DrillFloorElevation"]);
}
if(jsonValue.HasMember("Zw") && jsonValue["Zw"].IsObject()) {
m_zw.FromJsonValue(jsonValue["Zw"]);
}
if(jsonValue.HasMember("WellLength") && jsonValue["WellLength"].IsObject()) {
m_wellLength.FromJsonValue(jsonValue["WellLength"]);
}
if(jsonValue.HasMember("RateDependentSkin") && jsonValue["RateDependentSkin"].IsObject()) {
m_rateDependentSkin.FromJsonValue(jsonValue["RateDependentSkin"]);
}
if(jsonValue.HasMember("dSdQ") && jsonValue["dSdQ"].IsObject()) {
m_dSdQ.FromJsonValue(jsonValue["dSdQ"]);
}
if(jsonValue.HasMember("WellboreModel") && jsonValue["WellboreModel"].IsObject()) {
m_wellboreModel.FromJsonValue(jsonValue["WellboreModel"]);
}
if(jsonValue.HasMember("WellboreStorage") && jsonValue["WellboreStorage"].IsObject()) {
m_wellboreStorage.FromJsonValue(jsonValue["WellboreStorage"]);
}
if(jsonValue.HasMember("BottomholeMD") && jsonValue["BottomholeMD"].IsObject()) {
m_bottomholeMD.FromJsonValue(jsonValue["BottomholeMD"]);
}
if(jsonValue.HasMember("InputWellHead") && jsonValue["InputWellHead"].IsObject()) {
m_inputWellHead.FromJsonValue(jsonValue["InputWellHead"]);
}
if(jsonValue.HasMember("WellHeadX") && jsonValue["WellHeadX"].IsObject()) {
m_wellHeadX.FromJsonValue(jsonValue["WellHeadX"]);
}
if(jsonValue.HasMember("WellHeadY") && jsonValue["WellHeadY"].IsObject()) {
m_wellHeadY.FromJsonValue(jsonValue["WellHeadY"]);
}
if(jsonValue.HasMember("FinalWellboreStorage") && jsonValue["FinalWellboreStorage"].IsObject()) {
m_finalWellboreStorage.FromJsonValue(jsonValue["FinalWellboreStorage"]);
}
if(jsonValue.HasMember("CInitialCFinal") && jsonValue["CInitialCFinal"].IsObject()) {
m_cInitialCFinal.FromJsonValue(jsonValue["CInitialCFinal"]);
}
if(jsonValue.HasMember("DtChangingStorage") && jsonValue["DtChangingStorage"].IsObject()) {
m_dtChangingStorage.FromJsonValue(jsonValue["DtChangingStorage"]);
}
if(jsonValue.HasMember("LeakSkin") && jsonValue["LeakSkin"].IsObject()) {
m_leakSkin.FromJsonValue(jsonValue["LeakSkin"]);
}
// 反序列化时间变表皮状态
if(jsonValue.HasMember("TimeDependentSkin") && jsonValue["TimeDependentSkin"].IsBool()) {
m_bTimeDependentSkin = jsonValue["TimeDependentSkin"].GetBool();
}
// 反序列化图元可见性 (m_bPlotVisible)
if(jsonValue.HasMember("PlotVisible") && jsonValue["PlotVisible"].IsBool()) {
m_bPlotVisible = jsonValue["PlotVisible"].GetBool();
}
// 反序列化图元外观属性
if(jsonValue.HasMember("DotStyle") && jsonValue["DotStyle"].IsInt()) {
m_nDotStyle = jsonValue["DotStyle"].GetInt();
}
if(jsonValue.HasMember("DotColorR") && jsonValue["DotColorR"].IsInt()) {
m_nDotColorR = jsonValue["DotColorR"].GetInt();
}
if(jsonValue.HasMember("DotColorG") && jsonValue["DotColorG"].IsInt()) {
m_nDotColorG = jsonValue["DotColorG"].GetInt();
}
if(jsonValue.HasMember("DotColorB") && jsonValue["DotColorB"].IsInt()) {
m_nDotColorB = jsonValue["DotColorB"].GetInt();
}
if(jsonValue.HasMember("DotRadius") && jsonValue["DotRadius"].IsDouble()) {
m_dDotRadius = jsonValue["DotRadius"].GetDouble();
}
if(jsonValue.HasMember("DotFilling") && jsonValue["DotFilling"].IsBool()) {
m_bDotFilling = jsonValue["DotFilling"].GetBool();
}
if(jsonValue.HasMember("ShowSubObjs") && jsonValue["ShowSubObjs"].IsBool()) {
m_bShowSubObjs = jsonValue["ShowSubObjs"].GetBool();
}
// 反序列化井的流动段索引 (m_nIndexF)
if(jsonValue.HasMember("IndexFlow") && jsonValue["IndexFlow"].IsInt()) {
m_nIndexF = jsonValue["IndexFlow"].GetInt();
}
// 先恢复选择键;记录数据由 Manager 在 JSON 读取后从成果 Gauge 副本恢复。
if(jsonValue.HasMember("SelectedPressureGaugeCode") &&
jsonValue["SelectedPressureGaugeCode"].IsString()) {
m_sSelectedPressureGaugeCode = QString::fromUtf8(
jsonValue["SelectedPressureGaugeCode"].GetString());
}
if(jsonValue.HasMember("SelectedFlowGaugeCode") &&
jsonValue["SelectedFlowGaugeCode"].IsString()) {
m_sSelectedFlowGaugeCode = QString::fromUtf8(
jsonValue["SelectedFlowGaugeCode"].GetString());
}
if(jsonValue.HasMember("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()) {
// 清空并释放现有射孔段对象
qDeleteAll(m_vecPerforations);
m_vecPerforations.clear();
const rapidjson::Value& perforationsArray = jsonValue["Perforations"];
for(rapidjson::SizeType i = 0; i < perforationsArray.Size(); ++i) {
if(perforationsArray[i].IsObject()) {
nmDataPerforation* newPerforation = new nmDataPerforation(); // 创建新对象
newPerforation->FromJsonValue(perforationsArray[i]);
m_vecPerforations.append(newPerforation); // 添加指针
}
}
}
// 默认与当前井身长度一致
m_dLastWellLength = m_wellLength.getValue().toDouble();
}
void nmDataWellBase::connectAttributeSignals() {
// 通用属性
connect(&m_inputWellHead, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_wellHeadX, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_wellHeadY, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_x, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_y, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_drillFloorElevation, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_radius, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_zw, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_wellLength, SIGNAL(sigValueChanged()), this, SLOT(slotWellLengthChanged()));
connect(&m_rateDependentSkin, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_dSdQ, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_wellboreModel, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_wellboreStorage, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_finalWellboreStorage, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_cInitialCFinal, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_dtChangingStorage, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_leakSkin, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
connect(&m_bottomholeMD, SIGNAL(sigValueChanged()), this, SIGNAL(sigWellDataChanged()));
}
void nmDataWellBase::notifyParameterChanged()
{
emit sigParameterChanged();
}
void nmDataWellBase::slotWellLengthChanged()
{
double oldLength = m_dLastWellLength;
double newLength = m_wellLength.getValue().toDouble();
// 避免除以零和不必要的计算
if (qFuzzyIsNull(oldLength) || oldLength <= 0 || qFuzzyCompare(oldLength, newLength)) {
return;
}
// 获取井口深度
double wellHeadMd = this->getBottomholeMD().getValue().toDouble();
// 对于多段压裂水平井类型井口MD是第一段射孔的起点
if (m_eWellType == Horizontal_Fractured_Well)
{
nmDataPerforation* pBasePerforation = getPerforation(0);
if (pBasePerforation != nullptr) {
wellHeadMd = pBasePerforation->getMdStart().getValue().toDouble();
}
}
// 遍历所有射孔段并按比例更新其MD数据
foreach(nmDataPerforation* pPerfData, m_vecPerforations) {
if(pPerfData) {
double originalPerforationMdStart = pPerfData->getMdStart().getValue().toDouble();
double originalPerforationMdEnd = pPerfData->getMdEnd().getValue().toDouble();
// 计算射孔在旧井筒上的相对位置
double relativePerforationStart = (originalPerforationMdStart - wellHeadMd) / oldLength;
double relativePerforationEnd = (originalPerforationMdEnd - wellHeadMd) / oldLength;
// 根据新的井筒长度计算新的绝对MD值
double newPerforationMdStart = wellHeadMd + (relativePerforationStart * newLength);
double newPerforationMdEnd = wellHeadMd + (relativePerforationEnd * newLength);
pPerfData->getMdStart().setValue(newPerforationMdStart);
pPerfData->getMdEnd().setValue(newPerforationMdEnd);
}
}
// 更新历史井身
m_dLastWellLength = newLength;
emit sigWellDataChanged();
}
// Getters and Setters
void nmDataWellBase::setWellName(const QString& name)
{
m_wellName = name;
}
QString nmDataWellBase::getWellName() const
{
return m_wellName;
}
void nmDataWellBase::setWellCode(const QString& code)
{
m_wellCode = code;
}
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;
}
bool nmDataWellBase::restoreWellInstanceId(
const QString& sWellInstanceId)
{
QUuid oUuid(sWellInstanceId);
if(sWellInstanceId.isEmpty() || oUuid.isNull()) {
return false;
}
m_sWellInstanceId = oUuid.toString().remove('{').remove('}');
// UUID 改变后原弱引用不再具有关联意义,等待 Manager 重新绑定。
m_pPebiResultSnapshot.clear();
return true;
}
QString nmDataWellBase::createWellInstanceId()
{
// UUID 只表示井对象身份;删除后新建的同名或同编码井必须获得新值。
return QUuid::createUuid().toString().remove('{').remove('}');
}
void nmDataWellBase::setX(const nmDataAttribute& attr)
{
m_x = attr;
emit sigWellDataChanged();
}
nmDataAttribute& nmDataWellBase::getX()
{
return m_x;
}
void nmDataWellBase::setY(const nmDataAttribute& attr)
{
m_y = attr;
emit sigWellDataChanged();
}
nmDataAttribute& nmDataWellBase::getY()
{
return m_y;
}
void nmDataWellBase::setRadius(const nmDataAttribute& attr)
{
m_radius = attr;
}
nmDataAttribute& nmDataWellBase::getRadius()
{
return m_radius;
}
void nmDataWellBase::setDrillFloorElevation(const nmDataAttribute& attr)
{
m_drillFloorElevation = attr;
}
nmDataAttribute& nmDataWellBase::getDrillFloorElevation()
{
return m_drillFloorElevation;
}
void nmDataWellBase::setZw(const nmDataAttribute& attr)
{
m_zw = attr;
}
nmDataAttribute& nmDataWellBase::getZw()
{
return m_zw;
}
void nmDataWellBase::setWellLength(const nmDataAttribute& attr)
{
m_wellLength = attr;
emit sigWellDataChanged();
}
nmDataAttribute& nmDataWellBase::getWellLength()
{
return m_wellLength;
}
void nmDataWellBase::setRateDependentSkin(const nmDataAttribute& attr)
{
m_rateDependentSkin = attr;
}
nmDataAttribute& nmDataWellBase::getRateDependentSkin()
{
return m_rateDependentSkin;
}
void nmDataWellBase::setdSdQ(const nmDataAttribute& attr)
{
m_dSdQ = attr;
}
nmDataAttribute& nmDataWellBase::getdSdQ()
{
return m_dSdQ;
}
void nmDataWellBase::setWellboreModel(const nmDataAttribute& attr)
{
m_wellboreModel = attr;
}
nmDataAttribute& nmDataWellBase::getWellboreModel()
{
return m_wellboreModel;
}
void nmDataWellBase::setWellboreStorage(const nmDataAttribute& attr)
{
m_wellboreStorage = attr;
}
nmDataAttribute& nmDataWellBase::getWellboreStorage()
{
return m_wellboreStorage;
}
void nmDataWellBase::setBottomholeMD(const nmDataAttribute& attr)
{
m_bottomholeMD = attr;
emit sigWellDataChanged();
}
nmDataAttribute& nmDataWellBase::getBottomholeMD()
{
return m_bottomholeMD;
}
bool nmDataWellBase::isTimeDependentSkin() const
{
return m_bTimeDependentSkin;
}
void nmDataWellBase::setTimeDependentSkin(bool enabled)
{
m_bTimeDependentSkin = enabled;
}
// 获取压力曲线点数据:委托到当前选中的压力记录,未选中或未命中时返回空。
QVector<QPointF> nmDataWellBase::getPressurePoints() const
{
const nmPressureGaugeRecord* pRecord =
findGaugeRecordByCode(m_vecPressureRecords, m_sSelectedPressureGaugeCode);
return pRecord == nullptr ? QVector<QPointF>() : pRecord->vecPressurePoints;
}
// 设置压力曲线点数据:写入当前选中记录;若无选中记录,新建一条内部占位记录承载数据,
// 兼容井型切换等历史调用方对该 setter 的既有假设。
void nmDataWellBase::setPressurePoints(const QVector<QPointF>& points)
{
nmPressureGaugeRecord* pRecord =
findGaugeRecordByCode(m_vecPressureRecords, m_sSelectedPressureGaugeCode);
if(pRecord == nullptr) {
nmPressureGaugeRecord newRecord;
newRecord.sGaugeCode = kManualPressureGaugeCode;
m_vecPressureRecords.append(newRecord);
m_sSelectedPressureGaugeCode = kManualPressureGaugeCode;
pRecord = &m_vecPressureRecords.last();
}
pRecord->vecPressurePoints = points;
pRecord->eStatus = points.isEmpty() ? NM_GaugeRecord_Empty : NM_GaugeRecord_Usable;
}
// 获取井别对应相的流量曲线点数据
QVector<QPointF> nmDataWellBase::getFlowPoints() const
{
return getFlowPoints(getReferenceFlowPhase());
}
// 设置井别对应相的流量曲线点数据
void nmDataWellBase::setFlowPoints(const QVector<QPointF>& points)
{
setFlowPoints(getReferenceFlowPhase(), points);
}
NM_PHASE_TYPE nmDataWellBase::getReferenceFlowPhase() const
{
switch(m_eWellCategory) {
case NM_WellCategory_Oil:
return PHASE_Oil;
case NM_WellCategory_Gas:
return PHASE_Gas;
case NM_WellCategory_Water:
return PHASE_Water;
default:
return PHASE_UNKNOWN;
}
}
// 获取指定相流量点数据:委托到当前选中的流量记录,未选中或未命中时返回空。
QVector<QPointF> nmDataWellBase::getFlowPoints(
NM_PHASE_TYPE eFlowPhase) const
{
const nmFlowGaugeRecord* pRecord =
findGaugeRecordByCode(m_vecFlowRecords, m_sSelectedFlowGaugeCode);
if(pRecord == nullptr) {
return QVector<QPointF>();
}
switch(eFlowPhase) {
case PHASE_Oil:
return pRecord->vecOilPoints;
case PHASE_Gas:
return pRecord->vecGasPoints;
case PHASE_Water:
return pRecord->vecWaterPoints;
default:
return QVector<QPointF>();
}
}
// 设置指定相流量点数据:写入当前选中记录;若无选中记录,新建一条内部占位记录承载数据,
// 兼容井型切换等历史调用方对该 setter 的既有假设。
void nmDataWellBase::setFlowPoints(
NM_PHASE_TYPE eFlowPhase,
const QVector<QPointF>& points)
{
nmFlowGaugeRecord* pRecord =
findGaugeRecordByCode(m_vecFlowRecords, m_sSelectedFlowGaugeCode);
if(pRecord == nullptr) {
nmFlowGaugeRecord newRecord;
newRecord.sGaugeCode = kManualFlowGaugeCode;
m_vecFlowRecords.append(newRecord);
m_sSelectedFlowGaugeCode = kManualFlowGaugeCode;
pRecord = &m_vecFlowRecords.last();
}
switch(eFlowPhase) {
case PHASE_Oil:
pRecord->vecOilPoints = points;
break;
case PHASE_Gas:
pRecord->vecGasPoints = points;
break;
case PHASE_Water:
pRecord->vecWaterPoints = points;
break;
default:
break;
}
pRecord->eStatus = (pRecord->vecOilPoints.isEmpty()
&& pRecord->vecGasPoints.isEmpty()
&& pRecord->vecWaterPoints.isEmpty())
? NM_GaugeRecord_Empty : NM_GaugeRecord_Usable;
}
// 设置全部压力记录列表(不改变当前选中 Code
void nmDataWellBase::setPressureRecords(const QVector<nmPressureGaugeRecord>& vecRecords)
{
m_vecPressureRecords = vecRecords;
}
// 设置全部流量记录列表(不改变当前选中 Code
void nmDataWellBase::setFlowRecords(const QVector<nmFlowGaugeRecord>& vecRecords)
{
m_vecFlowRecords = vecRecords;
}
// 按 Gauge Code 选中一条压力记录;传入空字符串表示无选中。
void nmDataWellBase::selectPressureGaugeCode(const QString& sGaugeCode)
{
m_sSelectedPressureGaugeCode = sGaugeCode;
}
// 按 Gauge Code 选中一条流量记录;传入空字符串表示无选中。
void nmDataWellBase::selectFlowGaugeCode(const QString& sGaugeCode)
{
m_sSelectedFlowGaugeCode = sGaugeCode;
}
// 切换选中流量记录:把当前 m_nIndexF 写回旧记录的 nIndexF
// 再用新记录的 nIndexF 恢复并按新记录的真实段数校验当前索引。
void nmDataWellBase::switchSelectedFlowGaugeCode(const QString& sGaugeCode)
{
nmFlowGaugeRecord* pOldRecord =
findGaugeRecordByCode(m_vecFlowRecords, m_sSelectedFlowGaugeCode);
if(pOldRecord != nullptr) {
pOldRecord->nIndexF = m_nIndexF;
}
m_sSelectedFlowGaugeCode = sGaugeCode;
nmFlowGaugeRecord* pNewRecord =
findGaugeRecordByCode(m_vecFlowRecords, m_sSelectedFlowGaugeCode);
const int nSegmentCount = getFlowSegmentCount();
if(pNewRecord == nullptr || nSegmentCount <= 0) {
m_nIndexF = 0;
return;
}
int nRestoredIndex = pNewRecord->nIndexF;
if(nRestoredIndex < 1 || nRestoredIndex > nSegmentCount) {
nRestoredIndex = nSegmentCount;
}
m_nIndexF = nRestoredIndex;
}
QString nmDataWellBase::getSelectedPressureGaugeCode() const
{
return m_sSelectedPressureGaugeCode;
}
QString nmDataWellBase::getSelectedFlowGaugeCode() const
{
return m_sSelectedFlowGaugeCode;
}
QVector<nmPressureGaugeRecord> nmDataWellBase::getPressureRecords() const
{
return m_vecPressureRecords;
}
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;
}
void nmDataWellBase::buildGaugeInputData(
nmWellGaugeInputData& oInput) const
{
oInput = nmWellGaugeInputData();
oInput.sWellInstanceId = m_sWellInstanceId;
oInput.sWellCode = m_wellCode;
oInput.eWellCategory = m_eWellCategory;
oInput.sPressureGaugeCode = m_sSelectedPressureGaugeCode;
oInput.vecPressurePoints = getPressurePoints();
oInput.sFlowGaugeCode = m_sSelectedFlowGaugeCode;
oInput.bUseOilRate = m_bUseOilRate;
oInput.bUseGasRate = m_bUseGasRate;
oInput.bUseWaterRate = m_bUseWaterRate;
oInput.eFlowSchedulePhase = getFlowSchedulePhase();
oInput.nFlowSectionIndex = m_nIndexF;
QVector<QPointF> vecSchedulePoints;
if(oInput.eFlowSchedulePhase != PHASE_UNKNOWN) {
vecSchedulePoints = getFlowSegmentPoints(oInput.eFlowSchedulePhase);
}
oInput.vecFlowDurations.reserve(vecSchedulePoints.size());
for(int nIndex = 0; nIndex < vecSchedulePoints.size(); ++nIndex) {
oInput.vecFlowDurations.append(vecSchedulePoints[nIndex].x());
}
buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Oil),
m_bUseOilRate, vecSchedulePoints.size(), oInput.vecOilRates);
buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Gas),
m_bUseGasRate, vecSchedulePoints.size(), oInput.vecGasRates);
buildNormalizedPhaseRates(getFlowSegmentPoints(PHASE_Water),
m_bUseWaterRate, vecSchedulePoints.size(), oInput.vecWaterRates);
}
QString nmDataWellBase::calculateGaugeInputSha1(
const nmWellGaugeInputData& oInput)
{
QByteArray baData;
appendGaugeUInt32(baData, 1u);
if(!appendGaugeString(baData, oInput.sWellInstanceId) ||
!appendGaugeString(baData, oInput.sWellCode) ||
!appendGaugeString(baData, oInput.sPressureGaugeCode) ||
!appendGaugeString(baData, oInput.sFlowGaugeCode)) {
return QString();
}
appendGaugeUInt32(baData, static_cast<quint32>(oInput.eWellCategory));
appendGaugePointVector(baData, oInput.vecPressurePoints);
baData.append(oInput.bUseOilRate ? '\1' : '\0');
baData.append(oInput.bUseGasRate ? '\1' : '\0');
baData.append(oInput.bUseWaterRate ? '\1' : '\0');
appendGaugeUInt32(baData,
static_cast<quint32>(oInput.eFlowSchedulePhase));
appendGaugeDoubleVector(baData, oInput.vecFlowDurations);
appendGaugeDoubleVector(baData, oInput.vecOilRates);
appendGaugeDoubleVector(baData, oInput.vecGasRates);
appendGaugeDoubleVector(baData, oInput.vecWaterRates);
appendGaugeUInt32(baData,
static_cast<quint32>(oInput.nFlowSectionIndex));
return QString::fromLatin1(QCryptographicHash::hash(
baData, QCryptographicHash::Sha1).toHex());
}
QString nmDataWellBase::calculateGaugeInputSha1() const
{
nmWellGaugeInputData oInput;
buildGaugeInputData(oInput);
return calculateGaugeInputSha1(oInput);
}
int nmDataWellBase::getRawFlowRecordSegmentCount(
const nmFlowGaugeRecord& oRecord)
{
bool bSharedPlaceholder = false;
if(oRecord.bMultiPhase && !oRecord.vecOilPoints.isEmpty() &&
!oRecord.vecGasPoints.isEmpty() &&
!oRecord.vecWaterPoints.isEmpty()) {
bSharedPlaceholder = oRecord.vecOilPoints.first() == QPointF(0.0, 0.0) &&
oRecord.vecGasPoints.first() == QPointF(0.0, 0.0) &&
oRecord.vecWaterPoints.first() == QPointF(0.0, 0.0);
}
const QVector<QPointF> arrPoints[] = {
oRecord.vecOilPoints, oRecord.vecGasPoints, oRecord.vecWaterPoints
};
int nMaximumCount = 0;
for(int nPhase = 0; nPhase < 3; ++nPhase) {
int nCount = arrPoints[nPhase].size();
if(oRecord.bMultiPhase) {
if(bSharedPlaceholder && nCount > 0) {
--nCount;
}
} else if(nCount > 0 &&
arrPoints[nPhase].first() == QPointF(0.0, 0.0)) {
--nCount;
}
nMaximumCount = qMax(nMaximumCount, nCount);
}
return nMaximumCount;
}
QVector<QPointF> nmDataWellBase::getFlowSegmentPoints(
NM_PHASE_TYPE eFlowPhase) const
{
QVector<QPointF> vecSegments = getFlowPoints(eFlowPhase);
const nmFlowGaugeRecord* pRecord =
findGaugeRecordByCode(m_vecFlowRecords,
m_sSelectedFlowGaugeCode);
bool bHasPlaceholder = false;
if(pRecord != nullptr && pRecord->bMultiPhase) {
// 多相数据共用同一时间列,只有完整的 (0,0,0,0) 才是占位行。
// 不能按单相分别删除,否则某相首段流量为零时会造成三相错位。
bHasPlaceholder = !pRecord->vecOilPoints.isEmpty() &&
!pRecord->vecGasPoints.isEmpty() &&
!pRecord->vecWaterPoints.isEmpty() &&
pRecord->vecOilPoints.first().x() == 0.0 &&
pRecord->vecGasPoints.first().x() == 0.0 &&
pRecord->vecWaterPoints.first().x() == 0.0 &&
pRecord->vecOilPoints.first().y() == 0.0 &&
pRecord->vecGasPoints.first().y() == 0.0 &&
pRecord->vecWaterPoints.first().y() == 0.0;
} else {
// 单相记录沿用框架约定,首个 (0,0) 为非业务占位点。
bHasPlaceholder = !vecSegments.isEmpty() &&
vecSegments.first().x() == 0.0 &&
vecSegments.first().y() == 0.0;
}
if(bHasPlaceholder && !vecSegments.isEmpty()) {
vecSegments.remove(0);
}
return vecSegments;
}
int nmDataWellBase::getFlowSegmentCount(NM_PHASE_TYPE eFlowPhase) const
{
return getFlowSegmentPoints(eFlowPhase).size();
}
bool nmDataWellBase::hasFlowPhaseData(NM_PHASE_TYPE eFlowPhase) const
{
return getFlowSegmentCount(eFlowPhase) > 0;
}
bool nmDataWellBase::hasAnyFlowData() const
{
return hasFlowPhaseData(PHASE_Oil) ||
hasFlowPhaseData(PHASE_Gas) ||
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(nmIsSelectableFlowRecord(oRecord)) {
return true;
}
}
return false;
}
QVector<QPointF> nmDataWellBase::getFlowSegmentPoints() const
{
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) && nmHasNonZeroFlowRate(
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(nmHasNonZeroFlowRate(vecPoints)) {
return arrPhases[nIndex];
}
}
return PHASE_UNKNOWN;
}
int nmDataWellBase::getFlowSegmentCount() const
{
return getFlowSegmentPoints().size();
}
bool nmDataWellBase::hasValidFlowSectionIndex() const
{
return m_nIndexF >= 1 && m_nIndexF <= getFlowSegmentCount();
}
int nmDataWellBase::getIndexF() const
{
return m_nIndexF;
}
void nmDataWellBase::setIndexF(const int newIndex)
{
m_nIndexF = newIndex;
}
// Location---位置
void nmDataWellBase::setInputWellHead(const nmDataAttribute& attr)
{
m_inputWellHead = attr;
}
nmDataAttribute& nmDataWellBase::getInputWellHead()
{
return m_inputWellHead;
}
void nmDataWellBase::setWellHeadX(const nmDataAttribute& attr)
{
m_wellHeadX = attr;
}
nmDataAttribute& nmDataWellBase::getWellHeadX()
{
return m_wellHeadX;
}
void nmDataWellBase::setWellHeadY(const nmDataAttribute& attr)
{
m_wellHeadY = attr;
}
nmDataAttribute& nmDataWellBase::getWellHeadY()
{
return m_wellHeadY;
}
// Wellbore---井储
void nmDataWellBase::setCInitialCFinal(const nmDataAttribute& attr)
{
m_cInitialCFinal = attr;
}
nmDataAttribute& nmDataWellBase::getCInitialCFinal()
{
return m_cInitialCFinal;
}
void nmDataWellBase::setDtChangingStorage(const nmDataAttribute& attr)
{
m_dtChangingStorage = attr;
}
nmDataAttribute& nmDataWellBase::getDtChangingStorage()
{
return m_dtChangingStorage;
}
void nmDataWellBase::setLeakSkin(const nmDataAttribute& attr)
{
m_leakSkin = attr;
}
nmDataAttribute& nmDataWellBase::getLeakSkin()
{
return m_leakSkin;
}
void nmDataWellBase::setFinalWellboreStorage(const nmDataAttribute& attr)
{
m_finalWellboreStorage = attr;
}
nmDataAttribute& nmDataWellBase::getFinalWellboreStorage()
{
return m_finalWellboreStorage;
}
bool nmDataWellBase::getPlotVisible() const
{
return m_bPlotVisible;
}
void nmDataWellBase::setPlotVisible(const bool newState)
{
m_bPlotVisible = newState;
}
void nmDataWellBase::setWellType(NM_WELL_MODEL newWellType)
{
m_eWellType = newWellType;
}
NM_WELL_MODEL nmDataWellBase::getWellType() const
{
return m_eWellType;
}
// 历史数据相关方法
QVector<QVector<double>> nmDataWellBase::getHistoryPressure() {
return m_vvecHsyPressure;
}
void nmDataWellBase::setHistoryPressure(QVector<QVector<double>> pressureData) {
m_vvecHsyPressure = pressureData;
m_sHistoryGaugeInputSha1.clear();
}
QVector<QVector<double>> nmDataWellBase::getHistoryLogLog() {
return m_vvecHsyLogLog;
}
void nmDataWellBase::setHistoryLogLog(QVector<QVector<double>> loglogData) {
m_vvecHsyLogLog = loglogData;
m_sHistoryGaugeInputSha1.clear();
}
QVector<QVector<double>> nmDataWellBase::getHistorySemiLog() {
return m_vvecHsySemiLog;
}
void nmDataWellBase::setHistorySemiLog(QVector<QVector<double>> semiLogData) {
m_vvecHsySemiLog = semiLogData;
m_sHistoryGaugeInputSha1.clear();
}
void nmDataWellBase::setHistoryData(
const QVector<QVector<double> >& vecPressure,
const QVector<QVector<double> >& vecLogLog,
const QVector<QVector<double> >& vecSemiLog,
const QString& sGaugeInputSha1)
{
m_vvecHsyPressure = vecPressure;
m_vvecHsyLogLog = vecLogLog;
m_vvecHsySemiLog = vecSemiLog;
m_sHistoryGaugeInputSha1 = sGaugeInputSha1;
}
QString nmDataWellBase::getHistoryGaugeInputSha1() const
{
return m_sHistoryGaugeInputSha1;
}
// 计算结果相关方法
QVector<QVector<double>> nmDataWellBase::getResultPressure()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultPressure;
}
}
return QVector<QVector<double> >();
}
QVector<QVector<double>> nmDataWellBase::getResultLogLog()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultLogLog;
}
}
return QVector<QVector<double> >();
}
QVector<QVector<double>> nmDataWellBase::getResultSemiLog()
{
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
m_pPebiResultSnapshot.toStrongRef();
if(!pSnapshot.isNull()) {
const nmPebiResultWellSnapshot* pWell =
pSnapshot->findWell(m_sWellInstanceId);
if(pWell != NULL) {
return pWell->m_oCurves.m_vecResultSemiLog;
}
}
return QVector<QVector<double> >();
}
void nmDataWellBase::bindPebiResultSnapshot(
const QWeakPointer<const nmPebiResultSnapshot>& pSnapshot)
{
m_pPebiResultSnapshot = pSnapshot;
}
// 射孔管理相关方法
void nmDataWellBase::addPerforation(nmDataPerforation* perforation)
{
if(perforation) { // 确保传入的指针非空
m_vecPerforations.append(perforation);
emit sigWellDataChanged();
}
}
void nmDataWellBase::removePerforation(int index)
{
if(index >= 0 && index < m_vecPerforations.size()) {
delete m_vecPerforations.at(index); // 释放内存
m_vecPerforations.remove(index); // 从 QVector 中移除指针
emit sigWellDataChanged();
}
}
nmDataPerforation* nmDataWellBase::getPerforation(int index)
{
if(index >= 0 && index < m_vecPerforations.size()) {
return m_vecPerforations.at(index);
}
return nullptr;
}
nmDataPerforation nmDataWellBase::getPerforationCopy(int index)
{
if (index >= 0 && index < m_vecPerforations.size() && m_vecPerforations[index] != nullptr) {
return *m_vecPerforations[index]; // 调用拷贝构造函数
}
return nmDataPerforation(); // 返回默认构造对象
}
void nmDataWellBase::updatePerforation(int index, const nmDataPerforation& newData)
{
if (index >= 0 && index < m_vecPerforations.size() && m_vecPerforations[index] != nullptr) {
*m_vecPerforations[index] = newData; // 调用赋值运算符
emit sigWellDataChanged(); // 发出数据变化信号
}
}
int nmDataWellBase::getPerforationCount()
{
return m_vecPerforations.size();
}
QVector<nmDataPerforation*>& nmDataWellBase::getPerforations()
{
return m_vecPerforations;
}
void nmDataWellBase::clearPerforations()
{
foreach (nmDataPerforation* perf , m_vecPerforations)
{
delete perf;
}
m_vecPerforations.clear();
}
bool nmDataWellBase::applyWellboreEdit(
double dBottomholeMd,
double dWellLength,
const QVector<nmDataPerforation>& vecPerforations)
{
// 第一步:批量写入只接受完整快照,水平压裂井还必须保留基准射孔。
if (vecPerforations.size() != m_vecPerforations.size() ||
(m_eWellType == Horizontal_Fractured_Well &&
vecPerforations.isEmpty())) {
return false;
}
for (int nIndex = 0; nIndex < m_vecPerforations.size(); ++nIndex) {
if (m_vecPerforations[nIndex] == nullptr) {
return false;
}
}
// 第二步:先同步井长基准,使原井长槽自然跳过隐式缩放。
bool bSignalsWereBlocked = blockSignals(true);
m_bottomholeMD.setValue(dBottomholeMd);
m_dLastWellLength = dWellLength;
m_wellLength.setValue(dWellLength);
for (int nIndex = 0; nIndex < vecPerforations.size(); ++nIndex) {
*m_vecPerforations[nIndex] = vecPerforations[nIndex];
}
blockSignals(bSignalsWereBlocked);
// 第三步:完整数据落地后只广播一次稳定状态。
if (!bSignalsWereBlocked) {
emit sigWellDataChanged();
}
return true;
}
bool nmDataWellBase::isBasePerforation()
{
if(m_vecPerforations.size() > 1 || m_vecPerforations.size() <= 0 ) {
return false;
}
// 检查第一个射孔段数据是不是基础射孔
nmDataPerforation* pPerf = m_vecPerforations[0];
if (pPerf == nullptr)
return false;
// 名称检查
if (pPerf->getName().getValue().toString() != "Perforation#1")
return false;
// 长度检查
double bottomholeMd = this->getBottomholeMD().getValue().toDouble();
double mdStart = pPerf->getMdStart().getValue().toDouble();
double mdEnd = pPerf->getMdEnd().getValue().toDouble();
double expectedMdEnd = bottomholeMd + this->getWellLength().getValue().toDouble();
if (qAbs(mdStart - bottomholeMd) > 1e-9)
return false;
if (qAbs(mdEnd - expectedMdEnd) > 1e-9)
return false;
// 表皮系数检查
if(qAbs(pPerf->getSkin().getValue().toDouble()) > 1e-9)
return false;
return true;
}
// 井级别的流量和时间查询方法
double nmDataWellBase::getTotalTimeRange() const
{
QVector<QPointF> allFlowPoints = getFlowPoints();
double totalTime = 0.0;
for (int i = 0; i < allFlowPoints.size(); ++i) {
totalTime += allFlowPoints[i].x();
}
return totalTime;
}
double nmDataWellBase::getFlowRateAtTime(double time) const
{
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 遍历所有流量点,找到包含指定时间的阶梯段
for (int i = 0; i < allFlowPoints.size(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepStartTime = currentTime;
double stepEndTime = currentTime + point.x();
// 如果时间在这个阶梯段内
if (time >= stepStartTime && time < stepEndTime) {
return point.y(); // 返回该阶梯段的流量值
}
currentTime = stepEndTime;
// 如果时间超出了所有阶梯段,返回最后一个阶梯段的流量值
if (time >= stepEndTime && i == allFlowPoints.size() - 1) {
return point.y();
}
}
// 如果没有找到合适的阶梯段可能是时间为0或负数返回第一个阶梯段的流量值
if (!allFlowPoints.isEmpty()) {
return allFlowPoints.first().y();
}
return 100.0; // 默认值
}
QVector<double> nmDataWellBase::getAllFlowStepBoundaries() const
{
QVector<double> allBoundaries;
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 添加起始时间
allBoundaries.append(currentTime);
// 遍历所有流量点,收集所有阶梯边界时间
for (int i = 0; i < allFlowPoints.size(); ++i) {
const QPointF& point = allFlowPoints.at(i);
currentTime += point.x();
allBoundaries.append(currentTime);
}
return allBoundaries;
}
QVector<QPointF> nmDataWellBase::getFlowStepsInTimeRange(double startTime, double endTime) const
{
QVector<QPointF> stepsInRange;
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
for (int i = 0; i < allFlowPoints.size(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepStartTime = currentTime;
double stepEndTime = currentTime + point.x();
// 检查这个阶梯段是否与指定时间范围有重叠
if (stepEndTime > startTime && stepStartTime < endTime) {
// 计算在范围内的部分
double overlapStart = qMax(stepStartTime, startTime);
double overlapEnd = qMin(stepEndTime, endTime);
if (overlapEnd > overlapStart) {
stepsInRange.append(QPointF(overlapEnd - overlapStart, point.y()));
}
}
currentTime = stepEndTime;
if (currentTime >= endTime) {
break; // 超出范围,停止
}
}
return stepsInRange;
}
double nmDataWellBase::findFlowStepBoundaryAtTime(double timePoint) const
{
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 遍历所有流量点找到包含timePoint的阶梯段
for (int i = 0; i < allFlowPoints.count(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepStartTime = currentTime;
double stepEndTime = currentTime + point.x();
// 如果timePoint在这个阶梯段内
if (timePoint >= stepStartTime && timePoint < stepEndTime) {
return stepEndTime; // 返回该阶梯段的结束时间作为分割边界
}
currentTime = stepEndTime;
}
return -1.0; // 没有找到合适的阶梯边界
}
double nmDataWellBase::findSmartFlowStepBoundary(double targetTime) const
{
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 遍历所有流量点找到包含targetTime的阶梯段
for (int i = 0; i < allFlowPoints.count(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepStartTime = currentTime;
double stepEndTime = currentTime + point.x();
// 如果targetTime在这个阶梯段内
if (targetTime >= stepStartTime && targetTime <= stepEndTime) {
double stepMidTime = (stepStartTime + stepEndTime) / 2.0;
// 根据在阶梯段的前半部分还是后半部分决定吸附位置
if (targetTime <= stepMidTime) {
// 在前半部分,吸附到阶梯段开始
return stepStartTime;
} else {
// 在后半部分,吸附到阶梯段结束
return stepEndTime;
}
}
currentTime = stepEndTime;
}
// 如果没有找到对应的阶梯段使用已有的findNearestFlowStepBoundary函数
return findNearestFlowStepBoundary(targetTime);
}
double nmDataWellBase::findNearestFlowStepBoundary(double targetTime) const
{
QVector<double> boundaries = getAllFlowStepBoundaries();
if (boundaries.isEmpty()) {
return targetTime; // 如果没有边界,返回原时间
}
double nearestBoundary = boundaries[0];
double minDistance = qAbs(targetTime - boundaries[0]);
for (int i = 1; i < boundaries.size(); ++i) {
double distance = qAbs(targetTime - boundaries[i]);
if (distance < minDistance) {
minDistance = distance;
nearestBoundary = boundaries[i];
}
}
return nearestBoundary;
}
bool nmDataWellBase::isTimeInFlowStepFirstHalf(double time) const
{
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 遍历所有流量点找到包含time的阶梯段
for (int i = 0; i < allFlowPoints.count(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepStartTime = currentTime;
double stepEndTime = currentTime + point.x();
// 如果time在这个阶梯段内
if (time >= stepStartTime && time <= stepEndTime) {
double stepMidTime = (stepStartTime + stepEndTime) / 2.0;
return time <= stepMidTime;
}
currentTime = stepEndTime;
}
return false;
}
// 射孔段相关的查询方法
QVector<double> nmDataWellBase::getFlowStepBoundariesInSegment(int segmentIndex, int perforationIndex) const
{
QVector<double> boundaries;
if (perforationIndex < 0 || perforationIndex >= m_vecPerforations.size()) {
return boundaries;
}
nmDataPerforation* perforation = m_vecPerforations[perforationIndex];
if (!perforation) {
return boundaries;
}
const QVector<FlowSegmentData>& segments = perforation->getFlowSegments();
if (segmentIndex < 0 || segmentIndex >= segments.size()) {
return boundaries;
}
const FlowSegmentData& segment = segments[segmentIndex];
double segmentStartTime = segment.segmentStart.getValue().toDouble();
double segmentEndTime = segment.segmentEnd.getValue().toDouble();
QVector<QPointF> allFlowPoints = getFlowPoints();
double currentTime = 0.0;
// 遍历所有流量点,找到在该段范围内的所有阶梯边界
for (int i = 0; i < allFlowPoints.size(); ++i) {
const QPointF& point = allFlowPoints.at(i);
double stepEndTime = currentTime + point.x();
// 如果这个阶梯段与目标段有重叠
if (stepEndTime > segmentStartTime && currentTime < segmentEndTime) {
// 添加在段范围内的边界点
if (stepEndTime <= segmentEndTime) {
boundaries.append(stepEndTime);
}
}
currentTime = stepEndTime;
// 如果已经超出段的结束时间,停止遍历
if (currentTime >= segmentEndTime) {
break;
}
}
// 确保最后一个边界不超过段的结束时间
if (!boundaries.isEmpty() && boundaries.last() > segmentEndTime) {
boundaries[boundaries.size() - 1] = segmentEndTime;
}
// 如果没有找到任何边界,添加段的结束时间
if (boundaries.isEmpty()) {
boundaries.append(segmentEndTime);
}
return boundaries;
}
QVector<double> nmDataWellBase::getAllBoundariesInSegment(int segmentIndex, int perforationIndex) const
{
QVector<double> allBoundaries;
if (perforationIndex < 0 || perforationIndex >= m_vecPerforations.size()) {
return allBoundaries;
}
nmDataPerforation* perforation = m_vecPerforations[perforationIndex];
if (!perforation) {
return allBoundaries;
}
const QVector<FlowSegmentData>& segments = perforation->getFlowSegments();
if (segmentIndex < 0 || segmentIndex >= segments.size()) {
return allBoundaries;
}
const FlowSegmentData& segment = segments[segmentIndex];
double segmentStartTime = segment.segmentStart.getValue().toDouble();
double segmentEndTime = segment.segmentEnd.getValue().toDouble();
// 获取流量阶梯边界
QVector<double> flowBoundaries = getFlowStepBoundariesInSegment(segmentIndex, perforationIndex);
// 添加所有边界
allBoundaries.append(segmentStartTime);
for (int i = 0; i < flowBoundaries.size(); ++i) {
double boundary = flowBoundaries[i];
if (boundary > segmentStartTime && boundary <= segmentEndTime) {
allBoundaries.append(boundary);
}
}
// 排序并去重
qSort(allBoundaries);
for (int i = allBoundaries.size() - 1; i > 0; --i) {
if (qAbs(allBoundaries[i] - allBoundaries[i - 1]) < 1e-6) {
allBoundaries.remove(i);
}
}
return allBoundaries;
}
int nmDataWellBase::countFlowStepsInSegment(int segmentIndex, int perforationIndex) const
{
if (perforationIndex < 0 || perforationIndex >= m_vecPerforations.size()) {
return 0;
}
nmDataPerforation* perforation = m_vecPerforations[perforationIndex];
if (!perforation) {
return 0;
}
const QVector<FlowSegmentData>& segments = perforation->getFlowSegments();
if (segmentIndex < 0 || segmentIndex >= segments.size()) {
return 0;
}
const FlowSegmentData& segment = segments[segmentIndex];
double startTime = segment.segmentStart.getValue().toDouble();
double endTime = segment.segmentEnd.getValue().toDouble();
QVector<QPointF> stepsInRange = getFlowStepsInTimeRange(startTime, endTime);
return stepsInRange.size();
}
bool nmDataWellBase::canFlowSegmentBeSplit(int segmentIndex, int perforationIndex) const
{
return countFlowStepsInSegment(segmentIndex, perforationIndex) > 1;
}
double nmDataWellBase::findNearestFlowStepBoundaryBeforeSegment(int segmentIndex, int perforationIndex) const
{
if (perforationIndex < 0 || perforationIndex >= m_vecPerforations.size()) {
return -1.0;
}
nmDataPerforation* perforation = m_vecPerforations[perforationIndex];
if (!perforation) {
return -1.0;
}
const QVector<FlowSegmentData>& segments = perforation->getFlowSegments();
if (segmentIndex <= 0 || segmentIndex >= segments.size()) {
return -1.0; // 第一个段或无效索引,无法插入
}
// 获取当前段的开始时间
double currentSegmentStartTime = segments[segmentIndex].segmentStart.getValue().toDouble();
// 获取前一个段的开始和结束时间
double prevSegmentStartTime = segments[segmentIndex - 1].segmentStart.getValue().toDouble();
double prevSegmentEndTime = segments[segmentIndex - 1].segmentEnd.getValue().toDouble();
// 在前一个段内查找所有流量阶梯边界
QVector<double> boundaries = getFlowStepBoundariesInSegment(segmentIndex - 1, perforationIndex);
if (boundaries.isEmpty()) {
return -1.0;
}
// 找到距离当前段开始时间最近的边界(但要小于当前段的开始时间,且不等于前一个段的结束时间)
double nearestBoundary = -1.0;
double minDistance = 1e10;
for (int i = 0; i < boundaries.size(); ++i) {
double boundary = boundaries[i];
// 边界必须在前一个段内部,且小于当前段的开始时间,且不等于前一个段的结束时间
if (boundary > prevSegmentStartTime &&
boundary < currentSegmentStartTime &&
qAbs(boundary - prevSegmentEndTime) > 1e-6) { // 不等于前一个段的结束时间
double distance = currentSegmentStartTime - boundary;
if (distance < minDistance) {
minDistance = distance;
nearestBoundary = boundary;
}
}
}
return nearestBoundary;
}
bool nmDataWellBase::exportHistoryLogLogToCsv(const QString& outDir)
{
// 1) 取历史数据:期望 hist[0]=t, hist[1]=p, hist[2]=deriv
QVector<QVector<double> > hist = this->getHistoryLogLog();
if(hist.size() < 2) {
return false;
}
const QVector<double>& t = hist[0];
const QVector<double>& p = hist[1];
QVector<double> deriv;
bool hasDeriv = false;
if(hist.size() >= 3) {
deriv = hist[2];
hasDeriv = true;
}
// 2) 长度检查
int n = t.size();
if(n <= 0) return false;
if(p.size() != n) return false;
if(hasDeriv && deriv.size() != n) {
// 导数长度不一致就当没有导数
hasDeriv = false;
}
// 3) 输出文件名
QString wellName = this->getWellName(); // 你已有方法
if(wellName.isEmpty()) wellName = "UnknownWell";
// 文件直接放 temp 目录,不创建子目录
QString filePath = QDir(outDir).filePath(wellName + "_loglog.csv");
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
// 4) 写表头
if(hasDeriv) {
out << "t,p,deriv\n";
} else {
out << "t,p\n";
}
// 5) 正确写入:按索引 i 写 t[i], p[i], deriv[i]
for(int i = 0; i < n; ++i) {
// 过滤非法数据
double ti = t[i];
double pi = p[i];
if(ti <= 0) continue;
if(pi <= 0) continue;
out << QString::number(ti, 'g', 12) << ",";
out << QString::number(pi, 'g', 12);
if(hasDeriv) {
double di = deriv[i];
if(di <= 0) di = 1e-12; // 你后续 log10 时需要正数
out << "," << QString::number(di, 'g', 12);
}
out << "\n";
}
file.close();
return true;
}
bool nmDataWellBase::exportHistoryRateToCsv(const QString& outDir)
{
// 1) 获取真实流量段 (t, q),不把可选 (0,0) 占位点写入文件。
const QVector<QPointF> vecTimeQ = this->getFlowSegmentPoints();
if(vecTimeQ.isEmpty() || !hasValidFlowSectionIndex() ||
!nmIsValidWellCategory(m_eWellCategory)) {
return false;
}
// 2) sectionIndex流动段一基索引
int sectionIndex = this->getIndexF();
// 3) 输出文件名
QString wellName = this->getWellName();
if(wellName.isEmpty()) wellName = "UnknownWell";
QString filePath = QDir(outDir).filePath(wellName + "_rate.csv");
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
// 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) 写入每个点
for(int i = 0; i < vecTimeQ.size(); ++i)
{
double ti = vecTimeQ[i].x();
double qi = vecTimeQ[i].y();
if(ti <= 0) continue;
if(qi < 0) qi = 0; // 流量允许关井
out << QString::number(ti, 'g', 12) << ",";
out << QString::number(qi, 'g', 12) << ",";
out << sectionIndex << "\n";
}
file.close();
return true;
}
bool nmDataWellBase::exportHistoryPressureToCsv(const QString& outDir)
{
// 1) 取历史压力数据:期望 hist[0]=t, hist[1]=p
QVector<QVector<double> > hist = this->getHistoryPressure();
if(hist.size() < 2) {
return false;
}
const QVector<double>& t = hist[0];
const QVector<double>& p = hist[1];
// 2) 长度检查
int n = t.size();
if(n <= 0) return false;
if(p.size() != n) return false;
// 3) 输出文件名
QString wellName = this->getWellName();
if(wellName.isEmpty()) wellName = "UnknownWell";
// 文件直接放 temp 目录,不创建子目录
QString filePath = QDir(outDir).filePath(wellName + "_pressure.csv");
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
out.setRealNumberPrecision(12); // 设置精度
// 4) 写表头
out << "t,p\n";
// 5) 写入数据
for(int i = 0; i < n; ++i) {
double ti = t[i];
double pi = p[i];
// 过滤非法数据
if(ti <= 0) continue;
if(pi <= 0) continue; // 假设压力为正
out << ti << "," << pi << "\n";
}
file.close();
return true;
}
bool nmDataWellBase::exportResultLogLogToCsv(const QString& outDir)
{
QVector<QVector<double> > result = this->getResultLogLog();
if(result.size() < 2) {
return false;
}
const QVector<double>& t = result[0];
const QVector<double>& p = result[1];
QVector<double> deriv;
bool hasDeriv = false;
if(result.size() >= 3) {
deriv = result[2];
hasDeriv = true;
}
int n = t.size();
if(n <= 0) return false;
if(p.size() != n) return false;
if(hasDeriv && deriv.size() != n) {
hasDeriv = false;
}
QString wellName = this->getWellName();
if(wellName.isEmpty()) wellName = "UnknownWell";
QString filePath = QDir(outDir).filePath(wellName + "_result_loglog.csv");
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
if(hasDeriv) {
out << "t,p,deriv\n";
} else {
out << "t,p\n";
}
for(int i = 0; i < n; ++i) {
double ti = t[i];
double pi = p[i];
if(ti <= 0) continue;
if(pi <= 0) continue;
out << QString::number(ti, 'g', 12) << ",";
out << QString::number(pi, 'g', 12);
if(hasDeriv) {
double di = deriv[i];
if(di <= 0) di = 1e-12;
out << "," << QString::number(di, 'g', 12);
}
out << "\n";
}
file.close();
return true;
}
bool nmDataWellBase::exportResultPressureToCsv(const QString& outDir)
{
QVector<QVector<double> > result = this->getResultPressure();
if(result.size() < 2) {
return false;
}
const QVector<double>& t = result[0];
const QVector<double>& p = result[1];
int n = t.size();
if(n <= 0) return false;
if(p.size() != n) return false;
QString wellName = this->getWellName();
if(wellName.isEmpty()) wellName = "UnknownWell";
QString filePath = QDir(outDir).filePath(wellName + "_result_pressure.csv");
QFile file(filePath);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
out.setRealNumberPrecision(12);
out << "t,p\n";
for(int i = 0; i < n; ++i) {
double ti = t[i];
double pi = p[i];
if(ti <= 0) continue;
if(pi <= 0) continue;
out << ti << "," << pi << "\n";
}
file.close();
return true;
}