feat(nmNum): 完善多井参数界面与结果展示

- 参数按储层和井分组,采样与拟合同井参数互斥。

- 结果面板按井汇总筛选范围,补齐分组中文翻译。

- 调整拟合窗口字号、行高和输入框,支持全选及取消全选。
feature/Sensitivity-20261012
lvjunjie 4 days ago
parent 2a41333007
commit ca59dfcd97

Binary file not shown.

@ -4444,6 +4444,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
</context>
<context>
<name>nmWxAutomaticFitting</name>
<message>
<source>Reservoir parameters</source>
<translation>储层参数</translation>
</message>
<message>
<source>Well parameters</source>
<translation>井参数</translation>
</message>
<message>
<source>LM sampling:</source>
<translation>LM 采样方式:</translation>
@ -4545,8 +4553,8 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<translation>目标井</translation>
</message>
<message>
<source>Reverse Selection</source>
<translation>反选</translation>
<source>Select All</source>
<translation>全选</translation>
</message>
<message>
<source>OK</source>

@ -5,11 +5,11 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTableWidget>
#include <QCheckBox>
#include <QTreeWidget>
#include <QLineEdit>
#include <QComboBox>
#include <QPushButton>
#include <QCheckBox>
#include <QLabel>
#include <QHeaderView>
#include <QProgressDialog>
@ -39,13 +39,11 @@ public:
virtual QSize sizeHint() const;
private slots:
void onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
void onReverseSelection();
void onSelectAll(bool checked);
void onAccept();
void onReject();
void onWellSelected(int index);
void onAlgorithmChanged(int index);
void onParameterTableItemChanged(QTableWidgetItem* item);
void onParameterTableItemChanged(QTreeWidgetItem* item, int column);
// 自动拟合相关槽函数
void runAutoFitting();
@ -58,12 +56,13 @@ private:
void setupParameterTable();
void setupControlPanel();
void setupButtons();
void updateSelectAllState();
void setAutomaticFittingValue();
void loadSensitivitySettings();
void setParameterRowVisible(QTableWidget* table, int row, bool visible);
void renumberVisibleParameterRows(QTableWidget* table);
void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType);
void initializeSuggestedParameterRanges();
void saveModelParameters();
void initializeModelParameters(bool restoreRanges = true);
void addParameter(QTreeWidgetItem* group, int type, nmDataWellBase* well, double initial);
QVector<double> primaryInitialValues() const;
QStringList selectedParameterLabels(bool units) const;
void updateRangeForParameter(int parameterIndex, double centerValue);
void setParameterRange(int parameterIndex, double minValue, double maxValue);
bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue);
@ -79,25 +78,18 @@ private:
// UI组件
QVBoxLayout* m_mainLayout;
QGridLayout* m_controlLayout;
QTableWidget* m_parameterTable;
QTreeWidget* m_parameterTable;
QVector<QTreeWidgetItem*> m_parameterItems;
// 控制面板组件
QLineEdit* m_iterationEdit;
QLabel* m_iterationLabel;
QLineEdit* m_errorLimitEdit;
QComboBox* m_targetWellCombo;
QString m_targetWellName;
QComboBox* m_algorithmCombo;
// 参数复选框
QCheckBox* m_kCheckBox; // 渗透率
QCheckBox* m_sCheckBox; // 表皮系数
QCheckBox* m_cCheckBox; // 井筒储集系数
QCheckBox* m_phiCheckBox; // 孔隙度
QCheckBox* m_dfcCheckBox; // 裂缝导流能力
QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长
// 按钮
QPushButton* m_reverseBtn;
QCheckBox* m_selectAllCheckBox;
QPushButton* m_okBtn;
QPushButton* m_cancelBtn;
@ -112,7 +104,6 @@ private:
nmCalculationAutoFitLM* m_autoFitterLM;
QProgressDialog* m_progressDialog;
QTimer* m_progressTimer;
bool m_autoParameterRanges;
bool m_updatingParameterRanges;
// 拟合开始界面

@ -147,7 +147,7 @@ private:
void refreshModelValues();
void updateModelCount();
void setCalculationRunning(bool running);
int findVariableIndex(const QString& varName); // 查找变量索引
int findVariableIndex(const nmDataSensitive::VariableSampling& variable); // 查找变量索引
// UI 控件
QComboBox *calculationTypeCombo;

