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/nmDataDiagnostic.cpp

949 lines
34 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmDataDiagnostic.h"
#include "nmDataWellBase.h"
#include "nmDataReservoir.h"
#include <QDebug>
#include "qmath.h"
#include <QVector>
#include <algorithm>
#ifdef Q_OS_WIN
#include <windows.h>
#define DEBUG_OUT(msg) OutputDebugStringA(QString("%1\n").arg(msg).toLocal8Bit().data())
#endif
nmDataDiagnostic::nmDataDiagnostic()
{
m_diagnosticSkin = nmDataAttribute("Skin", 0.0, "");
m_diagnosticWellboreStorage = 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_diagnosticPermeability = nmDataAttribute("Permeability", 0.025, "md", UNIT_TYPE_PERMEABILITY, QStringList(), QStringList() << "md" << "Darcy" << "m^2" << "cm^2" << "um^2");
m_diagnosticTransmissibility = nmDataAttribute("Transmissibility", 1000.0, "md.m", UNIT_TYPE_CONDUCTIVITY, QStringList(), QStringList()<< "md.ft" << "md.m" << "m^3");
}
nmDataDiagnostic::~nmDataDiagnostic() {
}
// 序列化 nmDataDiagnostic 为 RapidJSON Value
rapidjson::Value nmDataDiagnostic::ToJsonValue(rapidjson::Document::AllocatorType& allocator) const
{
// 创建一个 RapidJSON 对象类型的值
rapidjson::Value diagnosticObject(rapidjson::kObjectType);
// 序列化 nmDataAttribute 类型的成员
// 调用 nmDataAttribute 自身的 ToJsonValue 方法进行递归序列化
diagnosticObject.AddMember("DiagnosticSkin", m_diagnosticSkin.ToJsonValue(allocator), allocator);
diagnosticObject.AddMember("DiagnosticWellboreStorage", m_diagnosticWellboreStorage.ToJsonValue(allocator), allocator);
diagnosticObject.AddMember("DiagnosticPermeability", m_diagnosticPermeability.ToJsonValue(allocator), allocator);
diagnosticObject.AddMember("DiagnosticTransmissibility", m_diagnosticTransmissibility.ToJsonValue(allocator), allocator);
return diagnosticObject; // 返回序列化后的 RapidJSON Value
}
// 从 RapidJSON Value 反序列化数据到 nmDataDiagnostic
void nmDataDiagnostic::FromJsonValue(const rapidjson::Value& jsonValue)
{
// 反序列化 nmDataAttribute 类型的成员
// 调用 nmDataAttribute 自身的 FromJsonValue 方法进行递归反序列化
if (jsonValue.HasMember("DiagnosticSkin") && jsonValue["DiagnosticSkin"].IsObject()) {
m_diagnosticSkin.FromJsonValue(jsonValue["DiagnosticSkin"]);
}
if (jsonValue.HasMember("DiagnosticWellboreStorage") && jsonValue["DiagnosticWellboreStorage"].IsObject()) {
m_diagnosticWellboreStorage.FromJsonValue(jsonValue["DiagnosticWellboreStorage"]);
}
if (jsonValue.HasMember("DiagnosticPermeability") && jsonValue["DiagnosticPermeability"].IsObject()) {
m_diagnosticPermeability.FromJsonValue(jsonValue["DiagnosticPermeability"]);
}
if (jsonValue.HasMember("DiagnosticTransmissibility") && jsonValue["DiagnosticTransmissibility"].IsObject()) {
m_diagnosticTransmissibility.FromJsonValue(jsonValue["DiagnosticTransmissibility"]);
}
}
// 获取诊断结果参数
nmDataAttribute& nmDataDiagnostic::getDiagnosticPermeability() { return m_diagnosticPermeability; }
nmDataAttribute& nmDataDiagnostic::getDiagnosticSkin() { return m_diagnosticSkin; }
nmDataAttribute& nmDataDiagnostic::getDiagnosticWellboreStorage() { return m_diagnosticWellboreStorage; }
nmDataAttribute& nmDataDiagnostic::getDiagnosticTransmissibility() { return m_diagnosticTransmissibility; }
void nmDataDiagnostic::resetFromDiagnostic(const QVector<QVector<double>>& rawData,
nmDataWellBase* pWell,
nmDataReservoir* pReservoir)
{
if (!pWell || !pReservoir) {
DEBUG_OUT("Invalid well or reservoir data");
return;
}
if (rawData.size() < 2 || rawData[0].isEmpty() || rawData[1].isEmpty()) {
DEBUG_OUT("Invalid rawData format");
return;
}
DEBUG_OUT("=== Starting Intelligent Diagnostic Analysis ===");
// 调用各个参数的诊断计算方法
calculateWellboreStorage(rawData, pWell, pReservoir);
calculateTransmissibility(rawData, pWell, pReservoir); // 先计算 kh
calculatePermeability(pWell, pReservoir); // 再基于 kh 计算 k
//calculateSkin(rawData, pWell, pReservoir);
DEBUG_OUT("=== Diagnostic Analysis Complete ===");
}
void nmDataDiagnostic::calculateWellboreStorage(const QVector<QVector<double>>& rawData,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Intelligent Wellbore Storage Analysis ===");
const QVector<double>& timeData = rawData[0]; // 时间数据 (hr)
const QVector<double>& pressureDropData = rawData[1]; // 压力差数据 (MPa)
// 确保有足够的数据点
int availablePoints = qMin(timeData.size(), pressureDropData.size());
if (availablePoints < 3) {
DEBUG_OUT("Not enough data points for wellbore storage analysis (need at least 3 points)");
return;
}
// 只分析早期数据点前20%或前30个点取较小值
int earlyPointsToUse = qMin(qMax(5, static_cast<int>(availablePoints * 0.2)), 30);
earlyPointsToUse = qMin(earlyPointsToUse, availablePoints);
DEBUG_OUT(QString("Total points: %1, analyzing early %2 points (%3%) for flow regime identification")
.arg(availablePoints).arg(earlyPointsToUse).arg(100.0 * earlyPointsToUse / availablePoints, 0, 'f', 1));
// 获取储层和井参数
double viscosity = pReservoir->getMiuo().getValue().toDouble(); // 粘度
double bo = pReservoir->getBo().getValue().toDouble(); // 体积系数
DEBUG_OUT(QString("Reservoir parameters: viscosity=%1, Bo=%2").arg(viscosity).arg(bo));
// 智能识别流动阶段分析wellbore storage和径向流两种机制
FlowRegimeAnalysis wellboreStorageAnalysis = analyzeFlowRegime(timeData, pressureDropData,
earlyPointsToUse, 1.0,
"Wellbore Storage",
pWell, pReservoir);
FlowRegimeAnalysis radialFlowAnalysis = analyzeFlowRegime(timeData, pressureDropData,
earlyPointsToUse, 0.5,
"Radial Flow",
pWell, pReservoir);
// 智能选择最佳结果 - 改进的选择逻辑
FlowRegimeAnalysis* selectedAnalysis = nullptr;
QString selectionReason = "";
if (wellboreStorageAnalysis.isValid && radialFlowAnalysis.isValid) {
// 检查每个斜率更接近哪个理论值
double wbSlope = wellboreStorageAnalysis.slope;
double rfSlope = radialFlowAnalysis.slope;
// 计算斜率到两个理论值的距离
double wbDistanceTo1 = qAbs(wbSlope - 1.0);
double wbDistanceTo05 = qAbs(wbSlope - 0.5);
double rfDistanceTo1 = qAbs(rfSlope - 1.0);
double rfDistanceTo05 = qAbs(rfSlope - 0.5);
// 判断wellbore storage斜率更接近哪个理论值
bool wbCloserTo1 = wbDistanceTo1 < wbDistanceTo05;
// 判断radial flow斜率更接近哪个理论值
bool rfCloserTo05 = rfDistanceTo05 < rfDistanceTo1;
DEBUG_OUT(QString("=== Selection Analysis ==="));
DEBUG_OUT(QString("WB slope %1: distance to 1.0=%2, to 0.5=%3, closer to %4")
.arg(wbSlope, 0, 'f', 4).arg(wbDistanceTo1, 0, 'f', 4).arg(wbDistanceTo05, 0, 'f', 4)
.arg(wbCloserTo1 ? "1.0" : "0.5"));
DEBUG_OUT(QString("RF slope %1: distance to 1.0=%2, to 0.5=%3, closer to %4")
.arg(rfSlope, 0, 'f', 4).arg(rfDistanceTo1, 0, 'f', 4).arg(rfDistanceTo05, 0, 'f', 4)
.arg(rfCloserTo05 ? "0.5" : "1.0"));
// 决策逻辑:
if (wbCloserTo1 && wellboreStorageAnalysis.avgTime < 0.05) {
// 如果wellbore storage斜率确实更接近1.0且时间很早优先选择wellbore storage
selectedAnalysis = &wellboreStorageAnalysis;
selectionReason = QString("Wellbore storage slope (%1) closer to 1.0 and early time (%2hr)")
.arg(wbSlope, 0, 'f', 4).arg(wellboreStorageAnalysis.avgTime, 0, 'f', 6);
} else if (rfCloserTo05) {
// 如果radial flow斜率确实更接近0.5选择radial flow
selectedAnalysis = &radialFlowAnalysis;
selectionReason = QString("Radial flow slope (%1) closer to 0.5, difference=%2")
.arg(rfSlope, 0, 'f', 4).arg(radialFlowAnalysis.slopeDifference, 0, 'f', 4);
} else if (wbCloserTo1) {
// 如果wellbore storage斜率更接近1.0选择wellbore storage
selectedAnalysis = &wellboreStorageAnalysis;
selectionReason = QString("Wellbore storage slope (%1) closer to 1.0, difference=%2")
.arg(wbSlope, 0, 'f', 4).arg(wellboreStorageAnalysis.slopeDifference, 0, 'f', 4);
} else {
// 兜底:选择斜率差异更小的
if (wellboreStorageAnalysis.slopeDifference < radialFlowAnalysis.slopeDifference) {
selectedAnalysis = &wellboreStorageAnalysis;
selectionReason = QString("Fallback: WB slope difference (%1) < RF difference (%2)")
.arg(wellboreStorageAnalysis.slopeDifference, 0, 'f', 4)
.arg(radialFlowAnalysis.slopeDifference, 0, 'f', 4);
} else {
selectedAnalysis = &radialFlowAnalysis;
selectionReason = QString("Fallback: RF slope difference (%1) < WB difference (%2)")
.arg(radialFlowAnalysis.slopeDifference, 0, 'f', 4)
.arg(wellboreStorageAnalysis.slopeDifference, 0, 'f', 4);
}
}
} else if (wellboreStorageAnalysis.isValid) {
selectedAnalysis = &wellboreStorageAnalysis;
selectionReason = "Only wellbore storage analysis is valid";
} else if (radialFlowAnalysis.isValid) {
selectedAnalysis = &radialFlowAnalysis;
selectionReason = "Only radial flow analysis is valid";
}
// 设置诊断结果
if (selectedAnalysis && selectedAnalysis->result > 0) {
m_diagnosticWellboreStorage.setValue(selectedAnalysis->result);
DEBUG_OUT(QString("=== Final Result ==="));
DEBUG_OUT(QString("Selected method: %1").arg(selectedAnalysis->description));
DEBUG_OUT(QString("Selection reason: %1").arg(selectionReason));
DEBUG_OUT(QString("Final wellbore storage: %1").arg(selectedAnalysis->result));
DEBUG_OUT(QString("Based on points [%1,%2] at times %3hr and %4hr")
.arg(selectedAnalysis->pointPair.first).arg(selectedAnalysis->pointPair.second)
.arg(timeData[selectedAnalysis->pointPair.first])
.arg(timeData[selectedAnalysis->pointPair.second]));
} else {
DEBUG_OUT("Unable to calculate wellbore storage - no valid flow regime identified");
}
}
void nmDataDiagnostic::calculateTransmissibility(const QVector<QVector<double>>& rawData,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Transmissibility (kh) Analysis ===");
// 检查数据格式需要3个向量 [时间, 压力, 导数]
if (rawData.size() < 3 || rawData[0].isEmpty() || rawData[1].isEmpty() || rawData[2].isEmpty()) {
DEBUG_OUT("Invalid rawData format: need [time, pressure, derivative] vectors");
return;
}
const QVector<double>& timeData = rawData[0]; // 时间数据 (hr)
const QVector<double>& derivativeData = rawData[2]; // 压力导数数据 (MPa)
// 确保有足够的数据点
int availablePoints = timeData.size();
if (availablePoints < 10) {
DEBUG_OUT("Not enough data points for transmissibility analysis (need at least 10 points)");
return;
}
DEBUG_OUT("Trying multiple analysis methods to find best transmissibility estimate...");
// 方法1标准径向流平台分析
FlowRegimeAnalysis standardRadialFlow = analyzeStandardRadialFlow(
timeData, derivativeData, availablePoints, pWell, pReservoir);
// 方法2晚期分析使用极小导数值
FlowRegimeAnalysis saphirStyleAnalysis = analyzeSaphirStyleLateTime(
timeData, derivativeData, availablePoints, pWell, pReservoir);
// 方法3早中期稳定段分析
FlowRegimeAnalysis earlyStableAnalysis = analyzeEarlyStableFlow(
timeData, derivativeData, availablePoints, pWell, pReservoir);
// 智能选择最佳方法
FlowRegimeAnalysis* selectedAnalysis = selectBestTransmissibilityAnalysis(
standardRadialFlow, saphirStyleAnalysis, earlyStableAnalysis);
if (selectedAnalysis && selectedAnalysis->isValid && selectedAnalysis->result > 0) {
double khValue = selectedAnalysis->result;
m_diagnosticTransmissibility.setValue(khValue);
DEBUG_OUT(QString("=== Transmissibility Analysis Result ==="));
DEBUG_OUT(QString("Selected method: %1").arg(selectedAnalysis->description));
DEBUG_OUT(QString("Transmissibility (kh): %1 md*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Analysis time: %1 hr").arg(selectedAnalysis->avgTime, 0, 'f', 4));
DEBUG_OUT(QString("Derivative value used: %1 MPa").arg(selectedAnalysis->avgPressure, 0, 'f', 6));
} else {
DEBUG_OUT("All transmissibility analysis methods failed");
}
}
void nmDataDiagnostic::calculatePermeability(nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Permeability (k) Analysis ===");
// 获取已计算的 kh 值
double khValue = m_diagnosticTransmissibility.getValue().toDouble();
if (khValue <= 0) {
DEBUG_OUT("Invalid transmissibility value - cannot calculate permeability");
DEBUG_OUT("Please ensure transmissibility is calculated first");
return;
}
// 获取地层厚度
double thickness = pReservoir->getThickness().getValue().toDouble(); // 地层厚度 (m)
if (thickness <= 0) {
DEBUG_OUT("Invalid reservoir thickness for permeability calculation");
return;
}
// 计算渗透率 k = kh / h
double kValue = khValue / thickness;
m_diagnosticPermeability.setValue(kValue);
DEBUG_OUT(QString("=== Permeability Analysis Result ==="));
DEBUG_OUT(QString("Transmissibility (kh): %1 md*m").arg(khValue, 0, 'f', 6));
DEBUG_OUT(QString("Reservoir thickness (h): %1 m").arg(thickness, 0, 'f', 2));
DEBUG_OUT(QString("Permeability (k): %1 md").arg(kValue, 0, 'f', 6));
}
//void nmDataDiagnostic::calculateSkin(const QVector<QVector<double>>& rawData,
// nmDataWellBase* pWell, nmDataReservoir* pReservoir)
//{
//
//}
nmDataDiagnostic::FlowRegimeAnalysis nmDataDiagnostic::analyzeFlowRegime(
const QVector<double>& timeData,
const QVector<double>& pressureDropData,
int pointCount, double targetSlope,
const QString& regimeName,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
FlowRegimeAnalysis analysis;
analysis.pointPair = findClosestSlopeToValue(timeData, pressureDropData, pointCount, targetSlope);
if (analysis.pointPair.first >= 0 && analysis.pointPair.second >= 0) {
int i = analysis.pointPair.first;
int j = analysis.pointPair.second;
double t1 = timeData[i];
double t2 = timeData[j];
double deltaP1 = pressureDropData[i];
double deltaP2 = pressureDropData[j];
analysis.slope = calculateSlopeBetweenPoints(t1, deltaP1, t2, deltaP2);
analysis.slopeDifference = qAbs(analysis.slope - targetSlope);
analysis.avgTime = (t1 + t2) / 2.0;
analysis.avgPressure = (deltaP1 + deltaP2) / 2.0;
// 获取流量
double flowRate = pWell->getFlowRateAtTime(analysis.avgTime);
if (flowRate > 0) {
// 获取储层参数
double viscosity = pReservoir->getMiuo().getValue().toDouble();
double bo = pReservoir->getBo().getValue().toDouble();
// 使用公式计算wellbore storage: c = qb*μo*Bo*t/(24*ΔP)
analysis.result = (flowRate * viscosity * bo * analysis.avgTime) / (24.0 * analysis.avgPressure);
analysis.isValid = true;
analysis.description = QString("%1 (slope=%2, target=%3, diff=%4)")
.arg(regimeName).arg(analysis.slope, 0, 'f', 4)
.arg(targetSlope, 0, 'f', 1).arg(analysis.slopeDifference, 0, 'f', 4);
DEBUG_OUT(QString("=== %1 Analysis ===").arg(regimeName));
DEBUG_OUT(QString("Best points: [%1,%2] at times %3hr - %4hr")
.arg(i).arg(j).arg(t1, 0, 'f', 6).arg(t2, 0, 'f', 6));
DEBUG_OUT(QString("Slope: %1 (target: %2, difference: %3)")
.arg(analysis.slope, 0, 'f', 4).arg(targetSlope, 0, 'f', 1)
.arg(analysis.slopeDifference, 0, 'f', 4));
DEBUG_OUT(QString("Average time: %1hr, Average derTaP: %2").arg(analysis.avgTime, 0, 'f', 6).arg(analysis.avgPressure, 0, 'f', 6));
DEBUG_OUT(QString("Flow rate: %1, Result: %2").arg(flowRate).arg(analysis.result, 0, 'f', 6));
}
}
return analysis;
}
QPair<int, int> nmDataDiagnostic::findClosestSlopeToValue(const QVector<double>& timeData,
const QVector<double>& pressureDropData,
int pointCount, double targetSlope)
{
double bestSlope = -1.0;
double minDifference = 999999.0;
QPair<int, int> bestPair(-1, -1);
// 遍历所有可能的点对,寻找斜率最接近目标值的
for (int i = 0; i < pointCount - 1; ++i) {
for (int j = i + 1; j < pointCount; ++j) {
double t1 = timeData[i];
double t2 = timeData[j];
double deltaP1 = pressureDropData[i];
double deltaP2 = pressureDropData[j];
// 确保数据有效
if (t1 <= 0 || t2 <= 0 || deltaP1 <= 0 || deltaP2 <= 0 || t2 <= t1) {
continue;
}
// 计算斜率
double slope = calculateSlopeBetweenPoints(t1, deltaP1, t2, deltaP2);
// 计算与目标值的差异
double difference = qAbs(slope - targetSlope);
// 如果这个斜率更接近目标值,更新最佳结果
if (difference < minDifference) {
minDifference = difference;
bestSlope = slope;
bestPair.first = i;
bestPair.second = j;
}
}
}
return bestPair;
}
double nmDataDiagnostic::calculateSlopeBetweenPoints(double t1, double deltaP1, double t2, double deltaP2)
{
if (t1 <= 0 || t2 <= 0 || deltaP1 <= 0 || deltaP2 <= 0 || t2 <= t1) {
return 0.0;
}
// 计算双对数斜率: slope = log(ΔP2/ΔP1) / log(t2/t1)
double logTimeRatio = log10(t2 / t1);
double logPressureRatio = log10(deltaP2 / deltaP1);
if (qAbs(logTimeRatio) < 1e-10) { // 避免除以零
return 0.0;
}
double slope = logPressureRatio / logTimeRatio;
return slope;
}
double nmDataDiagnostic::interpolatePressureAtTime(const QVector<double>& timeData,
const QVector<double>& pressureDropData,
double targetTime)
{
for (int i = 0; i < timeData.size() - 1; ++i) {
if (targetTime >= timeData[i] && targetTime <= timeData[i + 1]) {
// 线性插值
double ratio = (targetTime - timeData[i]) / (timeData[i + 1] - timeData[i]);
return pressureDropData[i] + ratio * (pressureDropData[i + 1] - pressureDropData[i]);
}
}
return -1.0; // 未找到合适的区间
}
nmDataDiagnostic::FlowRegimeAnalysis nmDataDiagnostic::analyzeRadialFlowForKh(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int startPoint, int pointCount,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
FlowRegimeAnalysis analysis;
analysis.isValid = false;
DEBUG_OUT(QString("Starting radial flow analysis from point %1, analyzing %2 points")
.arg(startPoint).arg(pointCount));
// 寻找导数平台段
QPair<int, int> bestPlatform = findDerivativePlatform(timeData, derivativeData,
startPoint, pointCount);
if (bestPlatform.first >= 0 && bestPlatform.second >= 0) {
int platformLength = bestPlatform.second - bestPlatform.first + 1;
// 计算整个平台段的平均值,而不是只使用两个端点
double totalDeriv = 0.0;
double totalTime = 0.0;
for (int i = bestPlatform.first; i <= bestPlatform.second; ++i) {
totalDeriv += derivativeData[i];
totalTime += timeData[i];
}
analysis.avgPressure = totalDeriv / platformLength; // 平台段平均值
analysis.avgTime = totalTime / platformLength; // 平台段时间平均值
analysis.pointPair = bestPlatform;
// 获取对应时间的流量
double flowRate = pWell->getFlowRateAtTime(analysis.avgTime);
double bo = pReservoir->getBo().getValue().toDouble();
double miuo = pReservoir->getMiuo().getValue().toDouble();
DEBUG_OUT(QString("Radial flow platform found: points [%1-%2], length=%3 points")
.arg(bestPlatform.first).arg(bestPlatform.second).arg(platformLength));
DEBUG_OUT(QString("Time range: %1hr to %2hr")
.arg(timeData[bestPlatform.first], 0, 'f', 4)
.arg(timeData[bestPlatform.second], 0, 'f', 4));
DEBUG_OUT(QString("Platform average derivative: %1 (from %2 points)")
.arg(analysis.avgPressure, 0, 'f', 6).arg(platformLength));
DEBUG_OUT(QString("Analysis time: %1 hr, flow rate: %2")
.arg(analysis.avgTime, 0, 'f', 4).arg(flowRate, 0, 'f', 4));
if (flowRate > 0 && analysis.avgPressure > 0) {
// 公式: kh = kh = (70.6 * q * B * μ) / (ΔP'_{platform})
double kh = flowRate * bo * miuo * 70.6 / analysis.avgPressure;
analysis.result = kh;
analysis.isValid = true;
// 计算平台段的整体斜率(使用首尾点)
double t1 = timeData[bestPlatform.first];
double t2 = timeData[bestPlatform.second];
double deriv1 = derivativeData[bestPlatform.first];
double deriv2 = derivativeData[bestPlatform.second];
analysis.slope = calculateSlopeBetweenPoints(t1, deriv1, t2, deriv2);
analysis.slopeDifference = qAbs(analysis.slope - 0.0);
analysis.description = QString("Radial Flow Platform (length=%1, DertaP'=%2, kh=%3)")
.arg(platformLength)
.arg(analysis.avgPressure, 0, 'f', 6)
.arg(kh, 0, 'f', 6);
DEBUG_OUT(QString("Platform overall slope: %1 (over %2 hours)")
.arg(analysis.slope, 0, 'f', 4)
.arg(t2 - t1, 0, 'f', 2));
DEBUG_OUT(QString("Calculated kh: %1").arg(kh, 0, 'f', 6));
}
}
return analysis;
}
QPair<int, int> nmDataDiagnostic::findDerivativePlatform(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int startPoint, int pointCount)
{
QPair<int, int> bestSegment(-1, -1);
if (startPoint < 0 || pointCount <= 0) {
DEBUG_OUT("Not enough points for platform identification");
return bestSegment;
}
int endPoint = qMin(startPoint + pointCount, qMin(timeData.size(), derivativeData.size()));
if (endPoint - startPoint < 3) {
DEBUG_OUT("Not enough points for platform identification");
return bestSegment;
}
DEBUG_OUT(QString("Searching for longest stable platform in points [%1-%2]").arg(startPoint).arg(endPoint-1));
// 首先计算整个分析区间的导数平均值,作为平台参考值
double totalDeriv = 0.0;
int count = 0;
for (int i = startPoint; i < endPoint; ++i) {
totalDeriv += derivativeData[i];
count++;
}
if (count <= 0) {
return bestSegment;
}
double averageDeriv = totalDeriv / count;
DEBUG_OUT(QString("Overall derivative average in analysis range: %1").arg(averageDeriv, 0, 'f', 6));
// 定义平台稳定性阈值(相对于平均值的允许偏差)
const double stabilityThreshold = 0.01; // 1%的偏差
double maxAllowedDeviation = averageDeriv * stabilityThreshold;
DEBUG_OUT(QString("Stability threshold: +-%1").arg(maxAllowedDeviation, 0, 'f', 6));
// 寻找最长的连续稳定段
int maxLength = 0;
int currentStart = -1;
int currentLength = 0;
for (int i = startPoint; i < endPoint; ++i) {
double deviation = qAbs(derivativeData[i] - averageDeriv);
if (deviation <= maxAllowedDeviation) {
// 点在稳定范围内
if (currentStart == -1) {
currentStart = i;
}
currentLength++;
} else {
// 点超出稳定范围,结束当前段
if (currentLength > maxLength) {
maxLength = currentLength;
bestSegment = qMakePair(currentStart, currentStart + currentLength - 1);
}
currentStart = -1;
currentLength = 0;
}
}
// 检查最后一段
if (currentLength > maxLength) {
maxLength = currentLength;
bestSegment = qMakePair(currentStart, currentStart + currentLength - 1);
}
if (bestSegment.first >= 0) {
DEBUG_OUT(QString("Longest stable platform found: points [%1-%2], length=%3 points")
.arg(bestSegment.first).arg(bestSegment.second).arg(maxLength));
DEBUG_OUT(QString("Time range: %1hr to %2hr")
.arg(timeData[bestSegment.first], 0, 'f', 4)
.arg(timeData[bestSegment.second], 0, 'f', 4));
// 计算该平台段的统计信息
double platformAvg = 0.0;
for (int i = bestSegment.first; i <= bestSegment.second; ++i) {
platformAvg += derivativeData[i];
}
platformAvg /= maxLength;
DEBUG_OUT(QString("Platform average derivative: %1, overall average: %2")
.arg(platformAvg, 0, 'f', 6).arg(averageDeriv, 0, 'f', 6));
} else {
DEBUG_OUT("No suitable platform segment found using stability criteria");
}
return bestSegment;
}
// 方法1标准径向流平台分析保持原有逻辑
nmDataDiagnostic::FlowRegimeAnalysis nmDataDiagnostic::analyzeStandardRadialFlow(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int availablePoints,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Method 1: Standard Radial Flow Platform Analysis ===");
// 使用中后期数据寻找平台
int startPoint = qMin(static_cast<int>(availablePoints * 0.3), availablePoints - 20);
int pointsToAnalyze = availablePoints - startPoint;
if (pointsToAnalyze < 5) {
FlowRegimeAnalysis failedAnalysis;
failedAnalysis.isValid = false;
failedAnalysis.description = "Standard Radial Flow (failed - insufficient points)";
return failedAnalysis;
}
return analyzeRadialFlowForKh(timeData, derivativeData, startPoint, pointsToAnalyze, pWell, pReservoir);
}
// 方法2晚期分析
nmDataDiagnostic::FlowRegimeAnalysis nmDataDiagnostic::analyzeSaphirStyleLateTime(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int availablePoints,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Method 2: Late-Time Analysis ===");
FlowRegimeAnalysis analysis;
analysis.isValid = false;
analysis.description = "Late-Time Analysis";
// 使用后50%的数据寻找极小导数值
int startPoint = qMax(static_cast<int>(availablePoints * 0.5), availablePoints - 50);
int endPoint = availablePoints - 1;
if (endPoint - startPoint < 10) {
DEBUG_OUT("Not enough late-time data for analysis");
return analysis;
}
DEBUG_OUT(QString("Analyzing late-time points [%1-%2] for minimum derivative")
.arg(startPoint).arg(endPoint));
// 寻找晚期导数的最小值段
QPair<int, int> minDerivativeSegment = findMinimumDerivativeSegment(
timeData, derivativeData, startPoint, endPoint);
if (minDerivativeSegment.first >= 0) {
// 计算该段的平均值
int segmentStart = minDerivativeSegment.first;
int segmentEnd = minDerivativeSegment.second;
int segmentLength = segmentEnd - segmentStart + 1;
double totalDeriv = 0.0;
double totalTime = 0.0;
for (int i = segmentStart; i <= segmentEnd; ++i) {
totalDeriv += derivativeData[i];
totalTime += timeData[i];
}
analysis.avgPressure = totalDeriv / segmentLength; // 最小导数段平均值
analysis.avgTime = totalTime / segmentLength; // 对应时间平均值
analysis.pointPair = minDerivativeSegment;
// 获取流量和储层参数
double flowRate = pWell->getFlowRateAtTime(analysis.avgTime);
double bo = pReservoir->getBo().getValue().toDouble();
double miuo = pReservoir->getMiuo().getValue().toDouble();
DEBUG_OUT(QString("Minimum derivative segment found: points [%1-%2], length=%3")
.arg(segmentStart).arg(segmentEnd).arg(segmentLength));
DEBUG_OUT(QString("Time range: %1hr to %2hr")
.arg(timeData[segmentStart], 0, 'f', 2).arg(timeData[segmentEnd], 0, 'f', 2));
DEBUG_OUT(QString("Minimum derivative average: %1 MPa")
.arg(analysis.avgPressure, 0, 'f', 6));
DEBUG_OUT(QString("Flow rate at analysis time: %1")
.arg(flowRate, 0, 'f', 4));
if (flowRate > 0 && analysis.avgPressure > 0) {
// kh = (70.6 * q * B * μ) / (DertaP'_min)
analysis.result = (70.6 * flowRate * bo * miuo) / analysis.avgPressure;
analysis.isValid = true;
// 计算质量指标
analysis.slope = 0.0; // 晚期分析不关注斜率
analysis.slopeDifference = 0.0;
analysis.description = QString("Late-Time (segment=%1pts, DertaP'=%2, kh=%3)")
.arg(segmentLength)
.arg(analysis.avgPressure, 0, 'f', 6)
.arg(analysis.result, 0, 'f', 0);
DEBUG_OUT(QString("Calculation successful:"));
DEBUG_OUT(QString("Formula: kh = (70.6 * %1 * %2 * %3) / %4")
.arg(flowRate, 0, 'f', 1).arg(bo, 0, 'f', 2)
.arg(miuo, 0, 'f', 2).arg(analysis.avgPressure, 0, 'f', 6));
DEBUG_OUT(QString("Result: kh = %1 md*m").arg(analysis.result, 0, 'f', 0));
} else {
DEBUG_OUT("Invalid flow rate or derivative value for analysis");
}
} else {
DEBUG_OUT("Failed to find suitable minimum derivative segment");
}
return analysis;
}
// 方法3早中期稳定段分析
nmDataDiagnostic::FlowRegimeAnalysis nmDataDiagnostic::analyzeEarlyStableFlow(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int availablePoints,
nmDataWellBase* pWell, nmDataReservoir* pReservoir)
{
DEBUG_OUT("=== Method 3: Early-Middle Stable Flow Analysis ===");
FlowRegimeAnalysis analysis;
analysis.isValid = false;
analysis.description = "Early-Middle Stable Flow";
// 使用10%-60%的数据寻找相对稳定段
int startPoint = qMax(static_cast<int>(availablePoints * 0.1), 5);
int endPoint = qMin(static_cast<int>(availablePoints * 0.6), availablePoints - 5);
if (endPoint - startPoint < 10) {
DEBUG_OUT("Not enough data for early-middle stable flow analysis");
return analysis;
}
DEBUG_OUT(QString("Searching for stable flow in points [%1-%2]")
.arg(startPoint).arg(endPoint));
// 寻找变异系数最小的连续段
QPair<int, int> mostStableSegment = findMostStableDerivativeSegment(
derivativeData, startPoint, endPoint);
if (mostStableSegment.first >= 0) {
int segmentStart = mostStableSegment.first;
int segmentEnd = mostStableSegment.second;
int segmentLength = segmentEnd - segmentStart + 1;
// 计算该段的平均值
double totalDeriv = 0.0;
double totalTime = 0.0;
for (int i = segmentStart; i <= segmentEnd; ++i) {
totalDeriv += derivativeData[i];
totalTime += timeData[i];
}
analysis.avgPressure = totalDeriv / segmentLength;
analysis.avgTime = totalTime / segmentLength;
analysis.pointPair = mostStableSegment;
// 获取流量和储层参数
double flowRate = pWell->getFlowRateAtTime(analysis.avgTime);
double bo = pReservoir->getBo().getValue().toDouble();
double miuo = pReservoir->getMiuo().getValue().toDouble();
if (flowRate > 0 && analysis.avgPressure > 0) {
analysis.result = (70.6 * flowRate * bo * miuo) / analysis.avgPressure;
analysis.isValid = true;
analysis.description = QString("Early-Middle Stable (segment=%1pts, DertaP'=%2, kh=%3)")
.arg(segmentLength)
.arg(analysis.avgPressure, 0, 'f', 6)
.arg(analysis.result, 0, 'f', 0);
DEBUG_OUT(QString("Early-middle stable flow found: points [%1-%2], kh=%3")
.arg(segmentStart).arg(segmentEnd).arg(analysis.result, 0, 'f', 0));
}
}
return analysis;
}
// 寻找最小导数段
QPair<int, int> nmDataDiagnostic::findMinimumDerivativeSegment(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int startPoint, int endPoint)
{
QPair<int, int> bestSegment(-1, -1);
// 寻找连续的低值段至少5个点
const int minSegmentLength = 5;
double minAvgDerivative = 999999.0;
for (int i = startPoint; i <= endPoint - minSegmentLength; ++i) {
for (int len = minSegmentLength; len <= qMin(20, endPoint - i + 1); ++len) {
int j = i + len - 1;
// 计算这个段的平均导数
double totalDeriv = 0.0;
for (int k = i; k <= j; ++k) {
totalDeriv += derivativeData[k];
}
double avgDeriv = totalDeriv / len;
// 如果这个段的平均导数更小,更新最佳结果
if (avgDeriv < minAvgDerivative) {
minAvgDerivative = avgDeriv;
bestSegment = qMakePair(i, j);
}
}
}
if (bestSegment.first >= 0) {
DEBUG_OUT(QString("Found minimum derivative segment [%1-%2] with average DertaP'=%3")
.arg(bestSegment.first).arg(bestSegment.second)
.arg(minAvgDerivative, 0, 'f', 6));
}
return bestSegment;
}
// 寻找最稳定的导数段
QPair<int, int> nmDataDiagnostic::findMostStableDerivativeSegment(
const QVector<double>& derivativeData,
int startPoint, int endPoint)
{
QPair<int, int> bestSegment(-1, -1);
const int minSegmentLength = 8;
double minVariationCoeff = 999999.0;
for (int i = startPoint; i <= endPoint - minSegmentLength; ++i) {
for (int len = minSegmentLength; len <= qMin(25, endPoint - i + 1); ++len) {
int j = i + len - 1;
// 计算变异系数 (标准差/均值)
double variationCoeff = calculateVariationCoefficient(derivativeData, i, j);
if (variationCoeff < minVariationCoeff) {
minVariationCoeff = variationCoeff;
bestSegment = qMakePair(i, j);
}
}
}
return bestSegment;
}
// 辅助函数:计算变异系数
double nmDataDiagnostic::calculateVariationCoefficient(
const QVector<double>& data, int start, int end)
{
if (end <= start) return 999999.0;
int count = end - start + 1;
// 计算均值
double mean = 0.0;
for (int i = start; i <= end; ++i) {
mean += data[i];
}
mean /= count;
if (mean <= 0) return 999999.0;
// 计算标准差
double variance = 0.0;
for (int i = start; i <= end; ++i) {
double diff = data[i] - mean;
variance += diff * diff;
}
double stdDev = qSqrt(variance / count);
return stdDev / mean; // 变异系数
}
// 智能选择最佳分析方法
nmDataDiagnostic::FlowRegimeAnalysis* nmDataDiagnostic::selectBestTransmissibilityAnalysis(
FlowRegimeAnalysis& standardRadial, FlowRegimeAnalysis& saphirStyle,
FlowRegimeAnalysis& earlyStable)
{
QVector<FlowRegimeAnalysis*> validMethods;
if (standardRadial.isValid) validMethods.append(&standardRadial);
if (saphirStyle.isValid) validMethods.append(&saphirStyle);
if (earlyStable.isValid) validMethods.append(&earlyStable);
if (validMethods.isEmpty()) {
DEBUG_OUT("No valid transmissibility calculation method found");
return NULL;
}
DEBUG_OUT("=== Method Selection Analysis ===");
for (int i = 0; i < validMethods.size(); ++i) {
FlowRegimeAnalysis* method = validMethods[i];
DEBUG_OUT(QString("Valid method: %1, kh=%2")
.arg(method->description).arg(method->result, 0, 'f', 0));
}
// 选择策略:
// 1. 优先选择标准径向流(如果存在且质量好)
// 2. 如果没有标准径向流,选择模拟商业软件行为
// 3. 最后选择早期稳定流
if (standardRadial.isValid && standardRadial.slopeDifference < 0.3) {
DEBUG_OUT("Selected: Standard radial flow (good platform quality)");
return &standardRadial;
}
if (saphirStyle.isValid) {
DEBUG_OUT("Selected: Saphir-style late-time analysis (following commercial software approach)");
return &saphirStyle;
}
if (earlyStable.isValid) {
DEBUG_OUT("Selected: Early stable flow (fallback method)");
return &earlyStable;
}
// 兜底:返回第一个有效方法
DEBUG_OUT(QString("Selected: First available method (%1)").arg(validMethods[0]->description));
return validMethods[0];
}
double nmDataDiagnostic::calculateSlopeVariation(
const QVector<double>& timeData,
const QVector<double>& derivativeData,
int start, int end)
{
if (end <= start + 1) return 999999.0;
double totalSlope = 0.0;
int slopeCount = 0;
// 计算段内所有相邻点之间的斜率绝对值
for (int i = start; i < end; ++i) {
if (i + 1 >= timeData.size()) break;
double slope = calculateSlopeBetweenPoints(timeData[i], derivativeData[i],
timeData[i+1], derivativeData[i+1]);
totalSlope += qAbs(slope); // 理想平台斜率应该接近0
slopeCount++;
}
return slopeCount > 0 ? totalSlope / slopeCount : 999999.0;
}