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/nmSubWxs/nmWxAutomaticFitting.cpp

1557 lines
56 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 "nmWxAutomaticFitting.h"
#include "nmCalculationAutoFitPSO.h"
#include "nmWxAutomaticfittingStart.h"
#include "nmWxParameterProperty.h"
#include "nmDataAnalyzeManager.h"
#include "iSubWndFitting.h"
#include "iSysParaHelper.h"
#include "iParameter.h"
#include "mModuleDefines.h"
#include "mGui/mGuiAnal/iAnalRun.h"
#include "iGuiPlot.h"
#include "ZxObjCurve.h"
#include "mAlgDefines.h"
#pragma comment(lib, "mGuiAnal.lib")
#include <QApplication>
#include <QHeaderView>
#include <cmath>
#include <float.h>
#ifdef Q_OS_WIN
#include <windows.h>
#define DEBUG_UI(msg) OutputDebugStringA(QString("[UI] %1\n").arg(msg).toLocal8Bit().data())
#endif
namespace {
bool nmAutoFitUiIsFinite(double value)
{
#ifdef Q_OS_WIN
return _finite(value) != 0;
#else
return std::isfinite(value);
#endif
}
// 物理边界可能由多个浮点配置量计算得到,界面十进制文本再转回 double 后会有
// 末位舍入差。这里只放宽约 8 个机器精度,不放宽实际物理范围。
bool nmAutoFitUiNearlyEqual(double left, double right)
{
const double scale = qMax(std::fabs(left), std::fabs(right));
return std::fabs(left - right) <= DBL_EPSILON * 8.0 * scale;
}
// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。
bool nmAutoFitReadPhysicalRange(const char* parameterName,
double fallbackMin, double fallbackMax, double& minValue, double& maxValue)
{
minValue = fallbackMin;
maxValue = fallbackMax;
iSysParaHelper* paraHelper = _paraHelper;
if(!paraHelper) {
return false;
}
iParameter* parameter = paraHelper->getPara(QString::fromLatin1(parameterName), s_Nm_Serie);
if(!parameter || !nmAutoFitUiIsFinite(parameter->m_dMin)
|| !nmAutoFitUiIsFinite(parameter->m_dMax)
|| parameter->m_dMin >= parameter->m_dMax) {
return false;
}
minValue = parameter->m_dMin;
maxValue = parameter->m_dMax;
return true;
}
// 主界面气井双对数中的源曲线和导数曲线已经采用拟压力量纲.
// 自动拟合直接读取这两条显示曲线, 避免井对象中的原始压力历史数据与结果曲线量纲不一致.
// 两条曲线可能因无效点过滤而长度不同, 因此按时间坐标匹配共同有效点.
bool getMainHistoryLogLog(iSubWndFitting* fitting,
QVector<QVector<double> >& data,
QString& errorMessage)
{
data.clear();
if(!fitting) {
errorMessage = QObject::tr("The current fitting window is unavailable.");
return false;
}
QWidget* widget = fitting->getFitSubRstWxOf(FSRT_DoubleLog, false, &errorMessage);
iGuiPlot* plot = qobject_cast<iGuiPlot*>(widget);
if(!plot) {
errorMessage = QObject::tr("The main double-log plot is unavailable.");
return false;
}
ZxObjCurve* sourceCurve = plot->getCurve(s_Souce_Curve);
ZxObjCurve* derivativeCurve = plot->getCurve(s_Deriv_Curve);
if(!sourceCurve || !derivativeCurve) {
errorMessage = QObject::tr("The main double-log history curves are unavailable.");
return false;
}
QVector<QPointF> sourcePoints = sourceCurve->getAllValues();
QVector<QPointF> derivativePoints = derivativeCurve->getAllValues();
data.resize(3);
int sourceIndex = 0;
int derivativeIndex = 0;
while(sourceIndex < sourcePoints.size()
&& derivativeIndex < derivativePoints.size()) {
const QPointF sourcePoint = sourcePoints[sourceIndex];
const QPointF derivativePoint = derivativePoints[derivativeIndex];
const double sourceTime = sourcePoint.x();
const double derivativeTime = derivativePoint.x();
if(!nmAutoFitUiIsFinite(sourceTime) || sourceTime <= 0.0) {
++sourceIndex;
continue;
}
if(!nmAutoFitUiIsFinite(derivativeTime) || derivativeTime <= 0.0) {
++derivativeIndex;
continue;
}
const double timeTolerance = qMax(1.0,
qMax(qAbs(sourceTime), qAbs(derivativeTime))) * 1e-8;
if(qAbs(sourceTime - derivativeTime) <= timeTolerance) {
const double source = sourcePoint.y();
const double derivative = derivativePoint.y();
if(nmAutoFitUiIsFinite(source)
&& nmAutoFitUiIsFinite(derivative)
&& source > 0.0 && derivative > 0.0) {
data[0].append(sourceTime);
data[1].append(source);
data[2].append(derivative);
}
++sourceIndex;
++derivativeIndex;
} else if(sourceTime < derivativeTime) {
++sourceIndex;
} else {
++derivativeIndex;
}
}
if(data[0].size() < 5) {
data.clear();
errorMessage = QObject::tr("The main double-log history curves have too few valid points.");
return false;
}
return true;
}
}
// 设置某一行参数是否显示。隐藏时同步取消勾选并禁用,避免隐藏参数参与自动拟合。
void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, bool visible)
{
if(!table || row < 0 || row >= table->rowCount()) {
return;
}
table->setRowHidden(row, !visible);
QCheckBox* checkBox = qobject_cast<QCheckBox*>(table->cellWidget(row, 1));
if(checkBox) {
checkBox->setEnabled(visible);
if(!visible) {
checkBox->setChecked(false);
}
}
}
// 隐藏参数行后重新整理序号,让界面看起来像删除了不需要的参数。
void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
{
if(!table) {
return;
}
int visibleIndex = 1;
for(int row = 0; row < table->rowCount(); ++row) {
if(table->isRowHidden(row)) {
continue;
}
QTableWidgetItem* item = table->item(row, 0);
if(!item) {
item = new QTableWidgetItem();
table->setItem(row, 0, item);
}
item->setText(QString::number(visibleIndex++));
item->setTextAlignment(Qt::AlignCenter);
}
}
// 根据当前模型类型控制Ct/Cf/Swi参数显示。
void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType)
{
if(!table) {
return;
}
for(int row = 0; row < table->rowCount(); ++row) {
setParameterRowVisible(table, row, true);
}
bool showCt = false;
bool showCf = false;
bool showSwi = false;
switch(eType) {
case SMT_Oil_ConstPvt:
case SMT_Water_ConstPvt:
// 常量PVT使用综合压缩系数Ct。
showCt = true;
break;
case SMT_Oil_VariablePvt:
case SMT_Water_VariablePvt:
case SMT_Gas_VariablePvt:
// 变化PVT使用岩石压缩系数Cf。
showCf = true;
break;
case SMT_Oil_Water_TwoPhase:
// 油水两相使用Cf并且只有它需要初始含水饱和度Swi。
showCf = true;
showSwi = true;
break;
default:
showCf = true;
break;
}
setParameterRowVisible(table, 5, showCt); // Ct
setParameterRowVisible(table, 6, showCf); // Cf
setParameterRowVisible(table, 7, showSwi); // Swi
renumberVisibleParameterRows(table);
}
// 获取参数的系统物理边界,并为 Swi 叠加当前储层饱和度约束。
bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
double& minValue, double& maxValue)
{
static const char* parameterNames[] = {
"Result_K", "Result_W_Skin", "Result_W_C", "Result_phi",
"Result_h", "Result_Cti", "Result_Cf", "Result_Swi"
};
// KAPPA 的边界使用 md、ft、bbl/psi自动拟合界面使用 Darcy、m、m^3/MPa
// 这里统一换算到界面和 PSO 实际使用的单位K 除以 1000h 由 ft 换成 m
// 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。
// Ct/Cf/Swi 沿用模型参数表边界。
static const double physicalMin[] = {
1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0
};
static const double physicalMax[] = {
1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0
};
if(parameterIndex < 0 || parameterIndex >= 8) {
return false;
}
minValue = physicalMin[parameterIndex];
maxValue = physicalMax[parameterIndex];
bool rangeRead = true;
if(parameterIndex >= 5) {
rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex],
physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue);
}
if(parameterIndex == 7) {
double soi = reservoirData.getSoi().getValue().toDouble();
double sgi = reservoirData.getSgi().getValue().toDouble();
if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) {
if(soi < 0.0 || sgi < 0.0 || soi + sgi > 1.0) {
// Soi+Sgi 超过 1 时没有可行的 Swi固定到物理下限避免继续搜索非法区间。
minValue = 0.0;
maxValue = 0.0;
} else {
maxValue = qMin(maxValue, 1.0 - soi - sgi);
}
}
}
return rangeRead;
}
// 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。
void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
double minValue, double maxValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterTable->rowCount()
|| !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(maxValue)) {
return;
}
if(maxValue < minValue) {
qSwap(minValue, maxValue);
}
const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true;
if(m_parameterTable->item(parameterIndex, 2)) {
m_parameterTable->item(parameterIndex, 2)->setText(QString::number(minValue, 'g', 10));
}
if(m_parameterTable->item(parameterIndex, 4)) {
m_parameterTable->item(parameterIndex, 4)->setText(QString::number(maxValue, 'g', 10));
}
switch(parameterIndex) {
case 0:
automaticFittingData.getPermeabilityMin().setValue(minValue);
automaticFittingData.getPermeabilityMax().setValue(maxValue);
break;
case 1:
automaticFittingData.getSkinMin().setValue(minValue);
automaticFittingData.getSkinMax().setValue(maxValue);
break;
case 2:
automaticFittingData.getWellboreStorageMin().setValue(minValue);
automaticFittingData.getWellboreStorageMax().setValue(maxValue);
break;
case 3:
automaticFittingData.getPorosityMin().setValue(minValue);
automaticFittingData.getPorosityMax().setValue(maxValue);
break;
case 4:
automaticFittingData.getThicknessMin().setValue(minValue);
automaticFittingData.getThicknessMax().setValue(maxValue);
break;
case 5:
automaticFittingData.getCtMin().setValue(minValue);
automaticFittingData.getCtMax().setValue(maxValue);
break;
case 6:
automaticFittingData.getCfMin().setValue(minValue);
automaticFittingData.getCfMax().setValue(maxValue);
break;
case 7:
automaticFittingData.getSwiMin().setValue(minValue);
automaticFittingData.getSwiMax().setValue(maxValue);
break;
default:
break;
}
m_updatingParameterRanges = wasUpdatingRanges;
}
// 根据当前初值生成建议搜索范围并始终截断在系统物理边界内。skin 使用
// 加减固定宽度,其余正值参数使用倍率范围;该规则在首次加载和拟合完成后复用。
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
double reference = centerValue;
const bool positiveParameter = parameterIndex != 1;
// 正值参数初值为 0 时不再借用旧的界面范围,直接从物理边界选取搜索尺度。
if(positiveParameter && reference <= 0.0) {
if(physicalMin > 0.0) {
reference = physicalMin;
} else {
reference = qMax(physicalMax * 0.01, 1.0e-12);
}
}
if(physicalMax < physicalMin) {
return;
}
reference = qBound(physicalMin, reference, physicalMax);
const double boundedCenterValue = qBound(physicalMin, centerValue, physicalMax);
double newMin = physicalMin;
double newMax = physicalMax;
if(parameterIndex == 1) {
const double skinHalfRange = 10.0;
newMin = qMax(physicalMin, reference - skinHalfRange);
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) {
const double lowerFactor = 0.1;
const double upperFactor = 10.0;
newMin = qMax(physicalMin, reference * lowerFactor);
newMax = qMin(physicalMax, reference * upperFactor);
} else if(parameterIndex == 7) {
// 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。
newMin = physicalMin;
newMax = physicalMax;
}
// 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。
newMin = qMin(newMin, boundedCenterValue);
newMax = qMax(newMax, boundedCenterValue);
newMin = qMax(newMin, physicalMin);
newMax = qMin(newMax, physicalMax);
if(newMax >= newMin) {
setParameterRange(parameterIndex, newMin, newMax);
}
}
// 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。
void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
{
if(!m_parameterTable) {
return;
}
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(initialItem) {
bool initialOk = false;
const double initialValue = initialItem->text().toDouble(&initialOk);
if(initialOk && nmAutoFitUiIsFinite(initialValue)) {
updateRangeForParameter(parameterIndex, initialValue);
} else {
// 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。
double physicalMin = 0.0;
double physicalMax = 0.0;
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
if(physicalMax >= physicalMin) {
setParameterRange(parameterIndex, physicalMin, physicalMax);
}
}
}
}
}
// 校正已保存的范围:保留物理边界内的用户区间,无交集时按当前初值生成兜底区间。
void nmWxAutomaticFitting::normalizeSavedParameterRanges()
{
if(!m_parameterTable) {
return;
}
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(!minItem || !maxItem || !initialItem) {
continue;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
bool savedMinOk = false;
bool savedMaxOk = false;
const double savedMin = minItem->text().toDouble(&savedMinOk);
const double savedMax = maxItem->text().toDouble(&savedMaxOk);
const bool savedRangeValid = savedMinOk && savedMaxOk
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
&& savedMax >= savedMin;
if(savedRangeValid && physicalMax >= physicalMin) {
const double clippedMin = qMax(savedMin, physicalMin);
const double clippedMax = qMin(savedMax, physicalMax);
if(clippedMax >= clippedMin) {
setParameterRange(parameterIndex, clippedMin, clippedMax);
continue;
}
}
// 已保存范围无效或与物理边界无交集时,按数据对象初值重新生成。
bool initialOk = false;
const double initialValue = initialItem->text().toDouble(&initialOk);
if(initialOk && nmAutoFitUiIsFinite(initialValue)) {
updateRangeForParameter(parameterIndex, initialValue);
} else if(physicalMax >= physicalMin) {
setParameterRange(parameterIndex, physicalMin, physicalMax);
}
}
}
// 校验当前表格中的参数范围parameterIndex 为 -1 时检查所有可见参数行。
bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex)
{
if(!m_parameterTable) {
errorMessage = tr("The parameter table is unavailable.");
return false;
}
if(parameterIndex < -1 || parameterIndex >= 8) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
"Thickness", "Ct", "Cf", "Swi"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
if(m_parameterTable->isRowHidden(currentParameterIndex)) {
continue;
}
QTableWidgetItem* minItem = m_parameterTable->item(currentParameterIndex, 2);
QTableWidgetItem* initialItem = m_parameterTable->item(currentParameterIndex, 3);
QTableWidgetItem* maxItem = m_parameterTable->item(currentParameterIndex, 4);
if(!minItem || !initialItem || !maxItem) {
errorMessage = tr("The range values for %1 are incomplete.")
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
bool minOk = false;
bool initialOk = false;
bool maxOk = false;
const double minValue = minItem->text().toDouble(&minOk);
const double initialValue = initialItem->text().toDouble(&initialOk);
const double maxValue = maxItem->text().toDouble(&maxOk);
if(!minOk || !initialOk || !maxOk
|| !nmAutoFitUiIsFinite(minValue)
|| !nmAutoFitUiIsFinite(initialValue)
|| !nmAutoFitUiIsFinite(maxValue)) {
errorMessage = tr("The minimum value, initial value, and maximum value of %1 must be finite numbers.")
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax);
if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax)
|| physicalMax < physicalMin) {
errorMessage = tr("The physical range of %1 is invalid.")
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
if((minValue < physicalMin && !nmAutoFitUiNearlyEqual(minValue, physicalMin))
|| (minValue > physicalMax && !nmAutoFitUiNearlyEqual(minValue, physicalMax))
|| (initialValue < physicalMin && !nmAutoFitUiNearlyEqual(initialValue, physicalMin))
|| (initialValue > physicalMax && !nmAutoFitUiNearlyEqual(initialValue, physicalMax))
|| (maxValue < physicalMin && !nmAutoFitUiNearlyEqual(maxValue, physicalMin))
|| (maxValue > physicalMax && !nmAutoFitUiNearlyEqual(maxValue, physicalMax))) {
errorMessage = tr("The values of %1 exceed the physical range [%2, %3].")
.arg(tr(parameterNames[currentParameterIndex]))
.arg(QString::number(physicalMin, 'g', 10))
.arg(QString::number(physicalMax, 'g', 10));
return false;
}
if(minValue > maxValue || minValue > initialValue || initialValue > maxValue) {
errorMessage = tr("The values of %1 must satisfy: minimum <= initial value <= maximum.")
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
}
return true;
}
nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
: iDlgBase(parent)
, m_autoFitterPSO(nullptr)
, m_progressDialog(nullptr)
, m_progressTimer(nullptr)
, m_progressMonitor(nullptr)
, m_autoParameterRanges(true)
// 构造期间先禁止即时校验,避免初始值写入和范围生成之间出现短暂的不一致。
, m_updatingParameterRanges(true)
{
DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16));
// 加载已有井数据
QVector<nmDataWellBase*> listWellData = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList();
// 遍历并分类井数据
foreach (nmDataWellBase* well, listWellData) {
if (auto vfWell = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
m_verticalFracturedWells.append(*vfWell);
}
else if (auto hfWell = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
m_horizontalFracturedWells.append(*hfWell);
}
else if (auto vWell = dynamic_cast<nmDataVerticalWell*>(well)) {
m_verticalWells.append(*vWell);
}
else if (auto hWell = dynamic_cast<nmDataHorizontalWell*>(well)) {
m_horizontalWells.append(*hWell);
}
}
// 获取数据
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy();
automaticFittingData = pManager->getAutomaticFittingDataCopy();
const bool hasSavedFittingData = pManager && pManager->getAutomaticFittingData() != nullptr;
// 自动范围始终开启用户在表格中修改上下限后itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true;
NM_SOLVER_MODEL_TYPE solverModelType = pManager->getSolverModelType();
// 未保存过配置时只保留参数选择的相态默认值,不再覆盖数据对象中的初值或范围。
if(!hasSavedFittingData) {
if(solverModelType == SMT_Oil_ConstPvt ||
solverModelType == SMT_Water_ConstPvt) {
// T1/T3 的综合压缩系数默认不参与拟合。
automaticFittingData.setCtSelected(false);
} else if(solverModelType == SMT_Oil_VariablePvt ||
solverModelType == SMT_Water_VariablePvt) {
// T2/T4 的岩石压缩系数默认参与拟合。
automaticFittingData.setCfSelected(true);
}
}
setupUI();
setWindowTitle(tr("Automatic fitting"));
setModal(true);
resize(800, 480);
if(m_targetWellCombo->count() > 0) {
onWellSelected(0); // 默认选中第一口井
}
if(!hasSavedFittingData) {
initializeSuggestedParameterRanges();
} else {
normalizeSavedParameterRanges();
}
m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed");
}
nmWxAutomaticFitting::~nmWxAutomaticFitting()
{
DEBUG_UI(QString("AutoFitting Destructor: this=0x%1").arg((quintptr)this, 0, 16));
DEBUG_UI("AutoFitting destructor - starting cleanup");
// 停止定时器
if (m_progressTimer) {
m_progressTimer->stop();
}
// 断开所有连接,避免悬空指针
if (m_autoFitterPSO) {
disconnect(m_autoFitterPSO, nullptr, this, nullptr);
}
DEBUG_UI("AutoFitting destructor - completed");
}
void nmWxAutomaticFitting::setupUI()
{
m_mainLayout = new QVBoxLayout(this);
// 创建主要内容的水平布局
QHBoxLayout* contentLayout = new QHBoxLayout();
// 添加左侧间距
contentLayout->addSpacing(20);
setupControlPanel(); // 左侧控制面板
contentLayout->addLayout(m_controlLayout);
// 添加控制面板和表格之间的间距
contentLayout->addSpacing(30);
setupParameterTable(); // 右侧参数表格
contentLayout->addWidget(m_parameterTable, 1); // 设置拉伸因子为1使表格可以扩展
// 添加右侧间距
contentLayout->addSpacing(20);
m_mainLayout->addLayout(contentLayout);
setupButtons(); // 底部按钮
setLayout(m_mainLayout);
}
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
m_parameterTable = new QTableWidget(8, 6, this);
// 设置表头
QStringList headers;
headers << "" << tr("Parameter") << tr("Min") << tr("Initial value") << tr("Max") << tr("Unit");
m_parameterTable->setHorizontalHeaderLabels(headers);
// 设置表格属性
m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems);
m_parameterTable->setAlternatingRowColors(true);
m_parameterTable->verticalHeader()->setVisible(false);
// 设置列宽
QHeaderView* header = m_parameterTable->horizontalHeader();
header->setResizeMode(0, QHeaderView::Fixed); // 序号列固定宽度
header->setResizeMode(1, QHeaderView::Fixed); // 参数列固定宽度
header->setResizeMode(2, QHeaderView::Stretch); // 其他列自适应
header->setResizeMode(3, QHeaderView::Stretch);
header->setResizeMode(4, QHeaderView::Stretch);
header->setResizeMode(5, QHeaderView::Fixed); // 单位列固定宽度
// 设置固定列的宽度
m_parameterTable->setColumnWidth(0, 40); // 序号列宽度
m_parameterTable->setColumnWidth(1, 120); // 参数列宽度
m_parameterTable->setColumnWidth(5, 80); // 单位列宽度
// 设置表格的大小策略
m_parameterTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// 渗透率 (Permeability)
m_parameterTable->setItem(0, 0, new QTableWidgetItem("1"));
m_kCheckBox = new QCheckBox(tr("Permeability"));
m_kCheckBox->setChecked(automaticFittingData.getPermeabilitySelected());
m_parameterTable->setCellWidget(0, 1, m_kCheckBox);
m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble())));
m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble())));
m_parameterTable->setItem(0, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMax().getValue().toDouble())));
m_parameterTable->setItem(0, 5, new QTableWidgetItem(tr("Darcy")));
// 表皮系数 (Skin)
m_parameterTable->setItem(1, 0, new QTableWidgetItem("2"));
m_sCheckBox = new QCheckBox(tr("Skin"));
m_sCheckBox->setChecked(automaticFittingData.getSkinSelected());
m_parameterTable->setCellWidget(1, 1, m_sCheckBox);
m_parameterTable->setItem(1, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMin().getValue().toDouble())));
m_parameterTable->setItem(1, 3, new QTableWidgetItem());
m_parameterTable->setItem(1, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMax().getValue().toDouble())));
m_parameterTable->setItem(1, 5, new QTableWidgetItem(""));
// 井筒储集系数 (Wellbore storage)
m_parameterTable->setItem(2, 0, new QTableWidgetItem("3"));
m_cCheckBox = new QCheckBox(tr("Wellbore storage"));
m_cCheckBox->setChecked(automaticFittingData.getWellboreStorageSelected());
m_parameterTable->setCellWidget(2, 1, m_cCheckBox);
m_parameterTable->setItem(2, 2, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMin().getValue().toDouble())));
m_parameterTable->setItem(2, 3, new QTableWidgetItem());
m_parameterTable->setItem(2, 4, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMax().getValue().toDouble())));
m_parameterTable->setItem(2, 5, new QTableWidgetItem(tr("m^3/MPa")));
// 孔隙度 (Porosity)
m_parameterTable->setItem(3, 0, new QTableWidgetItem("4"));
m_phiCheckBox = new QCheckBox(tr("Porosity"));
m_phiCheckBox->setChecked(automaticFittingData.getPorositySelected());
m_parameterTable->setCellWidget(3, 1, m_phiCheckBox);
m_parameterTable->setItem(3, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMin().getValue().toDouble())));
m_parameterTable->setItem(3, 3, new QTableWidgetItem(QString::number(reservoirData.getPorosity().getValue().toDouble())));
m_parameterTable->setItem(3, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMax().getValue().toDouble())));
m_parameterTable->setItem(3, 5, new QTableWidgetItem(""));
// 储层厚度 (Thickness)
m_parameterTable->setItem(4, 0, new QTableWidgetItem("5"));
m_hCheckBox = new QCheckBox(tr("Thickness"));
m_hCheckBox->setChecked(automaticFittingData.getThicknessSelected());
m_parameterTable->setCellWidget(4, 1, m_hCheckBox);
m_parameterTable->setItem(4, 2, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMin().getValue().toDouble())));
m_parameterTable->setItem(4, 3, new QTableWidgetItem(QString::number(reservoirData.getThickness().getValue().toDouble())));
m_parameterTable->setItem(4, 4, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMax().getValue().toDouble())));
m_parameterTable->setItem(4, 5, new QTableWidgetItem(tr("m")));
// 综合压缩系数 (Ct)
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_ctCheckBox = new QCheckBox(tr("Ct"));
m_ctCheckBox->setChecked(automaticFittingData.getCtSelected());
m_parameterTable->setCellWidget(5, 1, m_ctCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCtMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem(QString::number(reservoirData.getCt().getValue().toDouble())));
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCtMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(""));
// 岩石压缩系数 (Cf)
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_cfCheckBox = new QCheckBox(tr("Cf"));
m_cfCheckBox->setChecked(automaticFittingData.getCfSelected());
m_parameterTable->setCellWidget(6, 1, m_cfCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCfMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem(QString::number(reservoirData.getCf().getValue().toDouble())));
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCfMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(""));
// 初始含水饱和度 (Swi)
m_parameterTable->setItem(7, 0, new QTableWidgetItem("8"));
m_swiCheckBox = new QCheckBox(tr("Swi"));
m_swiCheckBox->setChecked(automaticFittingData.getSwiSelected());
m_parameterTable->setCellWidget(7, 1, m_swiCheckBox);
m_parameterTable->setItem(7, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMin().getValue().toDouble())));
m_parameterTable->setItem(7, 3, new QTableWidgetItem(QString::number(reservoirData.getSwi().getValue().toDouble())));
m_parameterTable->setItem(7, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(7, 5, new QTableWidgetItem(""));
// 设置表格行为
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
for(int j = 0; j < 6; ++j) {
QTableWidgetItem* item = m_parameterTable->item(i, j);
if(item) {
if(j == 0 || j == 5) { // 序号列和单位列只读
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
} else if(j >= 2) {
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable);
} else { // 参数列(复选框)不可选择
item->setFlags(Qt::NoItemFlags);
}
}
}
}
// 序号列居中对齐
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
QTableWidgetItem* item = m_parameterTable->item(i, 0);
if(item) {
item->setTextAlignment(Qt::AlignCenter);
}
}
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
if(pManager) {
updateParameterVisibility(m_parameterTable, pManager->getSolverModelType());
}
// 连接选择改变信号
connect(m_parameterTable, SIGNAL(currentCellChanged(int, int, int, int)),
this, SLOT(onCellSelectionChanged(int, int, int, int)));
connect(m_parameterTable, SIGNAL(itemChanged(QTableWidgetItem*)),
this, SLOT(onParameterTableItemChanged(QTableWidgetItem*)));
}
void nmWxAutomaticFitting::setupControlPanel()
{
m_controlLayout = new QVBoxLayout();
m_controlLayout->setSizeConstraint(QLayout::SetFixedSize);
// 上方弹簧
m_controlLayout->addStretch(1);
// 算法选择
QLabel* algorithmLabel = new QLabel(tr("Algorithm:"));
m_algorithmCombo = new QComboBox();
m_algorithmCombo->addItem(tr("PSO (Particle Swarm)"));
m_algorithmCombo->setCurrentIndex(0);
m_algorithmCombo->setMaximumWidth(160);
m_algorithmCombo->setMinimumWidth(160);
QLabel* surrogateLabel = new QLabel(tr("PSO acceleration:"));
m_surrogateCombo = new QComboBox();
//m_surrogateCombo->setEnabled(false);// 暂时不可编辑
m_surrogateCombo->addItem(tr("Off"));
m_surrogateCombo->addItem(tr("On"));
m_surrogateCombo->setCurrentIndex(automaticFittingData.getSurrogateScreeningEnabled() ? 1 : 0);
m_surrogateCombo->setMaximumWidth(160);
m_surrogateCombo->setMinimumWidth(160);
// 迭代次数
QLabel* iterationLabel = new QLabel(tr("Number of iterations:"));
m_iterationEdit = new QLineEdit(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
m_iterationEdit->setMaximumWidth(100);
m_iterationEdit->setMinimumWidth(100);
// 误差上限
QLabel* errorLabel = new QLabel(tr("Error tolerance:"));
m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble()));
m_errorLimitEdit->setMaximumWidth(100);
m_errorLimitEdit->setMinimumWidth(100);
// 目标井选择
QLabel* wellLabel = new QLabel(tr("Target Well:"));
m_targetWellCombo = new QComboBox();
// 添加垂直井
for(int i = 0; i < m_verticalWells.size(); ++i) {
m_targetWellCombo->addItem(m_verticalWells[i].getWellName());
}
// 添加水平井
for(int i = 0; i < m_horizontalWells.size(); ++i) {
m_targetWellCombo->addItem(m_horizontalWells[i].getWellName());
}
// 添加垂直压裂井
for(int i = 0; i < m_verticalFracturedWells.size(); ++i) {
m_targetWellCombo->addItem(m_verticalFracturedWells[i].getWellName());
}
// 添加水平压裂井
for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) {
m_targetWellCombo->addItem(m_horizontalFracturedWells[i].getWellName());
}
connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
m_targetWellCombo->setMaximumWidth(100);
m_targetWellCombo->setMinimumWidth(100);
// 添加到垂直布局
m_controlLayout->addWidget(algorithmLabel);
m_controlLayout->addWidget(m_algorithmCombo);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(surrogateLabel);
m_controlLayout->addWidget(m_surrogateCombo);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(iterationLabel);
m_controlLayout->addWidget(m_iterationEdit);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(errorLabel);
m_controlLayout->addWidget(m_errorLimitEdit);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(wellLabel);
m_controlLayout->addWidget(m_targetWellCombo);
// 下方弹簧
m_controlLayout->addStretch(1);
}
void nmWxAutomaticFitting::setupButtons()
{
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_reverseBtn = new QPushButton(tr("Reverse Selection"));
m_okBtn = new QPushButton(tr("OK"));
m_cancelBtn = new QPushButton(tr("Cancel"));
// 设置按钮大小
m_reverseBtn->setMinimumWidth(120);
m_okBtn->setMinimumWidth(80);
m_cancelBtn->setMinimumWidth(80);
buttonLayout->addStretch();
buttonLayout->addWidget(m_reverseBtn);
buttonLayout->addWidget(m_okBtn);
buttonLayout->addWidget(m_cancelBtn);
m_mainLayout->addLayout(buttonLayout);
// 连接信号槽
connect(m_reverseBtn, SIGNAL(clicked()), this, SLOT(onReverseSelection()));
connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onAccept()));
connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onReject()));
}
void nmWxAutomaticFitting::onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
{
if(!m_parameterTable) return;
QHeaderView* header = m_parameterTable->horizontalHeader();
if(!header) return;
// 重置所有列头的字体为正常
for(int i = 0; i < m_parameterTable->columnCount(); ++i) {
QFont font = header->font();
font.setBold(false);
if(m_parameterTable->horizontalHeaderItem(i)) {
m_parameterTable->horizontalHeaderItem(i)->setFont(font);
}
}
// 设置当前列的表头为粗体
if(currentColumn >= 0 && currentColumn < m_parameterTable->columnCount()) {
QFont font = header->font();
font.setBold(true);
if(m_parameterTable->horizontalHeaderItem(currentColumn)) {
m_parameterTable->horizontalHeaderItem(currentColumn)->setFont(font);
}
}
header->update();
}
void nmWxAutomaticFitting::onReverseSelection()
{
// 更新反选逻辑,包含所有参数
if(!m_parameterTable) {
return;
}
if(!m_parameterTable->isRowHidden(0)) m_kCheckBox->setChecked(!m_kCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(1)) m_sCheckBox->setChecked(!m_sCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(2)) m_cCheckBox->setChecked(!m_cCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(3)) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(4)) m_hCheckBox->setChecked(!m_hCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(5)) m_ctCheckBox->setChecked(!m_ctCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(6)) m_cfCheckBox->setChecked(!m_cfCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
{
if(!item || m_updatingParameterRanges) {
return;
}
// 用户改动范围后,切井和拟合结果不再自动覆盖这组手工范围。
if(item->column() == 2 || item->column() == 4) {
m_autoParameterRanges = false;
}
if(item->column() >= 2 && item->column() <= 4
&& !m_parameterTable->isRowHidden(item->row())) {
QString validationError;
if(!validateParameterTable(validationError, item->row())) {
// 表格编辑提交后立即提示,用户不需要先点击“确定”才发现错误。
QMessageBox::warning(this, tr("Invalid parameter range"), validationError);
}
}
}
void nmWxAutomaticFitting::onAccept()
{
QString validationError;
if(!validateParameterTable(validationError)) {
QMessageBox::warning(this, tr("Invalid parameter range"), validationError);
return;
}
// 校验通过后再保存参数设置,非法输入不会被静默裁剪。
setAutomaticFittingValue();
// 更新完成后,通知参数界面刷新
nmWxParameterProperty::notifyUpdateTable();
// 获取目标井的双对数历史数据
QString selectedWellName = m_targetWellCombo->currentText();
if(selectedWellName.isEmpty()) {
QMessageBox::warning(this, tr("Warning"), tr("Please select a target well!"));
return;
}
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
if(!pManager) {
QMessageBox::warning(this, tr("Warning"), tr("Data manager is unavailable!"));
return;
}
// 从数据管理器获取最新的井对象
nmDataWellBase* pTargetWell = pManager->findWellByName(selectedWellName);
if(!pTargetWell) {
QMessageBox::warning(this, tr("Warning"), tr("Target well not found!"));
return;
}
QVector<QVector<double> > targetLogLogData;
const bool usePseudoPressure = (pManager->getSolverModelType() == SMT_Gas_VariablePvt);
if(usePseudoPressure) {
// 目标曲线来自当前主界面, 必须确认界面显示的井就是用户选择的拟合井.
nmDataWellBase* pCurrentWell = pManager->getCurWellData();
if(!pCurrentWell || pCurrentWell->getWellName() != selectedWellName) {
QMessageBox::warning(this, tr("Warning"),
tr("Please display the selected gas well in the main result view before fitting."));
return;
}
iSubWndFitting* pFitting = nmDataAnalyzeManager::getCurrentFitting();
iAnalRun* pAnalRun = pFitting ? pFitting->getAnalRun() : nullptr;
// 自动拟合不依赖手动求解流程, 启动 PSO 前需要单独初始化同一拟压力转换器.
// 传入拟合窗口自己的页面数据, 保证目标曲线和每个粒子的结果使用同一 PVT 与模式.
if(!pAnalRun
|| !pAnalRun->configPsAbouts(true,
pFitting->getModelOption(),
false,
pFitting->getAllWxPtr())) {
QMessageBox::warning(this, tr("Warning"),
tr("Gas pseudo-pressure data is unavailable or invalid."));
return;
}
QString curveError;
if(!getMainHistoryLogLog(pFitting, targetLogLogData, curveError)) {
QMessageBox::warning(this, tr("Warning"), curveError);
return;
}
} else {
// 油井和水井不使用拟压力, 继续读取井对象中原有的双对数历史数据.
targetLogLogData = pTargetWell->getHistoryLogLog();
}
if(targetLogLogData.isEmpty() || targetLogLogData.size() < 3) {
QMessageBox::warning(this, tr("Warning"), tr("Target well has no LogLog history data!"));
return;
}
// 检查双对数数据的一致性
if(targetLogLogData[0].size() != targetLogLogData[1].size() ||
targetLogLogData[0].size() != targetLogLogData[2].size()) {
QMessageBox::warning(this, tr("Warning"), tr("Target well LogLog data is inconsistent!"));
return;
}
// 检查结果双对数数据
QVector<QVector<double> > rstLogLogData = pTargetWell->getResultLogLog();
if(rstLogLogData.isEmpty() || rstLogLogData.size() < 3) {
QMessageBox::warning(this, tr("Warning"), tr("Target well has no LogLog result data!"));
return;
}
// 检查是否有参数被选中
bool hasSelectedParams = m_kCheckBox->isChecked() || m_sCheckBox->isChecked() ||
m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() ||
m_hCheckBox->isChecked() ||
m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() ||
m_swiCheckBox->isChecked();
if(!hasSelectedParams) {
QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!"));
return;
}
// 收集选中的参数名称
QStringList selectedParameterNames;
if(m_kCheckBox->isChecked()) selectedParameterNames << tr("Permeability");
if(m_sCheckBox->isChecked()) selectedParameterNames << tr("Skin");
if(m_cCheckBox->isChecked()) selectedParameterNames << tr("Wellbore storage");
if(m_phiCheckBox->isChecked()) selectedParameterNames << tr("Porosity");
if(m_hCheckBox->isChecked()) selectedParameterNames << tr("Thickness");
if(m_ctCheckBox->isChecked()) selectedParameterNames << tr("Ct");
if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf");
if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi");
// 启动自动拟合 - 传递双对数历史数据
startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName);
}
void nmWxAutomaticFitting::onReject()
{
reject();
}
void nmWxAutomaticFitting::onWellSelected(int index)
{
// 获取选中的井名
QString selectedWellName = m_targetWellCombo->itemText(index);
// 在分类的井数据中查找匹配的井
bool found = false;
double skinValue = 0.0;
double wellboreStorageValue = 0.0;
// 查找垂直井
for(int i = 0; i < m_verticalWells.size(); ++i) {
if(m_verticalWells[i].getWellName() == selectedWellName) {
skinValue = m_verticalWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_verticalWells[i].getWellboreStorage().getValue().toDouble();
found = true;
break;
}
}
// 查找水平井
if (!found) {
for(int i = 0; i < m_horizontalWells.size(); ++i) {
if(m_horizontalWells[i].getWellName() == selectedWellName) {
skinValue = m_horizontalWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_horizontalWells[i].getWellboreStorage().getValue().toDouble();
found = true;
break;
}
}
}
// 查找垂直压裂井
if (!found) {
for(int i = 0; i < m_verticalFracturedWells.size(); ++i) {
if(m_verticalFracturedWells[i].getWellName() == selectedWellName) {
skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble();
found = true;
break;
}
}
}
// 查找水平压裂井
if (!found) {
for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) {
if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) {
skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble();
found = true;
break;
}
}
}
if (found) {
// 更新表格数据
// 确保表格项存在
if(!m_parameterTable->item(1, 3)) {
m_parameterTable->setItem(1, 3, new QTableWidgetItem());
}
if(!m_parameterTable->item(2, 3)) {
m_parameterTable->setItem(2, 3, new QTableWidgetItem());
}
// 设置皮肤系数Skin
m_parameterTable->item(1, 3)->setText(QString::number(skinValue));
// 设置井筒储集系数Wellbore storage
m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue));
if(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue);
updateRangeForParameter(2, wellboreStorageValue);
}
}
}
void nmWxAutomaticFitting::setAutomaticFittingValue()
{
// 保存参数选择状态
automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked());
automaticFittingData.setSkinSelected(m_sCheckBox->isChecked());
automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked());
automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked());
automaticFittingData.setThicknessSelected(m_hCheckBox->isChecked());
automaticFittingData.setCtSelected(m_ctCheckBox->isChecked());
automaticFittingData.setCfSelected(m_cfCheckBox->isChecked());
automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked());
automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1);
// 保存渗透率的最小值和最大值
automaticFittingData.getPermeabilityMin().setValue(m_parameterTable->item(0, 2)->text().toDouble());
automaticFittingData.getPermeabilityMax().setValue(m_parameterTable->item(0, 4)->text().toDouble());
// 保存表皮系数的最小值和最大值
automaticFittingData.getSkinMin().setValue(m_parameterTable->item(1, 2)->text().toDouble());
automaticFittingData.getSkinMax().setValue(m_parameterTable->item(1, 4)->text().toDouble());
// 保存井筒储集系数的最小值和最大值
automaticFittingData.getWellboreStorageMin().setValue(m_parameterTable->item(2, 2)->text().toDouble());
automaticFittingData.getWellboreStorageMax().setValue(m_parameterTable->item(2, 4)->text().toDouble());
// 保存孔隙度的最小值和最大值
automaticFittingData.getPorosityMin().setValue(m_parameterTable->item(3, 2)->text().toDouble());
automaticFittingData.getPorosityMax().setValue(m_parameterTable->item(3, 4)->text().toDouble());
// 保存储层厚度的最小值和最大值
automaticFittingData.getThicknessMin().setValue(m_parameterTable->item(4, 2)->text().toDouble());
automaticFittingData.getThicknessMax().setValue(m_parameterTable->item(4, 4)->text().toDouble());
// 保存综合压缩系数的最小值和最大值
automaticFittingData.getCtMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getCtMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存岩石压缩系数的最小值和最大值
automaticFittingData.getCfMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getCfMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存初始含水饱和度的最小值和最大值
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble());
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
// 保存储层数据的初值
reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率
reservoirData.getPorosity().setValue(m_parameterTable->item(3, 3)->text().toDouble()); // 孔隙度
reservoirData.getThickness().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 储层厚度
reservoirData.getCt().setValue(m_parameterTable->item(5, 3)->text().toDouble()); // 综合压缩系数
reservoirData.getCf().setValue(m_parameterTable->item(6, 3)->text().toDouble()); // 岩石压缩系数
reservoirData.getSwi().setValue(m_parameterTable->item(7, 3)->text().toDouble()); // 初始含水饱和度
// 更新储层数据(全局)
nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData);
// 保存自动拟合数据
nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData);
// 只更新目标井的参数
QString selectedWellName = m_targetWellCombo->currentText();
if(!selectedWellName.isEmpty()) {
double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble();
double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
nmDataWellBase* pTargetWell = manager->findWellByName(selectedWellName);
if(pTargetWell) {
// 更新Skin
nmDataPerforation* perf = pTargetWell->getPerforation(0);
if(perf) {
nmDataAttribute skinAttr = perf->getSkin();
skinAttr.setValue(newSkinValue);
perf->setSkin(skinAttr);
}
// 更新井筒储集系数
nmDataAttribute wellboreAttr = pTargetWell->getWellboreStorage();
wellboreAttr.setValue(newWellboreStorageValue);
pTargetWell->setWellboreStorage(wellboreAttr);
// 根据井类型单独更新这一口井
NM_WELL_MODEL wellType = pTargetWell->getWellType();
if(wellType == NM_WELL_MODEL::Vertical_Well) {
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pTargetWell);
if(pVerticalWell != nullptr) {
QVector<nmDataVerticalWell> wells;
wells.append(*pVerticalWell);
manager->updateVerticalWells(wells);
}
} else if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* pVerticalFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(pVerticalFracturedWell != nullptr) {
QVector<nmDataVerticalFracturedWell> wells;
wells.append(*pVerticalFracturedWell);
manager->updateVerticalFracturedWells(wells);
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(pHorizontalFracturedWell != nullptr) {
QVector<nmDataHorizontalFracturedWell> wells;
wells.append(*pHorizontalFracturedWell);
manager->updateHorizontalFracturedWells(wells);
}
}
}
}
}
void nmWxAutomaticFitting::startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName)
{
DEBUG_UI("=== START AUTO FITTING ===");
// 先清理之前的实例
cleanupFitting();
DEBUG_UI("Creating PSO auto fitter");
m_autoFitterPSO = new nmCalculationAutoFitPSO(this);
m_autoFitterPSO->setTargetLogLogData(targetData);
m_autoFitterPSO->setPSOTargetWellName(targetWellName);
//// 特定井名时使用快速路径
//if (targetWellName == "VerticalWell1") {
// m_autoFitterPSO->setSimulationMode(true);
// // 构建目标参数向量(按启用参数的顺序)
// QVector<double> targetParams;
// if(m_kCheckBox->isChecked()) targetParams.append(0.0033); // 渗透率
// if(m_sCheckBox->isChecked()) targetParams.append(1.75); // 表皮系数
// if(m_cCheckBox->isChecked()) targetParams.append(0.23); // 井筒储集系数
// if(m_phiCheckBox->isChecked()) targetParams.append(0.189); // 孔隙度
// double targetError = 0.02291;
// m_autoFitterPSO->setSimulationTargetParams(targetParams, targetError);
//}
//else if (targetWellName == "W0009_1") {
// m_autoFitterPSO->setSimulationMode(true);
// // 构建目标参数向量(按启用参数的顺序)
// QVector<double> targetParams;
// if(m_kCheckBox->isChecked()) targetParams.append(0.23); // 渗透率
// if(m_sCheckBox->isChecked()) targetParams.append(0.75); // 表皮系数
// if(m_cCheckBox->isChecked()) targetParams.append(1.28); // 井筒储集系数
// if(m_phiCheckBox->isChecked()) targetParams.append(0.1954); // 孔隙度
// double targetError = 0.02036;
// m_autoFitterPSO->setSimulationTargetParams(targetParams, targetError);
//}
m_progressMonitor = new nmWxAutomaticfittingStart(this);
m_progressMonitor->setAutoFitter(m_autoFitterPSO);
m_progressMonitor->setPseudoPressureMode(
nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt);
m_progressMonitor->setTargetLogLogData(targetData);
connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
int maxIterations = m_iterationEdit->text().toInt();
double targetError = m_errorLimitEdit->text().toDouble();
QString wellName = m_targetWellCombo->currentText();
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
m_progressMonitor->setSelectedParameters(selectedParams);
m_progressMonitor->show();
QTimer::singleShot(100, this, SLOT(runAutoFitting()));
}
void nmWxAutomaticFitting::runAutoFitting()
{
if (m_progressMonitor) {
m_progressMonitor->markFittingStarted();
}
if(m_autoFitterPSO) {
m_autoFitterPSO->startAutoFitting();
}
}
void nmWxAutomaticFitting::onFittingProgress(int iteration, double fitness)
{
}
void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& message)
{
// 立即断开信号连接,防止重复调用
if (m_autoFitterPSO) {
disconnect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
}
if(success) {
// 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。
updateBestParametersToTable();
QString resultInfo;
if(m_autoFitterPSO) {
double bestFitness = m_autoFitterPSO->getBestFitness();
// 检查是否是用户停止的情况
if(message.contains("stopped by user", Qt::CaseInsensitive)) {
resultInfo = tr("PSO Optimization stopped by user:\n");
resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4);
resultInfo += tr("Current parameters have been applied to the model.");
QMessageBox::information(this, tr("Optimization Stopped"), resultInfo);
} else {
resultInfo = tr("PSO Optimization completed:\n");
resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4);
resultInfo += tr("Optimized parameters have been applied to the model.");
QMessageBox::information(this, tr("Optimization Completed"), resultInfo);
}
}
} else {
// 只有真正失败的情况才显示警告
QMessageBox::warning(this, tr("Optimization Failed"), message);
}
}
void nmWxAutomaticFitting::onStopFitting()
{
if(m_autoFitterPSO && m_autoFitterPSO->isRunning()) {
m_autoFitterPSO->stopFitting();
}
}
void nmWxAutomaticFitting::cleanupFitting()
{
DEBUG_UI("=== CLEANUP FITTING START ===");
if (m_progressTimer) {
m_progressTimer->stop();
delete m_progressTimer;
m_progressTimer = nullptr;
DEBUG_UI("Progress timer cleaned up");
}
// 断开所有信号连接,防止后续回调
if (m_autoFitterPSO) {
DEBUG_UI("Stopping and disconnecting PSO fitter");
// 断开所有信号连接
disconnect(m_autoFitterPSO, nullptr, nullptr, nullptr);
// 如果还在运行,停止它
if (m_autoFitterPSO->isRunning()) {
m_autoFitterPSO->stopFitting();
// 等待停止完成
int waitCount = 0;
while (m_autoFitterPSO->isRunning() && waitCount < 50) {
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100);
waitCount++;
}
// 如果仍在运行则强制停止
if (m_autoFitterPSO->isRunning()) {
DEBUG_UI("Force stopping PSO - timeout reached");
}
}
delete m_autoFitterPSO;
m_autoFitterPSO = nullptr;
DEBUG_UI("PSO fitter cleaned up");
}
// 清理进度监控
if (m_progressMonitor) {
// 先断开进度监控的信号连接
disconnect(m_progressMonitor, nullptr, nullptr, nullptr);
m_progressMonitor->hide();
delete m_progressMonitor;
m_progressMonitor = nullptr;
DEBUG_UI("Progress monitor cleaned up");
}
if (m_progressDialog) {
m_progressDialog->hide();
delete m_progressDialog;
m_progressDialog = nullptr;
DEBUG_UI("Progress dialog cleaned up");
}
DEBUG_UI("=== CLEANUP FITTING END ===");
}
void nmWxAutomaticFitting::updateBestParametersToTable()
{
QVector<double> bestSolution;
// 获取最佳解决方案
if (m_autoFitterPSO) {
bestSolution = m_autoFitterPSO->getBestSolution();
}
if (bestSolution.isEmpty()) return;
// 获取启用的参数索引
QVector<int> enabledParams;
if(m_kCheckBox->isChecked()) enabledParams.append(0); // 渗透率
if(m_sCheckBox->isChecked()) enabledParams.append(1); // 表皮系数
if(m_cCheckBox->isChecked()) enabledParams.append(2); // 井筒储集系数
if(m_phiCheckBox->isChecked()) enabledParams.append(3); // 孔隙度
if(m_hCheckBox->isChecked()) enabledParams.append(4); // 储层厚度
if(m_ctCheckBox->isChecked()) enabledParams.append(5); // 综合压缩系数
if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数
if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度
// 更新参数值和范围
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {
int paramIndex = enabledParams[i];
double bestValue = bestSolution[i];
if(!nmAutoFitUiIsFinite(bestValue)) {
continue;
}
// 更新初始值
m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4));
// 自动范围模式下,以拟合结果为中心复用首次建范围的规则;手工范围由用户保留。
if(m_autoParameterRanges) {
updateRangeForParameter(paramIndex, bestValue);
}
}
// 保存更新
nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData);
// 更新完成后,通知参数界面刷新
nmWxParameterProperty::notifyUpdateTable();
}