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

1125 lines
46 KiB
C++

This file contains ambiguous Unicode characters!

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

#include "nmWxAutomaticFitting.h"
#include "nmCalculationAutoFitPSO.h"
#include "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 <QStyledItemDelegate>
#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 {
// 树节点和名称、单位列只读,仅允许编辑参数的三个数值列。
class nmFitParameterDelegate : public QStyledItemDelegate
{
public:
explicit nmFitParameterDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// 分组和参数统一增高,字号变化时也保留上下留白。
QSize size = QStyledItemDelegate::sizeHint(option, index);
size.setHeight(qMax(size.height(), qMax(32, option.fontMetrics.height() + 12)));
return size;
}
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
if(index.column() < 1 || index.column() > 3
|| !index.sibling(index.row(), 0).data(Qt::UserRole).isValid())
return nullptr;
return QStyledItemDelegate::createEditor(parent, option, index);
}
};
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;
}
}
// 获取可拟合参数的系统物理边界,索引 4 不再作为拟合参数。
bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
double& minValue, double& maxValue)
{
// 参数行按实际类型查询边界,不依赖它位于哪口井的分组。
static const char* names[] = {"Result_K", "Result_W_Skin", "Result_W_C",
"Result_phi", "Result_W_Dfc", "W_FractureHalfLength"};
const int type = automaticFittingData.getModelParameters()[parameterIndex].type;
if(!nmAutoFitReadPhysicalRange(names[type], minValue, maxValue)) return false;
if(type == 1) minValue = 0.0;
return true;
}
// 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。
void nmWxAutomaticFitting::setParameterRange(int parameterIndex, double minValue, double maxValue)
{
// 自动范围写入时不触发尚未完成的行校验。
const bool updating = m_updatingParameterRanges;
m_updatingParameterRanges = true;
m_parameterItems[parameterIndex]->setText(2, QString::number(minValue, 'g', 10));
m_parameterItems[parameterIndex]->setText(3, QString::number(maxValue, 'g', 10));
m_updatingParameterRanges = updating;
}
// 根据当前初值生成建议搜索范围,并始终截断在拟合边界内。skin 下界为 0,
// 上界按初值加固定宽度,其余正值参数使用倍率范围;每次打开窗口和拟合完成后复用。
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterItems.size()
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) {
return;
}
double reference = centerValue;
const int type = automaticFittingData.getModelParameters()[parameterIndex].type;
const bool positiveParameter = type != 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(type == 1) {
const double skinHalfRange = 10.0;
newMin = physicalMin;
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0
&& !(type == 4 && 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(type == 4) {
// 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);
}
}
// 校验参数范围;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 >= m_parameterItems.size()) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? m_parameterItems.size() : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
QTreeWidgetItem* item = m_parameterItems[currentParameterIndex];
// 已参与采样或当前算法不可用的参数不阻塞设置保存;折叠的分组仍须校验。
if(item->isDisabled()) continue;
const nmFitParameter& parameter = automaticFittingData.getModelParameters()[currentParameterIndex];
bool minOk = false;
bool initialOk = false;
bool maxOk = false;
const double minValue = item->text(2).toDouble(&minOk);
const double initialValue = item->text(1).toDouble(&initialOk);
const double maxValue = item->text(3).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(parameter.name());
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(parameter.name());
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(parameter.name())
.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(parameter.name());
return false;
}
// 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。
if(parameter.type == 4 && item->checkState(0) == Qt::Checked
&& (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) {
errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.")
.arg(parameter.name());
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_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();
if(m_sensitivitySettings) {
if(m_sensitivitySettings->confirmed) automaticFittingData = m_sensitivitySettings->parameters;
else automaticFittingData.getModelParameters().clear();
}
nmDataWellBase* primary = pManager->findWellByCode(pManager->getPrimaryWellCode());
m_targetWellName = primary ? primary->getWellName() : QString();
setupUI();
// 敏感性入口使用“确定”保存设置,普通入口仍直接开始拟合。
if(m_sensitivitySettings) m_okBtn->setText(tr("OK"));
setWindowTitle(tr("Automatic fitting"));
setModal(true);
// 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。
resize(sizeHint());
initializeModelParameters();
onAlgorithmChanged(m_algorithmCombo->currentIndex());
m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed");
}
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 QTreeWidget { 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 QTreeWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 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 QTreeWidget(this);
m_parameterTable->setFont(QFont("Microsoft YaHei", 10));
m_parameterTable->setColumnCount(5);
m_parameterTable->setHeaderLabels(QStringList() << tr("Parameter") << tr("Initial value")
<< tr("Min") << tr("Max") << tr("Unit"));
m_parameterTable->setItemDelegate(new nmFitParameterDelegate(m_parameterTable));
m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems);
m_parameterTable->setUniformRowHeights(true);
m_parameterTable->setIndentation(18);
QHeaderView* header = m_parameterTable->header();
header->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
header->setStretchLastSection(false);
header->setResizeMode(0, QHeaderView::Interactive);
m_parameterTable->setColumnWidth(0, 230);
for(int column = 1; column <= 3; ++column) header->setResizeMode(column, QHeaderView::Stretch);
header->setResizeMode(4, QHeaderView::Fixed);
m_parameterTable->setColumnWidth(4, 120);
connect(m_parameterTable, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
this, SLOT(onParameterTableItemChanged(QTreeWidgetItem*, int)));
}
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"));
// 从敏感性分析打开时仅使用 LM,普通自动拟合仍可选择算法。
m_algorithmCombo->setCurrentIndex(m_sensitivitySettings
|| nmDataAnalyzeManager::getCurrentInstance()->getEffectiveCalculationWells().size() > 1 ? 1 : 0);
m_algorithmCombo->setEnabled(!m_sensitivitySettings);
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()));
m_controlLayout->addWidget(algorithmLabel, 0, 0);
m_controlLayout->addWidget(m_algorithmCombo, 1, 0);
m_controlLayout->addWidget(errorLabel, 0, 1);
m_controlLayout->addWidget(m_errorLimitEdit, 1, 1);
m_controlLayout->addWidget(m_iterationLabel, 0, 2);
m_controlLayout->addWidget(m_iterationEdit, 1, 2);
// 右侧预留一列空白,三个输入框不再铺满整行。
for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1);
}
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
{
// 多井联合拟合使用 LM;PSO 仍只开放主井和共享储层参数。
const bool usePSO = index == 0;
const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode();
const bool updating = m_updatingParameterRanges;
m_updatingParameterRanges = true;
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i) {
const nmFitParameter& p = parameters[i];
nmDataWellBase* well = p.wellCode.isEmpty() ? nullptr
: nmDataAnalyzeManager::getCurrentInstance()->findWellByCode(p.wellCode);
const bool unavailable = (m_sensitivitySettings && m_sampledParameters.contains(p.samplingKey()))
|| (usePSO && !p.wellCode.isEmpty() && p.wellCode != primaryCode)
|| (p.type == 1 && (!well || well->getPerforationCount() == 0));
m_parameterItems[i]->setDisabled(unavailable);
if(unavailable) m_parameterItems[i]->setCheckState(0, Qt::Unchecked);
}
m_updatingParameterRanges = updating;
updateSelectAllState();
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_selectAllCheckBox = new QCheckBox(tr("Select All"));
m_okBtn = new QPushButton(tr("Start fitting"));
m_okBtn->setObjectName("startFitting");
m_okBtn->setDefault(true);
m_cancelBtn = new QPushButton(tr("Cancel"));
// 设置按钮大小
m_okBtn->setMinimumWidth(80);
m_cancelBtn->setMinimumWidth(80);
buttonLayout->addWidget(m_selectAllCheckBox);
buttonLayout->addStretch();
buttonLayout->addWidget(m_cancelBtn);
buttonLayout->addWidget(m_okBtn);
m_mainLayout->addLayout(buttonLayout);
// 连接信号槽
connect(m_selectAllCheckBox, SIGNAL(clicked(bool)), this, SLOT(onSelectAll(bool)));
connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onAccept()));
connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onReject()));
}
void nmWxAutomaticFitting::onSelectAll(bool checked)
{
// 批量切换可用参数,完成后统一同步全选框,避免逐项更新时改变点击状态。
const bool updating = m_updatingParameterRanges;
m_updatingParameterRanges = true;
for(int i = 0; i < m_parameterItems.size(); ++i) {
QTreeWidgetItem* item = m_parameterItems[i];
if(!item->isDisabled())
item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked);
}
m_updatingParameterRanges = updating;
updateSelectAllState();
}
void nmWxAutomaticFitting::updateSelectAllState()
{
// 仅统计可用参数;单项勾选和算法切换后,全选框都与当前列表保持一致。
int available = 0;
int selected = 0;
for(int i = 0; i < m_parameterItems.size(); ++i) {
if(m_parameterItems[i]->isDisabled()) continue;
++available;
if(m_parameterItems[i]->checkState(0) == Qt::Checked) ++selected;
}
m_selectAllCheckBox->setEnabled(available > 0);
m_selectAllCheckBox->setChecked(available > 0 && selected == available);
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTreeWidgetItem* item, int column)
{
// 勾选时同步全选框,数值编辑仍沿用原来的范围校验。
if(!m_updatingParameterRanges && column == 0) updateSelectAllState();
if(m_updatingParameterRanges || column < 1 || column > 3
|| !item->data(0, Qt::UserRole).isValid()) return;
QString error;
if(!validateParameterTable(error, item->data(0, Qt::UserRole).toInt()))
QMessageBox::warning(this, tr("Invalid parameter range"), error);
}
void nmWxAutomaticFitting::onAccept()
{
QString validationError;
if(!validateParameterTable(validationError)) {
QMessageBox::warning(this, tr("Invalid parameter range"), validationError);
return;
}
// 校验通过后再保存参数设置,非法输入不会被静默裁剪。
saveModelParameters();
setAutomaticFittingValue();
if(m_sensitivitySettings) {
// 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。
m_sensitivitySettings->parameters = automaticFittingData;
m_sensitivitySettings->targetWell = m_targetWellName;
m_sensitivitySettings->initialValues = primaryInitialValues();
m_sensitivitySettings->confirmed = true;
accept();
return;
}
// 更新完成后,通知参数界面刷新
nmWxParameterProperty::notifyUpdateTable();
// 获取目标井的双对数历史数据
QString selectedWellName = m_targetWellName;
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 = automaticFittingData.hasSelectedParameters();
if(!hasSelectedParams) {
QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!"));
return;
}
const QStringList selectedParameterNames = selectedParameterLabels(false);
// 启动自动拟合 - 传递双对数历史数据
startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName);
}
void nmWxAutomaticFitting::onReject()
{
reject();
}
void nmWxAutomaticFitting::setAutomaticFittingValue()
{
// 单井旧字段继续供 PSO 和旧配置读取,LM 直接使用完整分组参数列表。
automaticFittingData.setPermeabilitySelected(false);
automaticFittingData.setSkinSelected(false);
automaticFittingData.setWellboreStorageSelected(false);
automaticFittingData.setPorositySelected(false);
automaticFittingData.setFractureConductivitySelected(false);
automaticFittingData.setFractureHalfLengthSelected(false);
const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode();
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i) {
const nmFitParameter& p = parameters[i];
if(!p.wellCode.isEmpty() && p.wellCode != primaryCode) continue;
switch(p.type) {
case 0:
automaticFittingData.setPermeabilitySelected(p.selected);
automaticFittingData.getPermeabilityMin().setValue(p.lower);
automaticFittingData.getPermeabilityMax().setValue(p.upper);
break;
case 1:
automaticFittingData.setSkinSelected(p.selected);
automaticFittingData.getSkinMin().setValue(p.lower);
automaticFittingData.getSkinMax().setValue(p.upper);
break;
case 2:
automaticFittingData.setWellboreStorageSelected(p.selected);
automaticFittingData.getWellboreStorageMin().setValue(p.lower);
automaticFittingData.getWellboreStorageMax().setValue(p.upper);
break;
case 3:
automaticFittingData.setPorositySelected(p.selected);
automaticFittingData.getPorosityMin().setValue(p.lower);
automaticFittingData.getPorosityMax().setValue(p.upper);
break;
case 4:
automaticFittingData.setFractureConductivitySelected(p.selected);
automaticFittingData.getFractureConductivityMin().setValue(p.lower);
automaticFittingData.getFractureConductivityMax().setValue(p.upper);
break;
case 5:
automaticFittingData.setFractureHalfLengthSelected(p.selected);
automaticFittingData.getFractureHalfLengthMin().setValue(p.lower);
automaticFittingData.getFractureHalfLengthMax().setValue(p.upper);
break;
}
}
automaticFittingData.setSurrogateScreeningEnabled(false);
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO");
// 敏感性入口只保存设置;普通 LM 在启动时按井应用初值。
if(m_sensitivitySettings) return;
if(m_algorithmCombo->currentIndex() == 1) {
nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(automaticFittingData);
return;
}
const QVector<double> initial = primaryInitialValues();
// 只写回可拟合的渗透率、孔隙度,含水饱和度沿用模型当前值。
reservoirData.getPermeability().setValue(initial[0]); // 渗透率
reservoirData.getPorosity().setValue(initial[3]); // 孔隙度
// 更新储层数据(全局)
nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData);
// 保存自动拟合数据
nmDataAutomaticFitting psoSettings = automaticFittingData;
psoSettings.getModelParameters().clear();
nmDataAnalyzeManager::getCurrentInstance()->updateAutomaticFittingData(psoSettings);
// 只更新目标井的参数
QString selectedWellName = m_targetWellName;
if(!selectedWellName.isEmpty()) {
double newSkinValue = initial[1];
double newWellboreStorageValue = initial[2];
double newFractureConductivityValue = initial[4];
double newFractureHalfLengthValue = initial[5];
// 直接从数据管理器获取目标井
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);
}
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_targetWellName;
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
// 名称和单位均按实际优化向量的顺序展示。
m_progressMonitor->setSelectedParameters(selectedParams, selectedParameterLabels(true));
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();
saveModelParameters();
reservoirData = manager->getReservoirDataCopy();
initializeModelParameters(false);
onAlgorithmChanged(m_algorithmCombo->currentIndex());
saveModelParameters();
setAutomaticFittingValue();
nmWxParameterProperty::notifyUpdateTable();
}
void nmWxAutomaticFitting::saveModelParameters()
{
// 每个叶节点对应一个带井编码的参数,直接收集所有分组。
QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i) {
nmFitParameter& p = parameters[i];
QTreeWidgetItem* item = m_parameterItems[i];
p.selected = !item->isDisabled() && item->checkState(0) == Qt::Checked;
p.initial = item->text(1).toDouble();
p.lower = item->text(2).toDouble();
p.upper = item->text(3).toDouble();
}
}
void nmWxAutomaticFitting::initializeModelParameters(bool restoreRanges)
{
// 储层只建一组,各井按井型加入原有参数;不再通过切换页面收集数据。
const bool updating = m_updatingParameterRanges;
m_updatingParameterRanges = true;
const QVector<nmFitParameter> saved = automaticFittingData.getModelParameters();
automaticFittingData.getModelParameters().clear();
m_parameterItems.clear();
m_parameterTable->clear();
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
QTreeWidgetItem* reservoir = new QTreeWidgetItem(m_parameterTable, QStringList(tr("Reservoir parameters")));
addParameter(reservoir, 0, nullptr, reservoirData.getPermeability().getValue().toDouble());
addParameter(reservoir, 3, nullptr, reservoirData.getPorosity().getValue().toDouble());
QTreeWidgetItem* wellsGroup = new QTreeWidgetItem(m_parameterTable, QStringList(tr("Well parameters")));
const QVector<nmCalculationWellRef> wells = manager->getEffectiveCalculationWells();
for(int i = 0; i < wells.size(); ++i) {
nmDataWellBase* well = manager->findWellByCode(wells[i].m_sWellCode);
if(!well) continue;
QTreeWidgetItem* group = new QTreeWidgetItem(wellsGroup, QStringList(well->getWellName()));
const double skin = well->getPerforationCount() > 0
? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0;
addParameter(group, 1, well, skin);
addParameter(group, 2, well, well->getWellboreStorage().getValue().toDouble());
if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
addParameter(group, 4, well, fractured->getDfc().getValue().toDouble());
addParameter(group, 5, well, fractured->getFractureHalfLength().getValue().toDouble());
} else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
addParameter(group, 4, well, fractured->getDfc().getValue().toDouble());
addParameter(group, 5, well, fractured->getFractureHalfLength().getValue().toDouble());
}
}
const bool legacySelected[] = {automaticFittingData.getPermeabilitySelected(),
automaticFittingData.getSkinSelected(), automaticFittingData.getWellboreStorageSelected(),
automaticFittingData.getPorositySelected(), automaticFittingData.getFractureConductivitySelected(),
automaticFittingData.getFractureHalfLengthSelected()};
nmDataAttribute* legacyMin[] = {&automaticFittingData.getPermeabilityMin(), &automaticFittingData.getSkinMin(),
&automaticFittingData.getWellboreStorageMin(), &automaticFittingData.getPorosityMin(),
&automaticFittingData.getFractureConductivityMin(), &automaticFittingData.getFractureHalfLengthMin()};
nmDataAttribute* legacyMax[] = {&automaticFittingData.getPermeabilityMax(), &automaticFittingData.getSkinMax(),
&automaticFittingData.getWellboreStorageMax(), &automaticFittingData.getPorosityMax(),
&automaticFittingData.getFractureConductivityMax(), &automaticFittingData.getFractureHalfLengthMax()};
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i) {
const nmFitParameter& p = parameters[i];
updateRangeForParameter(i, p.initial);
for(int j = 0; j < saved.size(); ++j) {
if(saved[j].type != p.type || saved[j].wellCode != p.wellCode) continue;
m_parameterItems[i]->setCheckState(0, saved[j].selected ? Qt::Checked : Qt::Unchecked);
if(restoreRanges && p.initial >= saved[j].lower && p.initial <= saved[j].upper)
setParameterRange(i, saved[j].lower, saved[j].upper);
break;
}
// 兼容尚无多井列表的敏感性设置,旧井参数仍对应原来记录的目标井。
if(saved.isEmpty() && m_sensitivitySettings && m_sensitivitySettings->confirmed
&& (p.wellCode.isEmpty() || p.wellName == m_sensitivitySettings->targetWell)) {
m_parameterItems[i]->setCheckState(0, legacySelected[p.type] ? Qt::Checked : Qt::Unchecked);
const double lower = legacyMin[p.type]->getValue().toDouble();
const double upper = legacyMax[p.type]->getValue().toDouble();
if(restoreRanges && p.initial >= lower && p.initial <= upper) setParameterRange(i, lower, upper);
}
}
// 分组标题加粗并横跨数值列,叶节点仍保留独立勾选和编辑。
for(QTreeWidgetItemIterator it(m_parameterTable); *it; ++it) {
if((*it)->data(0, Qt::UserRole).isValid()) continue;
QFont font = (*it)->font(0);
font.setBold(true);
(*it)->setFont(0, font);
(*it)->setFirstColumnSpanned(true);
(*it)->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
m_parameterTable->expandAll();
m_updatingParameterRanges = updating;
}
void nmWxAutomaticFitting::addParameter(QTreeWidgetItem* group, int type,
nmDataWellBase* well, double initial)
{
// 名称在井分组内只显示参数本身,数据仍保留所属井编码供计算使用。
nmFitParameter parameter;
parameter.type = type;
parameter.initial = initial;
const QString name = parameter.name();
if(well) {
parameter.wellCode = well->getWellCode();
parameter.wellName = well->getWellName();
}
const int index = automaticFittingData.getModelParameters().size();
automaticFittingData.getModelParameters().append(parameter);
QTreeWidgetItem* item = new QTreeWidgetItem(group);
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
item->setData(0, Qt::UserRole, index);
item->setText(0, name);
item->setText(1, QString::number(initial, 'g', 10));
item->setText(4, parameter.unit());
item->setCheckState(0, !m_sensitivitySettings
&& (!well || well->getWellCode() == nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode())
? Qt::Checked : Qt::Unchecked);
m_parameterItems.append(item);
}
QVector<double> nmWxAutomaticFitting::primaryInitialValues() const
{
// 旧六参数接口仅提取主井与共享储层,其他井不会覆盖主井初值。
QVector<double> values(6, 0.0);
const QString primaryCode = nmDataAnalyzeManager::getCurrentInstance()->getPrimaryWellCode();
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i)
if(parameters[i].wellCode.isEmpty() || parameters[i].wellCode == primaryCode)
values[parameters[i].type] = parameters[i].initial;
return values;
}
QStringList nmWxAutomaticFitting::selectedParameterLabels(bool units) const
{
// LM 按完整向量顺序显示,PSO 沿用旧六参数类型顺序。
QStringList labels;
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
if(m_algorithmCombo->currentIndex() == 1) {
for(int i = 0; i < parameters.size(); ++i)
if(parameters[i].selected) labels << (units ? parameters[i].unit() : parameters[i].name());
} else {
for(int type = 0; type < 6; ++type)
for(int i = 0; i < parameters.size(); ++i)
if(parameters[i].selected && parameters[i].type == type)
labels << (units ? parameters[i].unit() : parameters[i].name());
}
return labels;
}