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

1545 lines
55 KiB
C++

#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
}
// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。
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;
}
// 根据初值生成首次或拟合后的建议范围,并始终限制在物理边界内。
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue, bool afterFit)
{
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 = afterFit ? 1.0 : 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 = afterFit ? 0.5 : 0.1;
const double upperFactor = afterFit ? 2.0 : 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, false);
} 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, false);
} 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 || minValue > physicalMax
|| initialValue < physicalMin || initialValue > physicalMax
|| maxValue < physicalMin || 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, false);
updateRangeForParameter(2, wellboreStorageValue, false);
}
}
}
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);
3 months ago
//// 特定井名时使用快速路径
//if (targetWellName == "VerticalWell1") {
3 months ago
// m_autoFitterPSO->setSimulationMode(true);
3 months ago
// // 构建目标参数向量(按启用参数的顺序)
// QVector<double> targetParams;
3 months ago
// 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); // 孔隙度
3 months ago
// 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, true);
}
}
// 保存更新
nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData);
// 更新完成后,通知参数界面刷新
nmWxParameterProperty::notifyUpdateTable();
}