feat: 实现变表皮自动计算,支持单相及油水两相模型

feature/Fix-TimeDependentSkin-20261029
lvjunjie 1 week ago
parent 8a78c5443d
commit 596ca7723e

Binary file not shown.

@ -7709,6 +7709,66 @@ Average pressure in contour: %2 MPa</source>
</context>
<context>
<name>nmWxTimeDependentSkin</name>
<message>
<source>Select a well with valid model results before estimating skin.</source>
<translation>请先选择具有有效模型结果的井,再估算表皮。</translation>
</message>
<message>
<source>Skin estimation requires a single perforation.</source>
<translation>自动估算表皮仅支持单射孔。</translation>
</message>
<message>
<source>Skin estimation does not yet support perforation closing intervals.</source>
<translation>自动估算表皮暂不支持设有射孔封堵区间的模型。</translation>
</message>
<message>
<source>Generate a model with the current inputs and a constant skin before computing skin steps.</source>
<translation>请先使用当前输入和恒定表皮生成模型结果,再计算各段表皮。</translation>
</message>
<message>
<source>The model result does not match this well&apos;s pressure and rate data. Generate the model again.</source>
<translation>模型结果与当前井的压力、流量数据不一致,请重新生成模型。</translation>
</message>
<message>
<source>Observation wells have no controlled rate for skin estimation.</source>
<translation>观察井没有可用于估算表皮的自身控制流量。</translation>
</message>
<message>
<source>Skin estimation requires model and measured pressure curves.</source>
<translation>自动估算表皮需要模型压力曲线和实测压力曲线。</translation>
</message>
<message>
<source>The model requires valid permeability, thickness and skin.</source>
<translation>模型的渗透率、厚度和表皮参数必须有效。</translation>
</message>
<message>
<source>Skin estimation supports single-phase oil, gas, water and oil-water models only.</source>
<translation>自动估算表皮支持单相油、气、水以及油水两相模型。</translation>
</message>
<message>
<source>Set positive reference volume factors and viscosities in the analysis PVT settings.</source>
<translation>请在分析的 PVT 设置中填写有效的参考体积系数和黏度,数值必须大于零。</translation>
</message>
<message>
<source>Oil-water estimation requires finite phase rates with the same flow direction.</source>
<translation>油水两相估算要求各相流量有效,且油、水流动方向一致。</translation>
</message>
<message>
<source>Variable-PVT skin estimation requires finite, positive B and viscosity values on a monotonic pressure table.</source>
<translation>变 PVT 表皮估算要求压力表严格单调,体积系数和黏度均为有效正数。</translation>
</message>
<message>
<source>Cannot read reference PVT values from this analysis.</source>
<translation>无法读取本分析的 PVT 参考值。</translation>
</message>
<message>
<source>The model rate schedule or pressure conversion factor is invalid.</source>
<translation>模型的流量制度或压力换算系数无效。</translation>
</message>
<message>
<source>No skin steps could be estimated. Check pressure data, non-zero rate steps and the gas PVT pressure range.</source>
<translation>没有可估算的表皮分段。请检查压力数据、非零流量段及气体 PVT 压力范围。</translation>
</message>
<message>
<source>Time Dependent Skin</source>
<translation>时间相关表皮系数</translation>

@ -473,6 +473,8 @@ public:
// 获取PEBI求解器需要的pvt数据
nmDataPvtParaForPebi* getPebiPvtPara();
// 读取所属分析窗口当前选用的 PVT 单值,参数名与底层 PVT 接口一致。
bool getAnalysisPvtValues(const QStringList& names, QMap<QString, double>& values) const;
// 获取气单相变化 PVT 求解所需的压力/拟压力转换表。
bool getPebiPseudoPressureTable(std::vector<double>& pressure,
std::vector<double>& pseudoPressure);

