#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 #include #include #include #ifdef Q_OS_WIN #include #define DEBUG_UI(msg) OutputDebugStringA(QString("[UI] %1\n").arg(msg).toLocal8Bit().data()) #endif namespace { bool nmAutoFitUiIsFinite(double value) { #ifdef Q_OS_WIN return _finite(value) != 0; #else return std::isfinite(value); #endif } // 物理边界可能由多个浮点配置量计算得到,界面十进制文本再转回 double 后会有 // 末位舍入差。这里只放宽约 8 个机器精度,不放宽实际物理范围。 bool nmAutoFitUiNearlyEqual(double left, double right) { const double scale = qMax(std::fabs(left), std::fabs(right)); return std::fabs(left - right) <= DBL_EPSILON * 8.0 * scale; } // 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。 bool nmAutoFitReadPhysicalRange(const char* parameterName, double fallbackMin, double fallbackMax, double& minValue, double& maxValue) { minValue = fallbackMin; maxValue = fallbackMax; iSysParaHelper* paraHelper = _paraHelper; if(!paraHelper) { return false; } iParameter* parameter = paraHelper->getPara(QString::fromLatin1(parameterName), s_Nm_Serie); if(!parameter || !nmAutoFitUiIsFinite(parameter->m_dMin) || !nmAutoFitUiIsFinite(parameter->m_dMax) || parameter->m_dMin >= parameter->m_dMax) { return false; } minValue = parameter->m_dMin; maxValue = parameter->m_dMax; return true; } // 主界面气井双对数中的源曲线和导数曲线已经采用拟压力量纲. // 自动拟合直接读取这两条显示曲线, 避免井对象中的原始压力历史数据与结果曲线量纲不一致. // 两条曲线可能因无效点过滤而长度不同, 因此按时间坐标匹配共同有效点. bool getMainHistoryLogLog(iSubWndFitting* fitting, QVector >& 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(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 sourcePoints = sourceCurve->getAllValues(); QVector 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(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 除以 1000,h 由 ft 换成 m, // 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。 // Ct/Cf/Swi 沿用模型参数表边界。 static const double physicalMin[] = { 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0 }; static const double physicalMax[] = { 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0 }; if(parameterIndex < 0 || parameterIndex >= 8) { return false; } minValue = physicalMin[parameterIndex]; maxValue = physicalMax[parameterIndex]; bool rangeRead = true; if(parameterIndex >= 5) { rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue); } if(parameterIndex == 7) { double soi = reservoirData.getSoi().getValue().toDouble(); double sgi = reservoirData.getSgi().getValue().toDouble(); if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) { if(soi < 0.0 || sgi < 0.0 || soi + sgi > 1.0) { // Soi+Sgi 超过 1 时没有可行的 Swi,固定到物理下限,避免继续搜索非法区间。 minValue = 0.0; maxValue = 0.0; } else { maxValue = qMin(maxValue, 1.0 - soi - sgi); } } } return rangeRead; } // 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。 void nmWxAutomaticFitting::setParameterRange(int parameterIndex, double minValue, double maxValue) { if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterTable->rowCount() || !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(maxValue)) { return; } if(maxValue < minValue) { qSwap(minValue, maxValue); } const bool wasUpdatingRanges = m_updatingParameterRanges; m_updatingParameterRanges = true; if(m_parameterTable->item(parameterIndex, 2)) { m_parameterTable->item(parameterIndex, 2)->setText(QString::number(minValue, 'g', 10)); } if(m_parameterTable->item(parameterIndex, 4)) { m_parameterTable->item(parameterIndex, 4)->setText(QString::number(maxValue, 'g', 10)); } switch(parameterIndex) { case 0: automaticFittingData.getPermeabilityMin().setValue(minValue); automaticFittingData.getPermeabilityMax().setValue(maxValue); break; case 1: automaticFittingData.getSkinMin().setValue(minValue); automaticFittingData.getSkinMax().setValue(maxValue); break; case 2: automaticFittingData.getWellboreStorageMin().setValue(minValue); automaticFittingData.getWellboreStorageMax().setValue(maxValue); break; case 3: automaticFittingData.getPorosityMin().setValue(minValue); automaticFittingData.getPorosityMax().setValue(maxValue); break; case 4: automaticFittingData.getThicknessMin().setValue(minValue); automaticFittingData.getThicknessMax().setValue(maxValue); break; case 5: automaticFittingData.getCtMin().setValue(minValue); automaticFittingData.getCtMax().setValue(maxValue); break; case 6: automaticFittingData.getCfMin().setValue(minValue); automaticFittingData.getCfMax().setValue(maxValue); break; case 7: automaticFittingData.getSwiMin().setValue(minValue); automaticFittingData.getSwiMax().setValue(maxValue); break; default: break; } m_updatingParameterRanges = wasUpdatingRanges; } // 根据当前初值生成建议搜索范围,并始终截断在系统物理边界内。skin 使用 // 加减固定宽度,其余正值参数使用倍率范围;该规则在首次加载和拟合完成后复用。 void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double centerValue) { if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8 || !nmAutoFitUiIsFinite(centerValue)) { return; } double physicalMin = 0.0; double physicalMax = 0.0; getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); double reference = centerValue; const bool positiveParameter = parameterIndex != 1; // 正值参数初值为 0 时不再借用旧的界面范围,直接从物理边界选取搜索尺度。 if(positiveParameter && reference <= 0.0) { if(physicalMin > 0.0) { reference = physicalMin; } else { reference = qMax(physicalMax * 0.01, 1.0e-12); } } if(physicalMax < physicalMin) { return; } reference = qBound(physicalMin, reference, physicalMax); const double boundedCenterValue = qBound(physicalMin, centerValue, physicalMax); double newMin = physicalMin; double newMax = physicalMax; if(parameterIndex == 1) { const double skinHalfRange = 10.0; newMin = qMax(physicalMin, reference - skinHalfRange); newMax = qMin(physicalMax, reference + skinHalfRange); } else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) { const double lowerFactor = 0.1; const double upperFactor = 10.0; newMin = qMax(physicalMin, reference * lowerFactor); newMax = qMin(physicalMax, reference * upperFactor); } else if(parameterIndex == 7) { // 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。 newMin = physicalMin; newMax = physicalMax; } // 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。 newMin = qMin(newMin, boundedCenterValue); newMax = qMax(newMax, boundedCenterValue); newMin = qMax(newMin, physicalMin); newMax = qMin(newMax, physicalMax); if(newMax >= newMin) { setParameterRange(parameterIndex, newMin, newMax); } } // 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。 void nmWxAutomaticFitting::initializeSuggestedParameterRanges() { if(!m_parameterTable) { return; } for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); if(initialItem) { bool initialOk = false; const double initialValue = initialItem->text().toDouble(&initialOk); if(initialOk && nmAutoFitUiIsFinite(initialValue)) { updateRangeForParameter(parameterIndex, initialValue); } else { // 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。 double physicalMin = 0.0; double physicalMax = 0.0; getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); if(physicalMax >= physicalMin) { setParameterRange(parameterIndex, physicalMin, physicalMax); } } } } } // 校正已保存的范围:保留物理边界内的用户区间,无交集时按当前初值生成兜底区间。 void nmWxAutomaticFitting::normalizeSavedParameterRanges() { if(!m_parameterTable) { return; } for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2); QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4); QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); if(!minItem || !maxItem || !initialItem) { continue; } double physicalMin = 0.0; double physicalMax = 0.0; getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); bool savedMinOk = false; bool savedMaxOk = false; const double savedMin = minItem->text().toDouble(&savedMinOk); const double savedMax = maxItem->text().toDouble(&savedMaxOk); const bool savedRangeValid = savedMinOk && savedMaxOk && nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax) && savedMax >= savedMin; if(savedRangeValid && physicalMax >= physicalMin) { const double clippedMin = qMax(savedMin, physicalMin); const double clippedMax = qMin(savedMax, physicalMax); if(clippedMax >= clippedMin) { setParameterRange(parameterIndex, clippedMin, clippedMax); continue; } } // 已保存范围无效或与物理边界无交集时,按数据对象初值重新生成。 bool initialOk = false; const double initialValue = initialItem->text().toDouble(&initialOk); if(initialOk && nmAutoFitUiIsFinite(initialValue)) { updateRangeForParameter(parameterIndex, initialValue); } else if(physicalMax >= physicalMin) { setParameterRange(parameterIndex, physicalMin, physicalMax); } } } // 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。 bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex) { if(!m_parameterTable) { errorMessage = tr("The parameter table is unavailable."); return false; } if(parameterIndex < -1 || parameterIndex >= 8) { errorMessage = tr("The parameter row is invalid."); return false; } static const char* parameterNames[] = { "Permeability", "Skin", "Wellbore storage", "Porosity", "Thickness", "Ct", "Cf", "Swi" }; const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex; const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1; for(int currentParameterIndex = firstParameterIndex; currentParameterIndex < lastParameterIndex; ++currentParameterIndex) { // 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。 if(m_parameterTable->isRowHidden(currentParameterIndex)) { continue; } QTableWidgetItem* minItem = m_parameterTable->item(currentParameterIndex, 2); QTableWidgetItem* initialItem = m_parameterTable->item(currentParameterIndex, 3); QTableWidgetItem* maxItem = m_parameterTable->item(currentParameterIndex, 4); if(!minItem || !initialItem || !maxItem) { errorMessage = tr("The range values for %1 are incomplete.") .arg(tr(parameterNames[currentParameterIndex])); return false; } bool minOk = false; bool initialOk = false; bool maxOk = false; const double minValue = minItem->text().toDouble(&minOk); const double initialValue = initialItem->text().toDouble(&initialOk); const double maxValue = maxItem->text().toDouble(&maxOk); if(!minOk || !initialOk || !maxOk || !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(initialValue) || !nmAutoFitUiIsFinite(maxValue)) { errorMessage = tr("The minimum value, initial value, and maximum value of %1 must be finite numbers.") .arg(tr(parameterNames[currentParameterIndex])); return false; } double physicalMin = 0.0; double physicalMax = 0.0; getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax); if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax) || physicalMax < physicalMin) { errorMessage = tr("The physical range of %1 is invalid.") .arg(tr(parameterNames[currentParameterIndex])); return false; } if((minValue < physicalMin && !nmAutoFitUiNearlyEqual(minValue, physicalMin)) || (minValue > physicalMax && !nmAutoFitUiNearlyEqual(minValue, physicalMax)) || (initialValue < physicalMin && !nmAutoFitUiNearlyEqual(initialValue, physicalMin)) || (initialValue > physicalMax && !nmAutoFitUiNearlyEqual(initialValue, physicalMax)) || (maxValue < physicalMin && !nmAutoFitUiNearlyEqual(maxValue, physicalMin)) || (maxValue > physicalMax && !nmAutoFitUiNearlyEqual(maxValue, physicalMax))) { errorMessage = tr("The values of %1 exceed the physical range [%2, %3].") .arg(tr(parameterNames[currentParameterIndex])) .arg(QString::number(physicalMin, 'g', 10)) .arg(QString::number(physicalMax, 'g', 10)); return false; } if(minValue > maxValue || minValue > initialValue || initialValue > maxValue) { errorMessage = tr("The values of %1 must satisfy: minimum <= initial value <= maximum.") .arg(tr(parameterNames[currentParameterIndex])); return false; } } return true; } nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) : iDlgBase(parent) , m_autoFitterPSO(nullptr) , m_progressDialog(nullptr) , m_progressTimer(nullptr) , m_progressMonitor(nullptr) , m_autoParameterRanges(true) // 构造期间先禁止即时校验,避免初始值写入和范围生成之间出现短暂的不一致。 , m_updatingParameterRanges(true) { DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16)); // 加载已有井数据 QVector listWellData = nmDataAnalyzeManager::getCurrentInstance()->getWellDataList(); // 遍历并分类井数据 foreach (nmDataWellBase* well, listWellData) { if (auto vfWell = dynamic_cast(well)) { m_verticalFracturedWells.append(*vfWell); } else if (auto hfWell = dynamic_cast(well)) { m_horizontalFracturedWells.append(*hfWell); } else if (auto vWell = dynamic_cast(well)) { m_verticalWells.append(*vWell); } else if (auto hWell = dynamic_cast(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 > 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 > rstLogLogData = pTargetWell->getResultLogLog(); if(rstLogLogData.isEmpty() || rstLogLogData.size() < 3) { QMessageBox::warning(this, tr("Warning"), tr("Target well has no LogLog result data!")); return; } // 检查是否有参数被选中 bool hasSelectedParams = m_kCheckBox->isChecked() || m_sCheckBox->isChecked() || m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() || m_hCheckBox->isChecked() || m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() || m_swiCheckBox->isChecked(); if(!hasSelectedParams) { QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!")); return; } // 收集选中的参数名称 QStringList selectedParameterNames; if(m_kCheckBox->isChecked()) selectedParameterNames << tr("Permeability"); if(m_sCheckBox->isChecked()) selectedParameterNames << tr("Skin"); if(m_cCheckBox->isChecked()) selectedParameterNames << tr("Wellbore storage"); if(m_phiCheckBox->isChecked()) selectedParameterNames << tr("Porosity"); if(m_hCheckBox->isChecked()) selectedParameterNames << tr("Thickness"); if(m_ctCheckBox->isChecked()) selectedParameterNames << tr("Ct"); if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf"); if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi"); // 启动自动拟合 - 传递双对数历史数据 startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName); } void nmWxAutomaticFitting::onReject() { reject(); } void nmWxAutomaticFitting::onWellSelected(int index) { // 获取选中的井名 QString selectedWellName = m_targetWellCombo->itemText(index); // 在分类的井数据中查找匹配的井 bool found = false; double skinValue = 0.0; double wellboreStorageValue = 0.0; // 查找垂直井 for(int i = 0; i < m_verticalWells.size(); ++i) { if(m_verticalWells[i].getWellName() == selectedWellName) { skinValue = m_verticalWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_verticalWells[i].getWellboreStorage().getValue().toDouble(); found = true; break; } } // 查找水平井 if (!found) { for(int i = 0; i < m_horizontalWells.size(); ++i) { if(m_horizontalWells[i].getWellName() == selectedWellName) { skinValue = m_horizontalWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_horizontalWells[i].getWellboreStorage().getValue().toDouble(); found = true; break; } } } // 查找垂直压裂井 if (!found) { for(int i = 0; i < m_verticalFracturedWells.size(); ++i) { if(m_verticalFracturedWells[i].getWellName() == selectedWellName) { skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble(); found = true; break; } } } // 查找水平压裂井 if (!found) { for(int i = 0; i < m_horizontalFracturedWells.size(); ++i) { if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) { skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble(); found = true; break; } } } if (found) { // 更新表格数据 // 确保表格项存在 if(!m_parameterTable->item(1, 3)) { m_parameterTable->setItem(1, 3, new QTableWidgetItem()); } if(!m_parameterTable->item(2, 3)) { m_parameterTable->setItem(2, 3, new QTableWidgetItem()); } // 设置皮肤系数(Skin) m_parameterTable->item(1, 3)->setText(QString::number(skinValue)); // 设置井筒储集系数(Wellbore storage) m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue)); if(m_autoParameterRanges) { updateRangeForParameter(1, skinValue); updateRangeForParameter(2, wellboreStorageValue); } } } void nmWxAutomaticFitting::setAutomaticFittingValue() { // 保存参数选择状态 automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked()); automaticFittingData.setSkinSelected(m_sCheckBox->isChecked()); automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked()); automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked()); automaticFittingData.setThicknessSelected(m_hCheckBox->isChecked()); automaticFittingData.setCtSelected(m_ctCheckBox->isChecked()); automaticFittingData.setCfSelected(m_cfCheckBox->isChecked()); automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked()); automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1); // 保存渗透率的最小值和最大值 automaticFittingData.getPermeabilityMin().setValue(m_parameterTable->item(0, 2)->text().toDouble()); automaticFittingData.getPermeabilityMax().setValue(m_parameterTable->item(0, 4)->text().toDouble()); // 保存表皮系数的最小值和最大值 automaticFittingData.getSkinMin().setValue(m_parameterTable->item(1, 2)->text().toDouble()); automaticFittingData.getSkinMax().setValue(m_parameterTable->item(1, 4)->text().toDouble()); // 保存井筒储集系数的最小值和最大值 automaticFittingData.getWellboreStorageMin().setValue(m_parameterTable->item(2, 2)->text().toDouble()); automaticFittingData.getWellboreStorageMax().setValue(m_parameterTable->item(2, 4)->text().toDouble()); // 保存孔隙度的最小值和最大值 automaticFittingData.getPorosityMin().setValue(m_parameterTable->item(3, 2)->text().toDouble()); automaticFittingData.getPorosityMax().setValue(m_parameterTable->item(3, 4)->text().toDouble()); // 保存储层厚度的最小值和最大值 automaticFittingData.getThicknessMin().setValue(m_parameterTable->item(4, 2)->text().toDouble()); automaticFittingData.getThicknessMax().setValue(m_parameterTable->item(4, 4)->text().toDouble()); // 保存综合压缩系数的最小值和最大值 automaticFittingData.getCtMin().setValue(m_parameterTable->item(5, 2)->text().toDouble()); automaticFittingData.getCtMax().setValue(m_parameterTable->item(5, 4)->text().toDouble()); // 保存岩石压缩系数的最小值和最大值 automaticFittingData.getCfMin().setValue(m_parameterTable->item(6, 2)->text().toDouble()); automaticFittingData.getCfMax().setValue(m_parameterTable->item(6, 4)->text().toDouble()); // 保存初始含水饱和度的最小值和最大值 automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble()); automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble()); // 保存迭代参数 automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); // 保存储层数据的初值 reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率 reservoirData.getPorosity().setValue(m_parameterTable->item(3, 3)->text().toDouble()); // 孔隙度 reservoirData.getThickness().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 储层厚度 reservoirData.getCt().setValue(m_parameterTable->item(5, 3)->text().toDouble()); // 综合压缩系数 reservoirData.getCf().setValue(m_parameterTable->item(6, 3)->text().toDouble()); // 岩石压缩系数 reservoirData.getSwi().setValue(m_parameterTable->item(7, 3)->text().toDouble()); // 初始含水饱和度 // 更新储层数据(全局) nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData); // 保存自动拟合数据 nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData); // 只更新目标井的参数 QString selectedWellName = m_targetWellCombo->currentText(); if(!selectedWellName.isEmpty()) { double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble(); double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble(); // 直接从数据管理器获取目标井 nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); nmDataWellBase* pTargetWell = manager->findWellByName(selectedWellName); if(pTargetWell) { // 更新Skin nmDataPerforation* perf = pTargetWell->getPerforation(0); if(perf) { nmDataAttribute skinAttr = perf->getSkin(); skinAttr.setValue(newSkinValue); perf->setSkin(skinAttr); } // 更新井筒储集系数 nmDataAttribute wellboreAttr = pTargetWell->getWellboreStorage(); wellboreAttr.setValue(newWellboreStorageValue); pTargetWell->setWellboreStorage(wellboreAttr); // 根据井类型单独更新这一口井 NM_WELL_MODEL wellType = pTargetWell->getWellType(); if(wellType == NM_WELL_MODEL::Vertical_Well) { nmDataVerticalWell* pVerticalWell = dynamic_cast(pTargetWell); if(pVerticalWell != nullptr) { QVector wells; wells.append(*pVerticalWell); manager->updateVerticalWells(wells); } } else if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) { nmDataVerticalFracturedWell* pVerticalFracturedWell = dynamic_cast(pTargetWell); if(pVerticalFracturedWell != nullptr) { QVector wells; wells.append(*pVerticalFracturedWell); manager->updateVerticalFracturedWells(wells); } } else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { nmDataHorizontalFracturedWell* pHorizontalFracturedWell = dynamic_cast(pTargetWell); if(pHorizontalFracturedWell != nullptr) { QVector wells; wells.append(*pHorizontalFracturedWell); manager->updateHorizontalFracturedWells(wells); } } } } } void nmWxAutomaticFitting::startAutoFitting(const QVector>& targetData, const QStringList& selectedParams, const QString& targetWellName) { DEBUG_UI("=== START AUTO FITTING ==="); // 先清理之前的实例 cleanupFitting(); DEBUG_UI("Creating PSO auto fitter"); m_autoFitterPSO = new nmCalculationAutoFitPSO(this); m_autoFitterPSO->setTargetLogLogData(targetData); m_autoFitterPSO->setPSOTargetWellName(targetWellName); //// 特定井名时使用快速路径 //if (targetWellName == "VerticalWell1") { // m_autoFitterPSO->setSimulationMode(true); // // 构建目标参数向量(按启用参数的顺序) // QVector targetParams; // if(m_kCheckBox->isChecked()) targetParams.append(0.0033); // 渗透率 // if(m_sCheckBox->isChecked()) targetParams.append(1.75); // 表皮系数 // if(m_cCheckBox->isChecked()) targetParams.append(0.23); // 井筒储集系数 // if(m_phiCheckBox->isChecked()) targetParams.append(0.189); // 孔隙度 // double targetError = 0.02291; // m_autoFitterPSO->setSimulationTargetParams(targetParams, targetError); //} //else if (targetWellName == "W0009_1") { // m_autoFitterPSO->setSimulationMode(true); // // 构建目标参数向量(按启用参数的顺序) // QVector 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 bestSolution; // 获取最佳解决方案 if (m_autoFitterPSO) { bestSolution = m_autoFitterPSO->getBestSolution(); } if (bestSolution.isEmpty()) return; // 获取启用的参数索引 QVector enabledParams; if(m_kCheckBox->isChecked()) enabledParams.append(0); // 渗透率 if(m_sCheckBox->isChecked()) enabledParams.append(1); // 表皮系数 if(m_cCheckBox->isChecked()) enabledParams.append(2); // 井筒储集系数 if(m_phiCheckBox->isChecked()) enabledParams.append(3); // 孔隙度 if(m_hCheckBox->isChecked()) enabledParams.append(4); // 储层厚度 if(m_ctCheckBox->isChecked()) enabledParams.append(5); // 综合压缩系数 if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数 if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度 // 更新参数值和范围 for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) { int paramIndex = enabledParams[i]; double bestValue = bestSolution[i]; if(!nmAutoFitUiIsFinite(bestValue)) { continue; } // 更新初始值 m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4)); // 自动范围模式下,以拟合结果为中心复用首次建范围的规则;手工范围由用户保留。 if(m_autoParameterRanges) { updateRangeForParameter(paramIndex, bestValue); } } // 保存更新 nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData); // 更新完成后,通知参数界面刷新 nmWxParameterProperty::notifyUpdateTable(); }