File diff suppressed because it is too large Load Diff

@ -151,16 +151,27 @@ void appendSensitivityParas(nmDataAnalyzeManager* manager, QStringList& paras,
if(!batch->filteredParameterBounds(i, minimum, maximum)) continue;
const QString lower = "Sensitivity_" + resultNames[index] + "_Lower";
const QString higher = "Sensitivity_" + resultNames[index] + "_Higher";
appendResultPara(paras, values, groupCode, lower, minimum);
appendResultPara(paras, values, groupCode, higher, maximum);
// 多井结果复用 XML 分组机制,同名参数用所属井的分组编码区分。
const QString parameterGroup = variable.m_wellCode.isEmpty() ? groupCode : groupCode + "_" + variable.m_wellCode;
if(parameterGroup != groupCode) groups[parameterGroup] = groups[groupCode] + " - " + variable.m_varGroup;
appendResultPara(paras, values, parameterGroup, lower, minimum);
appendResultPara(paras, values, parameterGroup, higher, maximum);
}
// 第四种在采样范围后追加各组拟合最优值的范围,沿用同一阈值和 XML 分组。
for(int i = 0; i < batch->m_fitParameterCodes.size(); ++i) {
double minimum = 0.0, maximum = 0.0;
if(!batch->filteredParameterBounds(i, minimum, maximum, true)) continue;
const QString name = "Sensitivity_Fit_" + batch->m_fitParameterCodes[i];
appendResultPara(paras, values, groupCode, name + "_Minimum", minimum);
appendResultPara(paras, values, groupCode, name + "_Maximum", maximum);
const QString code = batch->m_fitParameterCodes[i];
const int separator = code.indexOf('/');
const QString wellCode = separator < 0 ? QString() : code.left(separator);
const QString parameterGroup = wellCode.isEmpty() ? groupCode : groupCode + "_" + wellCode;
if(parameterGroup != groupCode) {
nmDataWellBase* well = manager->findWellByCode(wellCode);
groups[parameterGroup] = groups[groupCode] + " - " + (well ? well->getWellName() : wellCode);
}
const QString name = "Sensitivity_Fit_" + code.mid(separator + 1);
appendResultPara(paras, values, parameterGroup, name + "_Minimum", minimum);
appendResultPara(paras, values, parameterGroup, name + "_Maximum", maximum);
}
}