@ -6,6 +6,7 @@
#include <QPair>
#include <QPainter>
#include <QPolygon>
#include <QPointer>
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
@ -44,6 +45,7 @@ public:
void onRemoveClicked();
void onSplitStepsClicked();
void onComputeClicked();
void updateComputeState();
void onSkinVsRateClicked();
void onPerforationClosingBtnClicked();
void onPerforationClosingAccepted();
@ -95,6 +97,9 @@ private:
// 更新图表
void updateCharts();
// 检查自动估算表皮所需的基准结果和模型范围。
QString skinComputeUnavailableReason() const;
void showInteractiveChart(); // 显示交互式图表
void restoreOriginalCharts(); // 恢复原始占位符
void initializeChartFromData();
@ -211,6 +216,7 @@ private:
QString m_dSdQDisplayUnit;
// 数据成员
QPointer<nmDataAnalyzeManager> m_pDataManager; // 窗口所属分析,计算时不随当前页签切换
nmDataWellBase* m_currentWell; // 当前井数据
nmDataPerforation m_perforationData; // 射孔段数据
int m_currentPerforationIndex; // 当前射孔段索引

@ -4169,6 +4169,14 @@ nmDataPvtParaForPebi* nmDataAnalyzeManager::getPebiPvtPara()
return m_pebiPvtPara;
}
bool nmDataAnalyzeManager::getAnalysisPvtValues(const QStringList& names, QMap<QString, double>& values) const
{
// 按创建本 Manager 时绑定的分析读取,避免切换页签后串用其他分析的参考物性。
values.clear();
nmDataAnalyzeContextProvider* context = nmDataAnalyzeContext::provider();
return context && m_pOwnerFitting && context->getPvtParaValues(m_pOwnerFitting, names, values);
}
bool nmDataAnalyzeManager::getPebiPseudoPressureTable(std::vector<double>& pressure,
std::vector<double>& pseudoPressure)
{

@ -3,6 +3,7 @@
#include "nmDialogPreviewGuard.h"
#include "nmWxPressFlowChartWidget.h"
#include "nmWxChartWidget.h"
#include "nmPebiResultSnapshot.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "ZxDataEditorDelegate.h"
@ -38,6 +39,274 @@
#include <QWidget>
#include <QCoreApplication>
#include <QtCore/qnumeric.h>
#include <algorithm>
namespace {
const double skinPressureUnitFactor = 1e-3 / (86400.0 * 2.0 * 3.14159265358979323846 * 9.869233e-16 * 1e6);
bool validSkinPressureCurve(const QVector<QVector<double> >& curve)
{
// 插值只接受同一时间基准下有限、严格递增的压力序列。
if(curve.size() < 2 || curve[0].size() < 2 || curve[0].size() != curve[1].size()) return false;
for(int i = 0; i < curve[0].size(); ++i) {
if(!qIsFinite(curve[0][i]) || !qIsFinite(curve[1][i]) ||
(i > 0 && curve[0][i] <= curve[0][i - 1])) return false;
}
return true;
}
bool interpolateSkinPressure(const QVector<QVector<double> >& curve, double time, double& pressure,
bool extrapolate = false)
{
// 压力取样按步骤末端线性插值、沿端点斜率外推;PVT 查值默认仍限制在表内。
const QVector<double>& times = curve[0];
if(!extrapolate && (time < times.first() || time > times.last())) return false;
const int index = static_cast<int>(std::lower_bound(times.constBegin(), times.constEnd(), time) - times.constBegin());
const int right = qBound(1, index, times.size() - 1);
const double fraction = (time - times[right - 1]) / (times[right] - times[right - 1]);
pressure = curve[1][right - 1] + fraction * (curve[1][right] - curve[1][right - 1]);
return qIsFinite(pressure);
}
// 只保存本次估算用的值数据;液体参考物性读取所属分析,曲线和制度沿用模型快照。
struct SkinComputeData
{
QVector<double> rates;
QVector<QVector<double> > pressureMobility;
double pressureFactor;
};
bool skinPvtCurve(const QVector<double>& pressure, const QVector<double>& values,
QVector<QVector<double> >& curve)
{
// PVT 表可按压力升序或降序保存,统一方向后复用压力曲线的校验和插值。
curve.resize(2);
curve[0] = pressure;
curve[1] = values;
if(pressure.size() > 1 && pressure.first() > pressure.last()) {
std::reverse(curve[0].begin(), curve[0].end());
std::reverse(curve[1].begin(), curve[1].end());
}
if(!validSkinPressureCurve(curve)) return false;
for(int i = 0; i < curve[0].size(); ++i) {
if(curve[0][i] <= 0.0 || curve[1][i] <= 0.0) return false;
}
return true;
}
QString prepareSkinCompute(const nmPebiResultWellSnapshot& well,
const nmPebiResultReservoirParameters& reservoir,
const nmPebiResultPvtParameters& pvt, int modelType,
const QMap<QString, double>& referencePvt,
SkinComputeData& data)
{
data = SkinComputeData();
// mD、m、mPa.s、m³/d、MPa 的单位换算;普通压差用参考物性,气拟压力模式保留积分。
data.pressureFactor = reservoir.m_dPermeability * reservoir.m_dThickness / skinPressureUnitFactor;
if(!qIsFinite(reservoir.m_dPermeability) || reservoir.m_dPermeability <= 0.0 ||
!qIsFinite(reservoir.m_dThickness) || reservoir.m_dThickness <= 0.0 ||
!qIsFinite(data.pressureFactor) || data.pressureFactor <= 0.0 || !qIsFinite(well.m_dSkin))
return nmWxTimeDependentSkin::tr("The model requires valid permeability, thickness and skin.");
const QVector<double>* volume = nullptr;
const QVector<double>* viscosity = nullptr;
double constantVolume = 0.0, constantViscosity = 0.0;
NM_PHASE_TYPE phase = PHASE_UNKNOWN;
switch(modelType) {
case SMT_Oil_ConstPvt:
case SMT_Oil_VariablePvt:
phase = PHASE_Oil;
constantVolume = referencePvt.value("Bo");
constantViscosity = referencePvt.value("Miuo");
break;
case SMT_Water_ConstPvt:
case SMT_Water_VariablePvt:
phase = PHASE_Water;
constantVolume = referencePvt.value("Bw");
constantViscosity = referencePvt.value("Miuw");
break;
case SMT_Gas_VariablePvt:
// 气变化 PVT 按不使用拟压力的方式估算表皮,与油、水共用参考单值换算。
phase = PHASE_Gas;
constantVolume = referencePvt.value("Bg");
constantViscosity = referencePvt.value("Miug");
break;
case SMT_Gas_PseudoPressure:
// 气拟压力模式的结果压力为 MPa;Bg 已包含气体标准条件换算。
phase = PHASE_Gas;
volume = &pvt.m_vecBg;
viscosity = &pvt.m_vecMiug;
break;
case SMT_Oil_Water_TwoPhase:
break;
default:
return nmWxTimeDependentSkin::tr("Skin estimation supports single-phase oil, gas, water and oil-water models only.");
}
if(modelType == SMT_Oil_Water_TwoPhase) {
// 按模型读取已冻结的油水制度,只检查数组长度以保证逐段取值有效。
if(well.m_vecOilRates.size() != well.m_vecFlowDurations.size() ||
well.m_vecWaterRates.size() != well.m_vecFlowDurations.size())
return nmWxTimeDependentSkin::tr("The model rate schedule or pressure conversion factor is invalid.");
// 未启用 Perrine:使用模型渗透率和油相参考物性,水量通过 Bw/Bo 换算。
const double bo = referencePvt.value("Bo"), bw = referencePvt.value("Bw");
const double muo = referencePvt.value("Miuo");
if(!qIsFinite(bo) || bo <= 0.0 || !qIsFinite(bw) || bw <= 0.0 ||
!qIsFinite(muo) || muo <= 0.0)
return nmWxTimeDependentSkin::tr("Set positive reference volume factors and viscosities in the analysis PVT settings.");
data.pressureFactor /= bo * muo;
for(int i = 0; i < well.m_vecFlowDurations.size(); ++i) {
const double qo = well.m_vecOilRates[i];
const double qw = well.m_vecWaterRates[i];
// 两相反向流动不适用当前的等效生产流量计算。
if(!qIsFinite(qo) || !qIsFinite(qw) || (qo < 0.0 && qw > 0.0) || (qo > 0.0 && qw < 0.0))
return nmWxTimeDependentSkin::tr("Oil-water estimation requires finite phase rates with the same flow direction.");
data.rates.append(qo + qw * bw / bo);
}
} else {
// 求解输入已将未使用的相补零,这里直接取模型对应相的实际计算流量。
data.rates = phase == PHASE_Oil ? well.m_vecOilRates :
(phase == PHASE_Water ? well.m_vecWaterRates : well.m_vecGasRates);
if(volume) {
QVector<QVector<double> > volumeCurve, viscosityCurve;
if(!skinPvtCurve(pvt.m_vecPressure, *volume, volumeCurve) ||
!skinPvtCurve(pvt.m_vecPressure, *viscosity, viscosityCurve))
return nmWxTimeDependentSkin::tr("Variable-PVT skin estimation requires finite, positive B and viscosity values on a monotonic pressure table.");
// 底层 calPS 依赖全局模式且缺少冻结的配套尺度,这里保留同一快照的常规气体积分。
data.pressureMobility = volumeCurve;
for(int i = 0; i < volumeCurve[0].size(); ++i) {
const double inverse = 1.0 / (volumeCurve[1][i] * viscosityCurve[1][i]);
if(!qIsFinite(inverse) || inverse <= 0.0)
return nmWxTimeDependentSkin::tr("Variable-PVT skin estimation requires finite, positive B and viscosity values on a monotonic pressure table.");
data.pressureMobility[1][i] = inverse;
}
} else {
// 普通压差模式使用本次读取的参考 B、黏度,不按 Pi 重新查表。
if(!qIsFinite(constantVolume) || constantVolume <= 0.0 ||
!qIsFinite(constantViscosity) || constantViscosity <= 0.0)
return nmWxTimeDependentSkin::tr("Set positive reference volume factors and viscosities in the analysis PVT settings.");
data.pressureFactor /= constantVolume * constantViscosity;
}
}
if(!qIsFinite(data.pressureFactor) || data.pressureFactor <= 0.0 || data.rates.isEmpty() ||
data.rates.size() != well.m_vecFlowDurations.size())
return nmWxTimeDependentSkin::tr("The model rate schedule or pressure conversion factor is invalid.");
for(int i = 0; i < data.rates.size(); ++i) {
if(!qIsFinite(data.rates[i]) || !qIsFinite(well.m_vecFlowDurations[i]) || well.m_vecFlowDurations[i] <= 0.0)
return nmWxTimeDependentSkin::tr("The model rate schedule or pressure conversion factor is invalid.");
}
return QString();
}
bool skinPressureDifference(const SkinComputeData& data, double measuredPressure,
double modelPressure, double& difference)
{
difference = modelPressure - measuredPressure;
if(data.pressureMobility.isEmpty()) return qIsFinite(difference);
// 直接对冻结 PVT 的 1/(Bμ) 积分,等价于配套系数消去归一化常数后的拟压力差。
// 分段线性插值积分密度;端点也参与积分,避免仅对累计积分值插值的区间内误差。
const double lower = qMin(measuredPressure, modelPressure);
const double upper = qMax(measuredPressure, modelPressure);
double lowerValue, upperValue;
if(!interpolateSkinPressure(data.pressureMobility, lower, lowerValue) ||
!interpolateSkinPressure(data.pressureMobility, upper, upperValue)) return false;
double integral = 0.0, left = lower, leftValue = lowerValue;
const QVector<double>& pressure = data.pressureMobility[0];
int index = static_cast<int>(std::upper_bound(pressure.constBegin(), pressure.constEnd(), lower) - pressure.constBegin());
for(; index < pressure.size() && pressure[index] < upper; ++index) {
const double value = data.pressureMobility[1][index];
integral += (pressure[index] - left) * (leftValue + value) * 0.5;
left = pressure[index];
leftValue = value;
}
integral += (upper - left) * (leftValue + upperValue) * 0.5;
difference = modelPressure >= measuredPressure ? integral : -integral;
return qIsFinite(difference);
}
int estimateSkinSegments(const nmPebiResultWellSnapshot& well,
const SkinComputeData& data,
const QVector<QVector<double> >& measured,
QVector<FlowSegmentData>& segments)
{
const QVector<QVector<double> >& model = well.m_oCurves.m_vecResultPressure;
const QVector<double>& rates = data.rates;
if(!validSkinPressureCurve(model) || !validSkinPressureCurve(measured) ||
rates.size() != well.m_vecFlowDurations.size() || rates.isEmpty()) return 0;
QVector<double> ends;
double end = 0.0;
for(int i = 0; i < rates.size(); ++i) {
const double duration = well.m_vecFlowDurations[i];
if(!qIsFinite(duration) || duration <= 0.0 || !qIsFinite(rates[i])) return 0;
end += duration;
if(!qIsFinite(end)) return 0;
ends.append(end);
}
int computedCount = 0;
QVector<int> unmatchedSegments;
for(int i = 0; i < segments.size(); ++i) {
const double start = segments[i].segmentStart.getValue().toDouble();
const double finish = segments[i].segmentEnd.getValue().toDouble();
if(!qIsFinite(start) || !qIsFinite(finish) || finish <= start) {
continue;
}
// 按 Saphir 取起点落在当前表皮段内的最后一个非零流量段,并在该流量段末端取样。
double sampleTime = 0.0;
double rate = 0.0;
int rateIndex = -1;
for(int j = 0; j < rates.size(); ++j) {
const double rateStart = j == 0 ? 0.0 : ends[j - 1];
if(rateStart > finish - 1e-5) break;
if(rateStart >= start - 1e-5 && rates[j] != 0.0) {
sampleTime = ends[j];
rate = rates[j];
rateIndex = j;
}
}
// 没有符合条件的非零流量段时删除该分界;先完成原始分段取样,避免合并改变本次计算。
if(rateIndex < 0) {
unmatchedSegments.append(i);
continue;
}
double modelPressure = 0.0, measuredPressure = 0.0;
if(!interpolateSkinPressure(model, sampleTime, modelPressure, true) ||
!interpolateSkinPressure(measured, sampleTime, measuredPressure, true)) {
continue;
}
double pressureDifference = 0.0;
if(!skinPressureDifference(data, measuredPressure, modelPressure, pressureDifference)) {
continue;
}
// 修正始终相对目标井的冻结表皮,重复点击或换井不会累加、串用基准值。
const double skin = well.m_dSkin + data.pressureFactor * pressureDifference / rate;
if(!qIsFinite(skin)) {
continue;
}
segments[i].skinValue.setValue(qMax(-5.0, skin));
// 与 Compute 的单点校正一致,只估计总表皮,不从一个压力差同时反演 dS/dQ。
segments[i].dSdQ.setValue(0.0);
++computedCount;
}
// 从后往前移除无匹配段,前段延伸至被删段末端,保留其已算出的表皮值。
for(int i = unmatchedSegments.size() - 1; i >= 0; --i) {
const int index = unmatchedSegments[i];
if(index > 0) segments[index - 1].segmentEnd = segments[index].segmentEnd;
segments.remove(index);
}
return computedCount;
}
}
// 静态成员变量定义
nmWxTimeDependentSkin* nmWxTimeDependentSkin::s_instance = nullptr;
@ -76,6 +345,7 @@ void nmWxTimeDependentSkin::showForCurrentWell(nmDataWellBase* pSpecificWellData
nmWxTimeDependentSkin::nmWxTimeDependentSkin(nmDataWellBase* pWellData, QWidget *parent)
: iDlgBase(parent)
, m_pDataManager(nmDataAnalyzeManager::getCurrentInstance())
, m_pChartWidget(nullptr)
, m_pOriginalPressureChart(nullptr)
, m_pOriginalFlowRateChart(nullptr)
@ -217,6 +487,11 @@ nmWxTimeDependentSkin::nmWxTimeDependentSkin(nmDataWellBase* pWellData, QWidget
connect(m_pShowDatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(onShowDatesToggled(bool)));
connect(m_pComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onPerforationChanged(int)));
// 新结果发布或失效后同步按钮状态;点击时仍重新检查,避免使用旧分析结果。
nmDataAnalyzeManager* manager = m_pDataManager.data();
if(manager) connect(manager, SIGNAL(sigPebiResultSnapshotChanged()), this, SLOT(updateComputeState()));
updateComputeState();
}
nmWxTimeDependentSkin::~nmWxTimeDependentSkin()
@ -275,8 +550,6 @@ void nmWxTimeDependentSkin::initUI()
createButton(m_pRemoveBtn, "nmTimeDepSkin3", tr("Remove"));
createButton(m_pSplitStepsBtn, "nmTimeDepSkin4", tr("Split Steps"));
createButton(m_pComputeBtn, "nmTimeDepSkin5", tr("Compute"));
// 计算功能尚未实现,隐藏图标和文字容器,让射孔封堵前移到该位置。
m_pComputeBtn->parentWidget()->hide();
createButton(m_pPerforationClosingBtn, "nmTimeDepSkin6", tr("Perforation Closing"));
m_pToolbarLayout->addStretch();
@ -576,8 +849,6 @@ void nmWxTimeDependentSkin::updateTableFromFlowSegments()
const QVector<FlowSegmentData>& segments = m_perforationData.getFlowSegments();
if(segments.isEmpty()) return;
const bool enableRateDep = m_perforationData.isRateDependentEnabled();
const int requiredCols = enableRateDep ? 4 : 3;
@ -897,9 +1168,107 @@ void nmWxTimeDependentSkin::onSplitStepsClicked()
updateTableFromFlowSegments();
}
QString nmWxTimeDependentSkin::skinComputeUnavailableReason() const
{
// 多井按井 UUID 查找各自的冻结结果,观察井没有可用于表皮反算的自身流量。
nmDataAnalyzeManager* manager = m_pDataManager.data();
if(!manager || !m_currentWell)
return tr("Select a well with valid model results before estimating skin.");
if(m_currentWell->getPerforationCount() != 1)
return tr("Skin estimation requires a single perforation.");
if(!m_perforationData.getTimeValues().isEmpty())
return tr("Skin estimation does not yet support perforation closing intervals.");
if(!manager->isPebiResultSnapshotCurrent())
return tr("Generate a model with the current inputs and a constant skin before computing skin steps.");
QSharedPointer<const nmPebiResultSnapshot> snapshot = manager->getPebiResultSnapshot();
const nmPebiResultWellSnapshot* well = snapshot->findWell(m_currentWell->getWellInstanceId());
if(!well || !well->m_bHasSkin ||
well->m_sGaugeInputSha1.isEmpty() || well->m_sGaugeInputSha1 != m_currentWell->calculateGaugeInputSha1())
return tr("The model result does not match this well's pressure and rate data. Generate the model again.");
if(well->m_eWellMode != NM_CaseWell_RateControlled)
return tr("Observation wells have no controlled rate for skin estimation.");
// 直井、压裂井和多段压裂井共用模型压力与井总流量,估算整井等效表皮。
if(!validSkinPressureCurve(well->m_oCurves.m_vecResultPressure) || m_currentWell->getPressurePoints().size() < 2)
return tr("Skin estimation requires model and measured pressure curves.");
return QString();
}
void nmWxTimeDependentSkin::updateComputeState()
{
// 仅更新计算按钮的可用状态,切换页面不改变原有模型设置。
const QString reason = skinComputeUnavailableReason();
m_pComputeBtn->setEnabled(reason.isEmpty() && !m_pChartWidget);
}
void nmWxTimeDependentSkin::onComputeClicked()
{
const QString reason = skinComputeUnavailableReason();
if(!reason.isEmpty()) {
updateComputeState();
QMessageBox::information(this, tr("Compute"), reason);
return;
}
QSharedPointer<const nmPebiResultSnapshot> snapshot =
m_pDataManager->getPebiResultSnapshot();
const nmPebiResultWellSnapshot* well = snapshot->findWell(m_currentWell->getWellInstanceId());
const nmPebiResultPvtParameters& pvt = snapshot->getPvtParameters();
const int modelType = snapshot->getSolverSettings().m_nSolverModelType;
// 每次点击从所属分析重新读取参考单值,不回退到旧快照或默认物性。
QStringList names;
if(modelType == SMT_Oil_ConstPvt || modelType == SMT_Oil_VariablePvt || modelType == SMT_Oil_Water_TwoPhase)
names << "Bo" << "Miuo";
if(modelType == SMT_Water_ConstPvt || modelType == SMT_Water_VariablePvt)
names << "Bw" << "Miuw";
else if(modelType == SMT_Oil_Water_TwoPhase)
names << "Bw";
if(modelType == SMT_Gas_VariablePvt)
names << "Bg" << "Miug";
QMap<QString, double> referencePvt;
if(!names.isEmpty() && !m_pDataManager->getAnalysisPvtValues(names, referencePvt)) {
QMessageBox::information(this, tr("Compute"), tr("Cannot read reference PVT values from this analysis."));
return;
}
SkinComputeData data;
const QString preparationError = prepareSkinCompute(*well, snapshot->getReservoirParameters(), pvt, modelType, referencePvt, data);
if(!preparationError.isEmpty()) {
QMessageBox::information(this, tr("Compute"), preparationError);
return;
}
QVector<QVector<double> > measured(2);
const QVector<QPointF> points = m_currentWell->getPressurePoints();
for(int i = 0; i < points.size(); ++i) {
measured[0].append(points[i].x());
measured[1].append(points[i].y());
}
// 只更新本窗口副本;计算后移除无匹配流量段的分界,取消仍可放弃本次估算。
QVector<FlowSegmentData> segments = m_perforationData.getFlowSegments();
const int originalSegmentCount = segments.size();
const int computedCount = estimateSkinSegments(*well, data, measured, segments);
if(computedCount == 0 && segments.size() == originalSegmentCount) {
QMessageBox::information(this, tr("Compute"),
tr("No skin steps could be estimated. Check pressure data, non-zero rate steps and the gas PVT pressure range."));
return;
}
// 分段删除后重新定位选中项,避免旧索引指向其他行或越界;空结果由表格刷新清除旧行。
int selectedIndex = 0;
if(segments.size() != originalSegmentCount && !segments.isEmpty()) {
for(int i = 0; i < segments.size(); ++i) {
if(segments[i].isSelected) {
selectedIndex = i;
break;
}
}
for(int i = 0; i < segments.size(); ++i) segments[i].isSelected = i == selectedIndex;
}
m_perforationData.setFlowSegments(segments);
if(segments.size() != originalSegmentCount && !segments.isEmpty())
m_perforationData.setSelectedSegmentIndex(selectedIndex);
// 原来的表皮/流量拟合线已不对应新估算值,下次进入第二页时重新拟合。
m_perforationData.setLinePositions(-999.0, -999.0, -999.0, -999.0);
updateCharts();
updateTableFromFlowSegments();
}
void nmWxTimeDependentSkin::onPerforationClosingBtnClicked()
@ -918,6 +1287,7 @@ void nmWxTimeDependentSkin::onPerforationClosingAccepted()
m_perforationData.setTimeValues(data.getTimeValues());
m_perforationData.setTimeStates(data.getTimeStates());
m_perforationData.setBaseTime(data.getBaseTime());
updateComputeState();
}
void nmWxTimeDependentSkin::onSkinVsRateClicked()
@ -1207,9 +1577,10 @@ void nmWxTimeDependentSkin::restoreToolbarState()
m_pSplitStepsBtn->parentWidget()->setVisible(true);
}
// 切回默认界面时仍保持计算入口隐藏。
// 计算入口仅在默认的表皮分段页面显示。
if(m_pComputeBtn && m_pComputeBtn->parentWidget()) {
m_pComputeBtn->parentWidget()->setVisible(false);
m_pComputeBtn->parentWidget()->setVisible(true);
updateComputeState();
}
if(m_pPerforationClosingBtn && m_pPerforationClosingBtn->parentWidget()) {

Loading…
Cancel
Save