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

1132 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 "ZxSupSubHelper.h"
#include "mModuleDefines.h"
#include "mGui/mGuiAnal/iAnalRun.h"
#include "mAlgDefines.h"
#pragma comment(lib, "mGuiAnal.lib")
#include <QApplication>
#include <QHeaderView>
#include <QPainter>
#include <QStyle>
#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 {
// 拟合窗口与系统参数表共用参数编码,单位读取 XML 解析后的上标文本。
const char* const fitParameterNames[] = {"Result_K", "Result_W_Skin", "Result_W_C",
"Result_phi", "Result_W_Dfc", "W_FractureHalfLength"};
QString nmAutoFitDisplayUnit(const nmFitParameter& parameter)
{
iSysParaHelper* helper = _paraHelper;
iParameter* definition = helper
? helper->getPara(QString::fromLatin1(fitParameterNames[parameter.type]), s_Nm_Serie) : nullptr;
// 配置不可用时仍沿用基准单位,并复用主软件的上标转换。
const QString unit = definition ? definition->m_sUnit : parameter.unit();
QString displayUnit;
return ZxSupSubHelper::checkRichText(unit, displayUnit) ? displayUnit : unit;
}
// 树节点和名称、单位列只读,仅允许编辑参数的三个数值列。
class nmFitParameterDelegate : public QStyledItemDelegate
{
public:
explicit nmFitParameterDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyledItemDelegate::paint(painter, option, index);
// 仅为具体参数绘制网格,分组标题保留原生树样式;线色沿用全局表格配置。
if(!index.sibling(index.row(), 0).data(Qt::UserRole).isValid()) return;
const QTreeWidget* tree = qobject_cast<QTreeWidget*>(parent());
const int gridColor = tree->style()->styleHint(QStyle::SH_Table_GridLineColor,
&option, tree);
painter->save();
painter->setPen(QPen(QColor::fromRgb(gridColor), 1));
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
// 每组首行补上顶边,名称列的左边随树层级缩进,保留折叠区域。
if(index.row() == 0)
painter->drawLine(option.rect.topLeft(), option.rect.topRight());
if(index.column() == 0)
painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
painter->restore();
}
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)
{
// 参数行按实际类型查询边界,不依赖它位于哪口井的分组。
const int type = automaticFittingData.getModelParameters()[parameterIndex].type;
if(!nmAutoFitReadPhysicalRange(fitParameterNames[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();
}
// 普通拟合绑定打开窗口时的结果井;敏感性设置仍沿用主分析井。
const QString resultWellId = pManager->getCurrentResultWellInstanceId();
nmDataWellBase* targetWell = !m_sensitivitySettings && !resultWellId.isEmpty()
? pManager->findWellByInstanceId(resultWellId)
: pManager->findWellByCode(pManager->getPrimaryWellCode());
m_targetWellName = targetWell ? targetWell->getWellName() : QString();
m_targetWellCode = targetWell ? targetWell->getWellCode() : QString();
setupUI();
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
{
// 与改版前设置窗口保持相同推荐尺寸,仍允许手动放大。
return QSize(800, 480);
}
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");
m_mainLayout = new QVBoxLayout(this);
QGridLayout* contentLayout = new QGridLayout();
contentLayout->setContentsMargins(20, 0, 20, 0);
contentLayout->setColumnMinimumWidth(1, 30);
contentLayout->setColumnStretch(2, 1);
contentLayout->setRowStretch(0, 1);
setupControlPanel();
QWidget* controlsPanel = new QWidget(this);
controlsPanel->setFixedWidth(160);
controlsPanel->setLayout(m_controlLayout);
contentLayout->addWidget(controlsPanel, 0, 0);
setupParameterTable();
contentLayout->addWidget(m_parameterTable, 0, 2);
// 底部操作区与参数表共用一列,全选始终对齐表格左边缘。
contentLayout->addLayout(setupButtons(), 1, 2);
m_mainLayout->addLayout(contentLayout, 1);
setLayout(m_mainLayout);
}
void nmWxAutomaticFitting::setupParameterTable()
{
// 用可折叠分组直接展示各井参数,数值列沿用原来的编辑方式。
m_parameterTable = new QTreeWidget(this);
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->setAlternatingRowColors(false);
m_parameterTable->setUniformRowHeights(true);
m_parameterTable->setIndentation(18);
QHeaderView* header = m_parameterTable->header();
header->setDefaultAlignment(Qt::AlignCenter);
header->setStretchLastSection(false);
header->setResizeMode(0, QHeaderView::Interactive);
m_parameterTable->setColumnWidth(0, 190);
for(int column = 1; column <= 3; ++column) header->setResizeMode(column, QHeaderView::Stretch);
header->setResizeMode(4, QHeaderView::Fixed);
m_parameterTable->setColumnWidth(4, 80);
// 只调整显示顺序为最小值、初始值、最大值,保留现有列索引和校验逻辑。
header->moveSection(2, 1);
connect(m_parameterTable, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
this, SLOT(onParameterTableItemChanged(QTreeWidgetItem*, int)));
}
void nmWxAutomaticFitting::setupControlPanel()
{
// 三项设置从顶部排列,与右侧参数表顶端对齐,剩余空间留在底部。
m_controlLayout = new QGridLayout();
m_controlLayout->setContentsMargins(0, 0, 0, 0);
// 算法选择
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);
m_algorithmCombo->setFixedWidth(160);
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
// 迭代次数
m_iterationLabel = new QLabel();
m_iterationEdit = new QLineEdit(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
m_iterationEdit->setFixedWidth(100);
// 误差上限
QLabel* errorLabel = new QLabel(tr("Target error:"));
m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble()));
m_errorLimitEdit->setFixedWidth(100);
m_controlLayout->addWidget(algorithmLabel, 0, 0);
m_controlLayout->addWidget(m_algorithmCombo, 1, 0);
m_controlLayout->setRowMinimumHeight(2, 15);
m_controlLayout->addWidget(m_iterationLabel, 3, 0);
m_controlLayout->addWidget(m_iterationEdit, 4, 0);
m_controlLayout->setRowMinimumHeight(5, 15);
m_controlLayout->addWidget(errorLabel, 6, 0);
m_controlLayout->addWidget(m_errorLimitEdit, 7, 0);
m_controlLayout->setRowStretch(8, 1);
}
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
{
// 多井联合拟合使用 LM;PSO 只开放本次目标井和共享储层参数。
const bool usePSO = index == 0;
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 != m_targetWellCode)
|| (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."));
}
QHBoxLayout* nmWxAutomaticFitting::setupButtons()
{
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_selectAllCheckBox = new QCheckBox(tr("Select All"));
connect(m_selectAllCheckBox, SIGNAL(clicked(bool)), this, SLOT(onSelectAll(bool)));
m_okBtn = new QPushButton(tr("OK"));
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_okBtn);
buttonLayout->addWidget(m_cancelBtn);
// 连接信号槽
connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onAccept()));
connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onReject()));
return buttonLayout;
}
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 = targetInitialValues();
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 QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i) {
const nmFitParameter& p = parameters[i];
if(!p.wellCode.isEmpty() && p.wellCode != m_targetWellCode) 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 = targetInitialValues();
// 只写回可拟合的渗透率、孔隙度,含水饱和度沿用模型当前值。
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();
// 进程窗口与优化器使用同一目标井名称。
m_progressMonitor->setFittingParameters(maxIterations, targetError, targetWellName);
// 名称和单位均按实际优化向量的顺序展示。
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, nmAutoFitDisplayUnit(parameter));
item->setCheckState(0, !m_sensitivitySettings
&& (!well || well->getWellCode() == m_targetWellCode)
? Qt::Checked : Qt::Unchecked);
m_parameterItems.append(item);
}
QVector<double> nmWxAutomaticFitting::targetInitialValues() const
{
// 旧六参数接口仅提取目标井与共享储层,其他井不会覆盖目标井初值。
QVector<double> values(6, 0.0);
const QVector<nmFitParameter>& parameters = automaticFittingData.getModelParameters();
for(int i = 0; i < parameters.size(); ++i)
if(parameters[i].wellCode.isEmpty() || parameters[i].wellCode == m_targetWellCode)
values[parameters[i].type] = parameters[i].initial;
return values;
}
QStringList nmWxAutomaticFitting::selectedParameterLabels(bool units) const
{
// 单位与设置表格共用 XML 定义;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 ? nmAutoFitDisplayUnit(parameters[i]) : 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 ? nmAutoFitDisplayUnit(parameters[i]) : parameters[i].name());
}
return labels;
}