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

1429 lines
57 KiB
C++

#include "nmWxAutomaticFitting.h"
#include "nmCalculationAutoFitPSO.h"
#include "nmCalculationAutoFitLM.h"
#include "nmWxAutomaticfittingStart.h"
#include "nmWxParameterProperty.h"
#include "nmAttrRegistry.h"
#include "nmDataAnalyzeManager.h"
#include "iSubWndFitting.h"
#include "iSysParaHelper.h"
#include "iParameter.h"
#include "mModuleDefines.h"
#include "mGui/mGuiAnal/iAnalRun.h"
#include "mAlgDefines.h"
#pragma comment(lib, "mGuiAnal.lib")
#include <QApplication>
#include <QDesktopWidget>
#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& minValue, double& maxValue)
{
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;
}
}
// 隐藏参数取消勾选并禁用;新显示的参数默认勾选,保留已显示参数的手工选择。
void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, bool visible)
{
if(!table || row < 0 || row >= table->rowCount()) {
return;
}
const bool wasHidden = table->isRowHidden(row);
table->setRowHidden(row, !visible);
QCheckBox* checkBox = qobject_cast<QCheckBox*>(table->cellWidget(row, 1));
if(checkBox) {
// 与当前采样重叠的参数不能参与拟合,切换目标井时也保持互斥。
const QString samplingNames[] = {"k", "Skin", "C", QString(QChar(0x03c6))};
const bool sampled = m_sensitivitySettings && row < 4 && m_sampledParameters.contains(samplingNames[row]);
checkBox->setEnabled(visible && !sampled);
if(!visible || sampled) {
checkBox->setChecked(false);
} else if(wasHidden) {
checkBox->setChecked(!m_sensitivitySettings);
}
}
}
// 按当前模型和井型可用的参数行重新整理显示序号。
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);
}
}
// 拟合表仅包含可优化参数,按模型和目标井类型控制显示。
void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType)
{
if(!table) {
return;
}
// 裂缝参数直接按井型设置,避免先显示再隐藏造成选择状态重置。
for(int row = 0; row < 4; ++row) {
setParameterRowVisible(table, row, true);
}
setParameterRowVisible(table, 4, false); // 原含水饱和度索引保留,不再参与拟合。
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
bool showFractureConductivity = false;
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(manager && m_targetWellCombo) {
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->currentText());
if(targetWell) {
NM_WELL_MODEL wellType = targetWell->getWellType();
showFractureConductivity =
wellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
wellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
setParameterRowVisible(table, 5, showFractureConductivity); // Dfc
setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长
renumberVisibleParameterRows(table);
}
// 获取可拟合参数的系统物理边界,索引 4 不再作为拟合参数。
bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
double& minValue, double& maxValue)
{
static const char* parameterNames[] = {
"Result_K", "Result_W_Skin", "Result_W_C", "Result_phi",
"", "Result_W_Dfc",
"W_FractureHalfLength"
};
if(parameterIndex < 0 || parameterIndex >= 7 || parameterIndex == 4) {
return false;
}
if(!nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], minValue, maxValue)) {
return false;
}
// 自动拟合的表皮搜索下界固定为 0,自动范围和手工输入校验使用同一边界。
if(parameterIndex == 1) {
minValue = 0.0;
}
return true;
}
// 将一组上下界同步到表格和自动拟合数据,保证 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 5:
automaticFittingData.getFractureConductivityMin().setValue(minValue);
automaticFittingData.getFractureConductivityMax().setValue(maxValue);
break;
case 6:
automaticFittingData.getFractureHalfLengthMin().setValue(minValue);
automaticFittingData.getFractureHalfLengthMax().setValue(maxValue);
break;
default:
break;
}
m_updatingParameterRanges = wasUpdatingRanges;
}
// 根据当前初值生成建议搜索范围,并始终截断在拟合边界内。skin 下界为 0,
// 上界按初值加固定宽度,其余正值参数使用倍率范围;每次打开窗口和拟合完成后复用。
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 7
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) {
return;
}
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 = physicalMin;
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0
&& !(parameterIndex == 5 && 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 == 5) {
// Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。
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 < 7; ++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;
if(getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)
&& 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 >= 7) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
"", "Fracture conductivity",
"Fracture half length"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? 7 : 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;
if(!getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax)
|| !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;
}
// 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。
if(currentParameterIndex == 5 && m_dfcCheckBox->isChecked()
&& (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) {
errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.")
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
}
return true;
}
nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSettings* settings,
const QStringList& sampledParameters)
: iDlgBase(parent)
, m_sensitivitySettings(settings)
, m_sampledParameters(sampledParameters)
, m_autoFitterPSO(nullptr)
, m_autoFitterLM(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));
// 获取数据
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy();
automaticFittingData = pManager->getAutomaticFittingDataCopy();
// 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true;
setupUI();
// 敏感性入口使用“确定”保存设置,普通入口仍直接开始拟合。
if(m_sensitivitySettings) m_okBtn->setText(tr("OK"));
setWindowTitle(tr("Automatic fitting"));
setModal(true);
// 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。
resize(sizeHint());
if(m_targetWellCombo->count() > 0) {
onWellSelected(0); // 默认选中第一口井
}
// 每次打开都按当前模型参数生成范围,不沿用上次的上下限。
initializeSuggestedParameterRanges();
if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings();
m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed");
}
void nmWxAutomaticFitting::loadSensitivitySettings()
{
// 先恢复目标井,再覆盖默认范围与初值,保留上次确认的完整设置。
m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell));
automaticFittingData = m_sensitivitySettings->parameters;
m_autoParameterRanges = false;
m_algorithmCombo->setCurrentIndex(automaticFittingData.getAlgorithmName() == "LM" ? 1 : 0);
m_iterationEdit->setText(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
m_errorLimitEdit->setText(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble(), 'g', 10));
const int rows[] = {0, 1, 2, 3, 5, 6};
QCheckBox* checks[] = {m_kCheckBox, m_sCheckBox, m_cCheckBox, m_phiCheckBox, m_dfcCheckBox, m_fractureHalfLengthCheckBox};
const bool selected[] = {automaticFittingData.getPermeabilitySelected(), automaticFittingData.getSkinSelected(),
automaticFittingData.getWellboreStorageSelected(), automaticFittingData.getPorositySelected(),
automaticFittingData.getFractureConductivitySelected(), automaticFittingData.getFractureHalfLengthSelected()};
nmDataAttribute* lower[] = {&automaticFittingData.getPermeabilityMin(), &automaticFittingData.getSkinMin(),
&automaticFittingData.getWellboreStorageMin(), &automaticFittingData.getPorosityMin(),
&automaticFittingData.getFractureConductivityMin(), &automaticFittingData.getFractureHalfLengthMin()};
nmDataAttribute* upper[] = {&automaticFittingData.getPermeabilityMax(), &automaticFittingData.getSkinMax(),
&automaticFittingData.getWellboreStorageMax(), &automaticFittingData.getPorosityMax(),
&automaticFittingData.getFractureConductivityMax(), &automaticFittingData.getFractureHalfLengthMax()};
for(int i = 0; i < 6; ++i) {
checks[i]->setChecked(selected[i] && checks[i]->isEnabled());
setParameterRange(rows[i], lower[i]->getValue().toDouble(), upper[i]->getValue().toDouble());
m_parameterTable->item(rows[i], 3)->setText(QString::number(m_sensitivitySettings->initialValues[i], 'g', 10));
}
}
QSize nmWxAutomaticFitting::sizeHint() const
{
// 沿用项目的可用屏幕尺寸计算方式,优先取父窗口所在屏幕,支持多显示器。
QDesktopWidget* desktop = QApplication::desktop();
if(desktop == nullptr) return iDlgBase::sizeHint();
const QRect available = desktop->availableGeometry(parentWidget() ? parentWidget()->window() : this);
return QSize(available.width() * 45 / 100, available.height() * 55 / 100);
}
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);
}
if (m_autoFitterLM) {
disconnect(m_autoFitterLM, nullptr, this, nullptr);
}
DEBUG_UI("AutoFitting destructor - completed");
}
void nmWxAutomaticFitting::setupUI()
{
// 用浅底色区分白色设置区和参数区,避免所有内容铺在同一平面。
setObjectName("autoFittingSettings");
setFont(QFont("Microsoft YaHei", 9));
setStyleSheet(
"#autoFittingSettings { background: #f3f5f8; color: #273449; }"
"#autoFittingSettings QFrame#fittingControls, #autoFittingSettings QFrame#fittingParameters { background: #ffffff; border: 1px solid #dce2ea; border-radius: 7px; }"
"#autoFittingSettings QLabel { color: #667487; background: transparent; border: none; }"
"#autoFittingSettings QLabel#parameterHeading { color: #273449; font-weight: bold; }"
"#autoFittingSettings QLineEdit, #autoFittingSettings QComboBox { background: #ffffff; color: #273449; border: 1px solid #cfd7e2; border-radius: 4px; padding: 5px 8px; min-height: 20px; selection-background-color: #dceafb; selection-color: #273449; }"
"#autoFittingSettings QLineEdit:focus, #autoFittingSettings QComboBox:focus { border-color: #3978c6; }"
"#autoFittingSettings QComboBox::drop-down { border: none; width: 24px; }"
"#autoFittingSettings QComboBox QAbstractItemView { background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }"
"#autoFittingSettings QTableWidget { border: none; background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }"
"#autoFittingSettings QHeaderView::section { background: #f5f7fa; color: #667487; border: none; border-bottom: 1px solid #e5eaf1; padding: 7px; }"
"#autoFittingSettings QTableWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 8px; }"
"#autoFittingSettings QCheckBox { color: #273449; background: transparent; padding-left: 10px; spacing: 8px; }"
"#autoFittingSettings QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 4px; padding: 6px 18px; min-width: 64px; min-height: 20px; }"
"#autoFittingSettings QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }"
"#autoFittingSettings QPushButton:pressed { background: #dce8f8; }"
"#autoFittingSettings QPushButton#startFitting { background: #3978c6; color: #ffffff; border-color: #3978c6; }"
"#autoFittingSettings QPushButton#startFitting:hover { background: #306bb5; }"
"#autoFittingSettings QPushButton#startFitting:pressed { background: #285b9c; }");
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(16, 16, 16, 14);
m_mainLayout->setSpacing(12);
setupControlPanel();
QFrame* controlsPanel = new QFrame(this);
controlsPanel->setObjectName("fittingControls");
m_controlLayout->setContentsMargins(16, 14, 16, 14);
controlsPanel->setLayout(m_controlLayout);
m_mainLayout->addWidget(controlsPanel);
setupParameterTable();
QFrame* tablePanel = new QFrame(this);
tablePanel->setObjectName("fittingParameters");
QVBoxLayout* tableLayout = new QVBoxLayout(tablePanel);
tableLayout->setContentsMargins(12, 12, 12, 8);
tableLayout->setSpacing(10);
QLabel* tableHeading = new QLabel(tr("Fitting parameters"), tablePanel);
tableHeading->setObjectName("parameterHeading");
tableLayout->addWidget(tableHeading);
tableLayout->addWidget(m_parameterTable, 1);
m_mainLayout->addWidget(tablePanel, 1);
setupButtons();
setLayout(m_mainLayout);
}
void nmWxAutomaticFitting::setupParameterTable()
{
// 敏感性设置首次打开时不预选参数,普通拟合仍默认全选,再按井型隐藏不可用参数。
m_parameterTable = new QTableWidget(7, 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(false);
m_parameterTable->setShowGrid(false);
m_parameterTable->verticalHeader()->setDefaultSectionSize(36);
m_parameterTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
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, 180); // 参数名称与复选框保留足够空间
m_parameterTable->setColumnWidth(5, 80); // 单位列宽度
// 只改变视觉顺序,保留数据读写使用的原列索引。
m_parameterTable->setColumnHidden(0, true);
header->moveSection(header->visualIndex(3), header->visualIndex(2));
// 设置表格的大小策略
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(!m_sensitivitySettings);
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("mD")));
// 表皮系数 (Skin)
m_parameterTable->setItem(1, 0, new QTableWidgetItem("2"));
m_sCheckBox = new QCheckBox(tr("Skin"));
m_sCheckBox->setChecked(!m_sensitivitySettings);
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(!m_sensitivitySettings);
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(!m_sensitivitySettings);
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(""));
// 索引 4 留空,保持裂缝参数与优化器的现有索引一致。
// 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中
// 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity"));
m_dfcCheckBox->setChecked(!m_sensitivitySettings);
m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem());
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(tr("md.m")));
// 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length"));
m_fractureHalfLengthCheckBox->setChecked(!m_sensitivitySettings);
m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem());
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(tr("m")));
// 设置表格行为
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 QGridLayout();
m_controlLayout->setHorizontalSpacing(16);
m_controlLayout->setVerticalSpacing(6);
// 算法选择
QLabel* algorithmLabel = new QLabel(tr("Algorithm:"));
m_algorithmCombo = new QComboBox();
m_algorithmCombo->addItem(tr("PSO (Particle Swarm)"));
m_algorithmCombo->addItem(tr("LM"));
m_algorithmCombo->setCurrentIndex(0);
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
// 迭代次数
m_iterationLabel = new QLabel();
m_iterationEdit = new QLineEdit(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
// 误差上限
QLabel* errorLabel = new QLabel(tr("Target error:"));
m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble()));
// 目标井选择
QLabel* wellLabel = new QLabel(tr("Target Well:"));
m_targetWellCombo = new QComboBox();
// 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
if(pManager != nullptr) {
const QString sPrimaryWellCode = pManager->getPrimaryWellCode();
nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode);
if(pPrimaryWell != nullptr) {
m_targetWellCombo->addItem(pPrimaryWell->getWellName());
}
if(pManager->getIncludeOtherWells()) {
const QVector<nmCalculationWellRef> vecIncludedWells =
pManager->getIncludedCalculationWells();
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
const QString& sWellCode = vecIncludedWells[nIndex].m_sWellCode;
nmDataWellBase* pWellData = pManager->findWellByCode(sWellCode);
if(pWellData == nullptr || sWellCode == sPrimaryWellCode ||
pWellData->getFlowSegmentCount() <= 0 ||
pWellData->getPressurePoints().size() < 2) {
continue;
}
m_targetWellCombo->addItem(pWellData->getWellName());
}
}
}
connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_targetWellCombo->setMinimumContentsLength(10);
m_controlLayout->addWidget(wellLabel, 0, 0);
m_controlLayout->addWidget(m_targetWellCombo, 1, 0);
m_controlLayout->addWidget(algorithmLabel, 0, 1);
m_controlLayout->addWidget(m_algorithmCombo, 1, 1);
m_controlLayout->addWidget(errorLabel, 0, 2);
m_controlLayout->addWidget(m_errorLimitEdit, 1, 2);
m_controlLayout->addWidget(m_iterationLabel, 0, 3);
m_controlLayout->addWidget(m_iterationEdit, 1, 3);
for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1);
onAlgorithmChanged(m_algorithmCombo->currentIndex());
}
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
{
// 两种算法统一显示最大迭代次数,提示仍说明 LM 共用次数的含义。
const bool usePSO = (index == 0);
m_iterationLabel->setText(tr("Maximum iterations:"));
m_iterationEdit->setToolTip(usePSO ? QString() : tr("Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times."));
}
void nmWxAutomaticFitting::setupButtons()
{
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_reverseBtn = new QPushButton(tr("Reverse Selection"));
m_okBtn = new QPushButton(tr("Start fitting"));
m_okBtn->setObjectName("startFitting");
m_okBtn->setDefault(true);
m_cancelBtn = new QPushButton(tr("Cancel"));
// 设置按钮大小
m_reverseBtn->setMinimumWidth(120);
m_okBtn->setMinimumWidth(80);
m_cancelBtn->setMinimumWidth(80);
buttonLayout->addWidget(m_reverseBtn);
buttonLayout->addStretch();
buttonLayout->addWidget(m_cancelBtn);
buttonLayout->addWidget(m_okBtn);
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_kCheckBox->isEnabled()) m_kCheckBox->setChecked(!m_kCheckBox->isChecked());
if(m_sCheckBox->isEnabled()) m_sCheckBox->setChecked(!m_sCheckBox->isChecked());
if(m_cCheckBox->isEnabled()) m_cCheckBox->setChecked(!m_cCheckBox->isChecked());
if(m_phiCheckBox->isEnabled()) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked());
if(m_dfcCheckBox->isEnabled()) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked());
if(m_fractureHalfLengthCheckBox->isEnabled()) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->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();
if(m_sensitivitySettings) {
// 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。
m_sensitivitySettings->parameters = automaticFittingData;
m_sensitivitySettings->targetWell = m_targetWellCombo->currentText();
m_sensitivitySettings->initialValues.clear();
const int rows[] = {0, 1, 2, 3, 5, 6};
for(int i = 0; i < 6; ++i)
m_sensitivitySettings->initialValues.append(m_parameterTable->item(rows[i], 3)->text().toDouble());
m_sensitivitySettings->confirmed = true;
accept();
return;
}
// 更新完成后,通知参数界面刷新
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;
}
const bool usePseudoPressure = (pManager->getSolverModelType() == SMT_Gas_VariablePvt);
if(usePseudoPressure) {
iSubWndFitting* pFitting = nmDataAnalyzeManager::getCurrentFitting();
iAnalRun* pAnalRun = pFitting ? pFitting->getAnalRun() : nullptr;
// 气井求解结果的后处理仍需初始化当前分析的拟压力转换器。
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;
}
}
// 开始拟合前重算气井目标,避免旧缓存或已修改的拟压力配置造成量纲不一致。
if(usePseudoPressure && !pManager->updateWellHistoryData(pTargetWell)) {
QMessageBox::warning(this, tr("Warning"),
tr("Gas pseudo-pressure data is unavailable or invalid."));
return;
}
// 油、气、水井统一读取所选目标井的历史曲线,不依赖主界面显示的结果井。
QVector<QVector<double> > 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_dfcCheckBox->isChecked() ||
m_fractureHalfLengthCheckBox->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_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity");
if(m_fractureHalfLengthCheckBox->isChecked()) selectedParameterNames << tr("Fracture half length");
// 启动自动拟合 - 传递双对数历史数据
startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName);
}
void nmWxAutomaticFitting::onReject()
{
reject();
}
void nmWxAutomaticFitting::onWellSelected(int index)
{
// 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(!manager) {
return;
}
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index));
if(!targetWell) {
return;
}
const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true;
updateParameterVisibility(m_parameterTable, manager->getSolverModelType());
nmDataPerforation* perforation = targetWell->getPerforation(0);
if(perforation) {
const double skin = perforation->getSkin().getValue().toDouble();
m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10));
if(m_autoParameterRanges) {
updateRangeForParameter(1, skin);
}
}
const double storage = targetWell->getWellboreStorage().getValue().toDouble();
m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10));
if(m_autoParameterRanges) {
updateRangeForParameter(2, storage);
}
bool fracturedWell = false;
double conductivity = 0.0;
double halfLength = 0.0;
if(nmDataVerticalFracturedWell* well = dynamic_cast<nmDataVerticalFracturedWell*>(targetWell)) {
conductivity = well->getDfc().getValue().toDouble();
halfLength = well->getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
} else if(nmDataHorizontalFracturedWell* well = dynamic_cast<nmDataHorizontalFracturedWell*>(targetWell)) {
conductivity = well->getDfc().getValue().toDouble();
halfLength = well->getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
}
if(fracturedWell) {
m_parameterTable->item(5, 3)->setText(QString::number(conductivity, 'g', 10));
m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10));
if(m_autoParameterRanges) {
updateRangeForParameter(5, conductivity);
updateRangeForParameter(6, halfLength);
}
}
m_updatingParameterRanges = wasUpdatingRanges;
}
void nmWxAutomaticFitting::setAutomaticFittingValue()
{
// 保存剩余拟合参数的选择状态,LM 和 PSO 共用此配置。
automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked());
automaticFittingData.setSkinSelected(m_sCheckBox->isChecked());
automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked());
automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked());
// 界面不再提供 PSO 加速选项,同时关闭旧配置中可能保留的加速开关。
automaticFittingData.setSurrogateScreeningEnabled(false);
// 保存渗透率的最小值和最大值
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.getFractureConductivityMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存裂缝半长的最小值和最大值
automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
// 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。
if(m_sensitivitySettings) {
automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO");
return;
}
// 只写回可拟合的渗透率、孔隙度,含水饱和度沿用模型当前值。
reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率
reservoirData.getPorosity().setValue(m_parameterTable->item(3, 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();
double newFractureConductivityValue = m_parameterTable->item(5, 3)->text().toDouble();
double newFractureHalfLengthValue = m_parameterTable->item(6, 3)->text().toDouble();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
nmDataWellBase* pTargetWell = manager->findWellByName(selectedWellName);
if(pTargetWell) {
// 先修改井参数副本,最后写回原对象中的现有属性,保持射孔对象地址不变。
nmDataPerforation* perf = pTargetWell->getPerforation(0);
if(perf) {
nmDataAttribute skinAttr = perf->getSkin();
skinAttr.setValue(newSkinValue);
perf->getSkin().setValue(skinAttr.getValue());
}
// 更新井筒储集系数
nmDataAttribute wellboreAttr = pTargetWell->getWellboreStorage();
wellboreAttr.setValue(newWellboreStorageValue);
pTargetWell->getWellboreStorage().setValue(wellboreAttr.getValue());
NM_WELL_MODEL wellType = pTargetWell->getWellType();
// Dfc 属于压裂井对象;只修改这一项,裂缝位置、长度和段数保持原值。
if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->getDfc().setValue(dfc.getValue());
nmDataAttribute halfLength = fracturedWell->getFractureHalfLength();
halfLength.setValue(newFractureHalfLengthValue);
fracturedWell->getFractureHalfLength().setValue(halfLength.getValue());
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->getDfc().setValue(dfc.getValue());
nmDataAttribute halfLength = fracturedWell->getFractureHalfLength();
halfLength.setValue(newFractureHalfLengthValue);
fracturedWell->getFractureHalfLength().setValue(halfLength.getValue());
}
}
// 全部井参数落地后统一刷新一次右侧参数面板。
if(manager->getAttrRegistry()) {
manager->getAttrRegistry()->refreshAll();
}
}
}
}
void nmWxAutomaticFitting::startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName)
{
DEBUG_UI("=== START AUTO FITTING ===");
// 先清理之前的实例
cleanupFitting();
const bool useLM = m_algorithmCombo && m_algorithmCombo->currentIndex() == 1;
if(useLM) {
DEBUG_UI("Creating LM auto fitter");
m_autoFitterLM = new nmCalculationAutoFitLM(this);
m_autoFitterLM->setTargetLogLogData(targetData);
m_autoFitterLM->setTargetWellName(targetWellName);
} else {
DEBUG_UI("Creating PSO auto fitter");
m_autoFitterPSO = new nmCalculationAutoFitPSO(this);
m_autoFitterPSO->setTargetLogLogData(targetData);
m_autoFitterPSO->setPSOTargetWellName(targetWellName);
}
5 months ago
//// 特定井名时使用快速路径
//if (targetWellName == "VerticalWell1") {
5 months ago
// m_autoFitterPSO->setSimulationMode(true);
5 months ago
// // 构建目标参数向量(按启用参数的顺序)
// QVector<double> targetParams;
5 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); // 孔隙度
5 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);
if(m_autoFitterLM) {
m_progressMonitor->setAutoFitter(m_autoFitterLM);
} else {
m_progressMonitor->setAutoFitter(m_autoFitterPSO);
}
m_progressMonitor->setPseudoPressureMode(
nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt);
m_progressMonitor->setTargetLogLogData(targetData);
if(m_autoFitterLM) {
connect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
} else {
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);
// 单位和勾选参数保持同一顺序,供横排参数区显示。
QStringList selectedUnits;
if(m_kCheckBox->isChecked()) selectedUnits << tr("mD");
if(m_sCheckBox->isChecked()) selectedUnits << QString();
if(m_cCheckBox->isChecked()) selectedUnits << tr("m^3/MPa");
if(m_phiCheckBox->isChecked()) selectedUnits << QString();
if(m_dfcCheckBox->isChecked()) selectedUnits << tr("md.m");
if(m_fractureHalfLengthCheckBox->isChecked()) selectedUnits << tr("m");
m_progressMonitor->setSelectedParameters(selectedParams, selectedUnits);
m_progressMonitor->show();
QTimer::singleShot(100, this, SLOT(runAutoFitting()));
}
void nmWxAutomaticFitting::runAutoFitting()
{
if (m_progressMonitor) {
m_progressMonitor->markFittingStarted();
}
if(m_autoFitterPSO) {
m_autoFitterPSO->startAutoFitting();
} else if(m_autoFitterLM) {
m_autoFitterLM->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 (m_autoFitterLM) {
disconnect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
}
// 拟合候选值通过参数副本写回,结束时统一把最终值同步到右侧参数面板。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(manager && manager->getAttrRegistry()) {
manager->getAttrRegistry()->refreshAll();
}
// 失败状态不代表参数未写回;初值与范围始终以模型实际保留的数据为准。
refreshParametersFromModel();
if(success) {
QString resultInfo;
if(m_autoFitterPSO || m_autoFitterLM) {
double bestFitness = m_autoFitterLM
? m_autoFitterLM->getBestFitness()
: m_autoFitterPSO->getBestFitness();
const QString algorithmName = m_autoFitterLM ? "LM" : "PSO";
// 检查是否是用户停止的情况
if(message.contains("stopped by user", Qt::CaseInsensitive)) {
resultInfo = tr("%1 Optimization stopped by user:\n").arg(algorithmName);
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("%1 Optimization completed:\n").arg(algorithmName);
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();
} else if(m_autoFitterLM && m_autoFitterLM->isRunning()) {
m_autoFitterLM->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_autoFitterLM) {
DEBUG_UI("Stopping and disconnecting LM fitter");
disconnect(m_autoFitterLM, nullptr, nullptr, nullptr);
if (m_autoFitterLM->isRunning()) {
m_autoFitterLM->stopFitting();
int waitCount = 0;
while (m_autoFitterLM->isRunning() && waitCount < 50) {
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100);
waitCount++;
}
}
delete m_autoFitterLM;
m_autoFitterLM = nullptr;
DEBUG_UI("LM 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::refreshParametersFromModel()
{
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(!manager) {
return;
}
// 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。
reservoirData = manager->getReservoirDataCopy();
const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true;
m_parameterTable->item(0, 3)->setText(QString::number(
reservoirData.getPermeability().getValue().toDouble(), 'g', 10));
m_parameterTable->item(3, 3)->setText(QString::number(
reservoirData.getPorosity().getValue().toDouble(), 'g', 10));
// 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。
// 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。
m_autoParameterRanges = false;
onWellSelected(m_targetWellCombo->currentIndex());
m_autoParameterRanges = true;
initializeSuggestedParameterRanges();
m_updatingParameterRanges = wasUpdatingRanges;
manager->updateAutomaticFittingData(automaticFittingData);
nmWxParameterProperty::notifyUpdateTable();
}