@ -206,7 +206,7 @@ QString sensitivityRunParameters(const nmDataSensitive::BatchResult& results, co
double value = run.m_vecParameters[i];
const QString unit = variable.m_modelValue.getUnit();
convertSamplingValue(variable.m_varName, value, samplingBaseUnit(variable.m_varName), unit, value);
descriptions << variable.m_varName + " = " + formatSamplingValue(value, 3)
descriptions << variable.displayName() + " = " + formatSamplingValue(value, 3)
+ (unit.isEmpty() ? QString() : " " + unit);
}
return descriptions.join(" + ");
@ -634,7 +634,7 @@ nmWxSensitivityScatter::nmWxSensitivityScatter(
options->addWidget(new QLabel(tr("X Axis:"), this));
m_parameter = new QComboBox(this);
for(int i = 0; i < results->m_variables.size(); ++i)
if(results->m_variables[i].m_enabled) m_parameter->addItem(results->m_variables[i].m_varName, i);
if(results->m_variables[i].m_enabled) m_parameter->addItem(results->m_variables[i].displayName(), i);
options->addWidget(m_parameter, 1);
options->addSpacing(20);
options->addWidget(new QLabel(tr("Y Axis:"), this));
@ -692,7 +692,7 @@ void nmWxSensitivityScatter::updateParameter()
const int index = m_parameter->itemData(m_parameter->currentIndex()).toInt();
const nmDataSensitive::VariableSampling& variable = m_results->m_variables[index];
const QString unit = variable.m_modelValue.getUnit();
m_plot->setAxisX(variable.m_varName, unit);
m_plot->setAxisX(variable.displayName(), unit);
double modelValue = variable.m_modelValue.getValue().toDouble();
const bool hasModel = convertSamplingValue(variable.m_varName, modelValue,
samplingBaseUnit(variable.m_varName), unit, modelValue) && qIsFinite(modelY) && (!errorAxis || modelY >= 0.0);
@ -1254,7 +1254,7 @@ private:
nmDataSensitive::VariableSampling& variable = m_data.getVariable(index);
const QString unit = variable.getModelValue().getUnit();
convertSamplingValue(variable.getVarName(), value, samplingBaseUnit(variable.getVarName()), unit, value);
return variable.getVarName() + " = " + formatSamplingValue(value, 3)
return variable.displayName() + " = " + formatSamplingValue(value, 3)
+ (unit.isEmpty() ? QString() : " " + unit);
}
@ -1391,12 +1391,26 @@ void nmWxSensitive::closeEvent(QCloseEvent* event)
void nmWxSensitive::refreshModelValues()
{
// 每次打开或模型更新时读取当前参数,沿用已有单位和物理范围限制。
nmDataWellBase* well = m_pDataManager
? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr;
nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : nullptr;
const QVector<nmCalculationWellRef> wells = m_pDataManager
? m_pDataManager->getEffectiveCalculationWells() : QVector<nmCalculationWellRef>();
// 旧版主井配置补上井编码;已不参与模型的井不再生成样本。
for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
if(variable.m_varName == "Skin" || variable.m_varName == "C") {
if(variable.m_wellCode.isEmpty() && m_pDataManager)
variable.m_wellCode = m_pDataManager->getPrimaryWellCode();
if(!m_pDataManager || !m_pDataManager->isWellSelectedForCalculation(variable.m_wellCode))
variable.m_enabled = false;
}
}
bool modelChanged = false;
const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6));
for (int n = 0; n < names.size(); ++n) {
for (int entry = 0; entry < wells.size() * 2 + 4; ++entry) {
const bool wellParameter = entry < wells.size() * 2;
const int n = wellParameter ? entry % 2 : entry - wells.size() * 2 + 2;
const QString wellCode = wellParameter ? wells[entry / 2].m_sWellCode : QString();
nmDataWellBase* well = wellParameter ? m_pDataManager->findWellByCode(wellCode) : nullptr;
nmDataAttribute* attribute = nullptr;
const QString name = names[n];
if (well && name == "Skin" && well->getPerforationCount() > 0)
@ -1409,17 +1423,19 @@ void nmWxSensitive::refreshModelValues()
int index = -1;
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i)
if (m_sensitiveData.getVariable(i).getVarName() == name) { index = i; break; }
if (m_sensitiveData.getVariable(i).getVarName() == name &&
m_sensitiveData.getVariable(i).m_wellCode == wellCode) { index = i; break; }
const bool fresh = index < 0;
if (fresh) {
nmDataSensitive::VariableSampling variable;
variable.setVarName(name);
variable.setVarGroup(n < 2 ? "Tested Well" : "Reservoir");
variable.m_wellCode = wellCode;
variable.setNumber(5);
m_sensitiveData.getVariables().append(variable);
index = m_sensitiveData.getVariableCount() - 1;
}
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index);
variable.setVarGroup(well ? well->getWellName() : "Reservoir");
double physicalMin = 0.0, physicalMax = 0.0;
if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) {
variable.setEnabled(false);
@ -1528,7 +1544,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
const QString name = variable.getVarName();
const int idx = findVariableIndex(name);
const int idx = findVariableIndex(variable);
if (idx < 0) continue;
const bool available = isSupportedSamplingVariable(name) &&
variable.getModelValue().getValue().isValid();
@ -1536,7 +1552,7 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
QTreeWidgetItem* parent = variablesTree->topLevelItem(group);
for (int child = 0; child < parent->childCount(); ++child) {
QTreeWidgetItem* item = parent->child(child);
if (item->text(0) != name) continue;
if (item->text(0) != name || item->data(0, Qt::UserRole + 1).toString() != variable.m_wellCode) continue;
item->setDisabled(!available);
if (!refreshRangesOnly)
item->setCheckState(0, available && variable.getEnabled() ? Qt::Checked : Qt::Unchecked);
@ -1594,15 +1610,19 @@ void nmWxSensitive::loadDataToUI(bool refreshRangesOnly)
}
}
int nmWxSensitive::findVariableIndex(const QString& varName)
int nmWxSensitive::findVariableIndex(const nmDataSensitive::VariableSampling& variable)
{
// 根据创建顺序查找变量索引
QStringList allVars;
allVars << "Zw" << "Hw" << "Lw" << "Skin" << "C" // Tested Well
<< "Pi" << "k" << "h" << QString(QChar(0x03c6)) << "ntg" << "kz/kr" // Reservoir
<< "Total compressibility"; // Pvt
return allVars.indexOf(varName);
// 页面由树节点创建,通过井编码与参数名定位,不依赖固定页面次序。
for(int group = 0; group < variablesTree->topLevelItemCount(); ++group) {
QTreeWidgetItem* parent = variablesTree->topLevelItem(group);
for(int child = 0; child < parent->childCount(); ++child) {
QTreeWidgetItem* item = parent->child(child);
if(item->text(0) == variable.m_varName &&
item->data(0, Qt::UserRole + 1).toString() == variable.m_wellCode)
return item->data(0, Qt::UserRole).toInt() - 1;
}
}
return -1;
}
void nmWxSensitive::updateModelCount()
@ -1618,9 +1638,10 @@ void nmWxSensitive::updateModelCount()
QTreeWidgetItem* item = parent->child(child);
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
if (variable.getVarName() != item->text(0)) continue;
if (variable.getVarName() != item->text(0) ||
variable.m_wellCode != item->data(0, Qt::UserRole + 1).toString()) continue;
variable.setEnabled(!item->isDisabled() && item->checkState(0) == Qt::Checked);
const int idx = findVariableIndex(variable.getVarName());
const int idx = findVariableIndex(variable);
if (idx >= 0) variable.setNumber(m_numSpins[idx]->value());
}
}
@ -1637,10 +1658,21 @@ void nmWxSensitive::updateModelCount()
// 第四种确认设置且至少选择一个拟合参数后才开放生成,空选择直接禁用。
bool hasFittingParameters = false;
if(!m_sensitiveData.getImproveSettings().isNull()) {
const nmDataAutomaticFitting& fitting = m_sensitiveData.getImproveSettings()->parameters;
hasFittingParameters = fitting.getPermeabilitySelected() || fitting.getSkinSelected() ||
fitting.getWellboreStorageSelected() || fitting.getPorositySelected() ||
fitting.getFractureConductivitySelected() || fitting.getFractureHalfLengthSelected();
nmSensitivityFitSettings settings = *m_sensitiveData.getImproveSettings();
QVector<nmFitParameter>& parameters = settings.parameters.getModelParameters();
// 新勾选的采样只取消同井同参数的拟合,其他井的同名参数仍可参与。
for(int i = 0; i < parameters.size(); ++i) {
if(!parameters[i].wellCode.isEmpty() &&
!m_pDataManager->isWellSelectedForCalculation(parameters[i].wellCode))
parameters[i].selected = false;
for(int v = 0; v < m_sensitiveData.getVariableCount(); ++v) {
const nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(v);
if(variable.m_enabled && variable.parameterKey() == parameters[i].samplingKey())
parameters[i].selected = false;
}
}
hasFittingParameters = settings.confirmed && settings.parameters.hasSelectedParameters();
m_sensitiveData.setImproveSettings(QSharedPointer<const nmSensitivityFitSettings>(new nmSensitivityFitSettings(settings)));
}
generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning &&
(calculationTypeCombo->currentIndex() != 3 || hasFittingParameters));
@ -1655,7 +1687,7 @@ bool nmWxSensitive::saveDataFromUI()
for (int i = 0; i < candidate.getVariableCount(); ++i) {
nmDataSensitive::VariableSampling& variable = candidate.getVariable(i);
const QString name = variable.getVarName();
const int idx = findVariableIndex(name);
const int idx = findVariableIndex(variable);
if (idx < 0 || !isSupportedSamplingVariable(name)) continue;
const QString unit = m_unitLabels[idx]->text();
const QString baseUnit = samplingBaseUnit(name);
@ -1898,22 +1930,25 @@ void nmWxSensitive::populateTreeWidget()
{
variablesTree->clear();
// ===== Tested Well =====
QTreeWidgetItem *testedWellItem = new QTreeWidgetItem(variablesTree);
testedWellItem->setText(0, tr("Tested Well"));
testedWellItem->setCheckState(0, Qt::Unchecked);
testedWellItem->setFlags(testedWellItem->flags() | Qt::ItemIsUserCheckable);
QStringList testedWellChildren;
testedWellChildren << "Zw" << "Hw" << "Lw" << "Skin" << "C";
// 只列出实际参与模型的井,每口井复用现有的参数及采样页。
const QVector<nmCalculationWellRef> wells = m_pDataManager
? m_pDataManager->getEffectiveCalculationWells() : QVector<nmCalculationWellRef>();
int i;
for (i = 0; i < testedWellChildren.size(); ++i) {
QTreeWidgetItem *childItem = new QTreeWidgetItem(testedWellItem);
childItem->setText(0, testedWellChildren.at(i));
childItem->setCheckState(0, Qt::Unchecked);
childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable);
childItem->setDisabled(!isSupportedSamplingVariable(testedWellChildren.at(i)));
for(int w = 0; w < wells.size(); ++w) {
nmDataWellBase* well = m_pDataManager->findWellByCode(wells[w].m_sWellCode);
if(!well) continue;
QTreeWidgetItem* testedWellItem = new QTreeWidgetItem(variablesTree);
testedWellItem->setText(0, wells.size() == 1 ? tr("Tested Well") : well->getWellName());
testedWellItem->setCheckState(0, Qt::Unchecked);
QStringList children;
children << "Zw" << "Hw" << "Lw" << "Skin" << "C";
for(i = 0; i < children.size(); ++i) {
QTreeWidgetItem* child = new QTreeWidgetItem(testedWellItem);
child->setText(0, children[i]);
child->setData(0, Qt::UserRole + 1, well->getWellCode());
child->setCheckState(0, Qt::Unchecked);
child->setDisabled(!isSupportedSamplingVariable(children[i]));
}
}
// ===== Reservoir =====
@ -2296,7 +2331,7 @@ void nmWxSensitive::updateValuesForPage(int idx)
table->setRowCount(number);
nmDataSensitive::VariableSampling variable;
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
if (findVariableIndex(m_sensitiveData.getVariable(i).getVarName()) == idx) {
if (findVariableIndex(m_sensitiveData.getVariable(i)) == idx) {
variable = m_sensitiveData.getVariable(i);
break;
}
@ -2375,7 +2410,7 @@ void nmWxSensitive::updateDistributionPage(int idx)
}
for(int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
nmDataSensitive::VariableSampling variable = m_sensitiveData.getVariable(i);
if(findVariableIndex(variable.m_varName) != idx) continue;
if(findVariableIndex(variable) != idx) continue;
const bool valid = readDistributionPage(idx, variable);
m_distributionPlots[idx]->setVariable(variable, valid);
break;
@ -2393,8 +2428,9 @@ void nmWxSensitive::onSamplingEditingFinished()
for (int idx = 0; idx < m_minSpins.size(); ++idx) {
if (edit != m_minSpins[idx] && edit != m_maxSpins[idx]) continue;
for (int i = 0; i < m_sensitiveData.getVariableCount(); ++i) {
const QString name = m_sensitiveData.getVariable(i).getVarName();
if (findVariableIndex(name) != idx) continue;
const nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(i);
const QString name = variable.getVarName();
if (findVariableIndex(variable) != idx) continue;
double lower = 0.0, upper = 0.0;
const QString unit = m_unitLabels[idx]->text();
const QString baseUnit = samplingBaseUnit(name);
@ -2634,7 +2670,7 @@ void nmWxSensitive::onImproveSettingsClicked()
updateModelCount();
QStringList sampledParameters;
foreach(const nmDataSensitive::VariableSampling& variable, m_sensitiveData.getVariables())
if(variable.m_enabled) sampledParameters.append(variable.m_varName);
if(variable.m_enabled) sampledParameters.append(variable.parameterKey());
nmWxAutomaticFitting dialog(this, &settings, sampledParameters);
if(dialog.exec() == QDialog::Accepted) {
m_sensitiveData.setImproveSettings(QSharedPointer<const nmSensitivityFitSettings>(new nmSensitivityFitSettings(settings)));

Loading…
Cancel
Save