fix(nmNum): 移除储层厚度及压缩系数的自动拟合功能

- 删除厚度、综合压缩系数 Ct、岩石压缩系数 Cf 的拟合控件、选择标志、上下界及配置读写代码
- 同步精简 LM、PSO 参数向量,调整剩余参数索引、初值读取和拟合结果回写
- 更新拟合记录字段及元数据版本,保持参数名称与索引一致
- 保留厚度、Ct、Cf 作为求解器和代理模型的固定输入,拟合过程不再调整
feature/Adapt-Autofit-20260904
lvjunjie 16 hours ago
parent 586a40df5c
commit ec384f7483

@ -165,8 +165,8 @@ private:
QVector<QVector<double> > m_userInitialLogLogData;
AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown;
// 参数索引0 k1 skin2 wellboreC3 phi4 h5 Ct
// 6 Cf7 Swi8 Dfc9 fractureHalfLength。
// 参数索引0 k1 skin2 wellboreC3 phi4 Swi
// 5 Dfc6 fractureHalfLength。
QVector<bool> m_parameterSelected;
QVector<double> m_parameterLower;
QVector<double> m_parameterUpper;

@ -20,7 +20,7 @@ class QProcess;
// PSO粒子结构
// 这里的 position / velocity / bestPosition 只保存“用户勾选参与拟合的参数”,
// 不是完整的 11 个储层/井筒参数。完整参数向量会在写 trace 或调用代理模型时
// 拟合参数为 k/skin/wellboreC/phi/Swi。包含固定储层输入的物理参数向量在写 trace 或调用代理模型时
// 通过 buildTraceParameterVector() 重新组装。
//
// surrogate* 和 screeningDecision 是 PSO 加速筛选的辅助字段。真实 pbest / gbest
@ -295,13 +295,12 @@ private:
//
// 参数索引约定:
// 0 k 渗透率1 skin 表皮系数2 wellboreC 井筒储集;
// 3 phi 孔隙度4 h 储层厚度5 Ct 综合压缩系数;
// 6 Cf 岩石压缩系数7 Swi 初始含水饱和度。
// 3 phi 孔隙度4 Swi 初始含水饱和度。
// m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。
QVector<bool> m_parameterSelected; // 完整 8 个参数是否被用户勾选参与拟合
QVector<double> m_parameterLower; // 完整 8 个参数的搜索下界。
QVector<double> m_parameterUpper; // 完整 8 个参数的搜索上界。
QVector<int> m_enabledParamIndices; // 被勾选参数在完整 8 维体系中的索引。
QVector<bool> m_parameterSelected; // 0 k1 skin2 wellboreC3 phi4 Swi
QVector<double> m_parameterLower; // 5 个拟合参数的搜索下界。
QVector<double> m_parameterUpper; // 5 个拟合参数的搜索上界。
QVector<int> m_enabledParamIndices; // 被勾选参数在 5 维拟合参数中的索引。
QVector<QVector<double> > m_targetLogLogData; // 目标井 history log-log 曲线time/pressure/derivative。
QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。

@ -47,27 +47,6 @@ public:
nmDataAttribute& getPorosityMin();
void setPorosityMin(const nmDataAttribute& porosityMin);
// Getter and Setter for thicknessMax
nmDataAttribute& getThicknessMax();
void setThicknessMax(const nmDataAttribute& thicknessMax);
// Getter and Setter for thicknessMin
nmDataAttribute& getThicknessMin();
void setThicknessMin(const nmDataAttribute& thicknessMin);
// Getter and Setter for ctMax
nmDataAttribute& getCtMax();
void setCtMax(const nmDataAttribute& ctMax);
// Getter and Setter for ctMin
nmDataAttribute& getCtMin();
void setCtMin(const nmDataAttribute& ctMin);
// Getter and Setter for cfMax
nmDataAttribute& getCfMax();
void setCfMax(const nmDataAttribute& cfMax);
// Getter and Setter for cfMin
nmDataAttribute& getCfMin();
void setCfMin(const nmDataAttribute& cfMin);
// Getter and Setter for swiMax
nmDataAttribute& getSwiMax();
void setSwiMax(const nmDataAttribute& swiMax);
@ -116,15 +95,6 @@ public:
bool getPorositySelected() const;
void setPorositySelected(bool selected);
bool getThicknessSelected() const;
void setThicknessSelected(bool selected);
bool getCtSelected() const;
void setCtSelected(bool selected);
bool getCfSelected() const;
void setCfSelected(bool selected);
bool getSwiSelected() const;
void setSwiSelected(bool selected);
@ -140,9 +110,6 @@ private:
nmDataAttribute m_skinMax;
nmDataAttribute m_wellboreStorageMax;
nmDataAttribute m_porosityMax;
nmDataAttribute m_thicknessMax;
nmDataAttribute m_ctMax;
nmDataAttribute m_cfMax;
nmDataAttribute m_swiMax;
nmDataAttribute m_fractureConductivityMax;
nmDataAttribute m_fractureHalfLengthMax;
@ -152,9 +119,6 @@ private:
nmDataAttribute m_skinMin;
nmDataAttribute m_wellboreStorageMin;
nmDataAttribute m_porosityMin;
nmDataAttribute m_thicknessMin;
nmDataAttribute m_ctMin;
nmDataAttribute m_cfMin;
nmDataAttribute m_swiMin;
nmDataAttribute m_fractureConductivityMin;
nmDataAttribute m_fractureHalfLengthMin;
@ -170,9 +134,6 @@ private:
bool m_skinSelected; // 是否选择表皮系数进行拟合
bool m_wellboreStorageSelected; // 是否选择井筒储集系数进行拟合
bool m_porositySelected; // 是否选择孔隙度进行拟合
bool m_thicknessSelected; // 是否选择储层厚度进行拟合
bool m_ctSelected; // 是否选择综合压缩系数进行拟合
bool m_cfSelected; // 是否选择岩石压缩系数进行拟合
bool m_swiSelected; // 是否选择初始含水饱和度进行拟合
bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合
bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合

@ -88,9 +88,6 @@ private:
QCheckBox* m_sCheckBox; // 表皮系数
QCheckBox* m_cCheckBox; // 井筒储集系数
QCheckBox* m_phiCheckBox; // 孔隙度
QCheckBox* m_hCheckBox; // 储层厚度
QCheckBox* m_ctCheckBox; // 综合压缩系数
QCheckBox* m_cfCheckBox; // 岩石压缩系数
QCheckBox* m_swiCheckBox; // 初始含水饱和度
QCheckBox* m_dfcCheckBox; // 裂缝导流能力
QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长

@ -129,11 +129,11 @@ static QString jsonStringArray(const QStringList& values)
}
// 信赖域搜索统一在 [0, 1] 内部坐标工作。正值参数使用对数坐标,使内部相同步长
// 表示近似相同的相对变化,避免 k、C、Ct、Cf 等跨数量级参数被线性尺度支配;
// 表示近似相同的相对变化,避免 k、C、Dfc 等跨数量级参数被线性尺度支配;
// skin 可为负数、Swi 的物理意义是线性比例,因此二者保持有界线性坐标。
static bool useTrustRegionLogScale(int parameterIndex, double lower, double upper)
{
return parameterIndex != 1 && parameterIndex != 7 &&
return parameterIndex != 1 && parameterIndex != 4 &&
lower > 0.0 && upper > lower;
}
@ -284,7 +284,7 @@ static int trustRegionDominantComponent(
}
// 求解选中参数对应的阻尼正规方程。上下和左右诊断量保留方向;形状没有
// 天然正负,因此使用 shapeLoss 对参数的局部导数。参数最多维,使用带
// 天然正负,因此使用 shapeLoss 对参数的局部导数。参数最多维,使用带
// 部分主元的高斯消元即可处理该小矩阵,并在主元退化时明确返回失败。
static bool solveTrustRegionLinearSystem(
QVector<QVector<double> > matrix,
@ -453,9 +453,6 @@ static QStringList traceParameterNames()
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi"
<< "Dfc"
<< "fractureHalfLength";
@ -743,9 +740,6 @@ void nmCalculationAutoFitLM::writeTraceHeader()
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi"
<< "Dfc"
<< "fractureHalfLength"
@ -804,7 +798,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 1,\n";
out << " \"schema_version\": 2,\n";
out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
out << " \"created_at\": "
@ -861,7 +855,7 @@ void nmCalculationAutoFitLM::writeTraceRow(
<< QString::number(iteration)
<< QString::number(parameterIndex)
<< csvEscape(phase);
for(int i = 0; i < 10; ++i) {
for(int i = 0; i < 7; ++i) {
cols << traceParamAt(fullParams, i);
}
cols << traceNumber(solverObjective)
@ -919,10 +913,10 @@ void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReas
QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{
// 将 LM 内部使用的“启用参数向量”还原成完整 10 维参数向量。
// 将 LM 内部使用的“启用参数向量”还原成完整 7 维参数向量。
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。
// trace CSV 和 meta 使用该完整向量记录一次候选评价。
QVector<double> fullParams(10, 0.0);
QVector<double> fullParams(7, 0.0);
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@ -930,10 +924,7 @@ QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
fullParams[0] = reservoirData.getPermeability().getValue().toDouble();
fullParams[3] = reservoirData.getPorosity().getValue().toDouble();
fullParams[4] = reservoirData.getThickness().getValue().toDouble();
fullParams[5] = reservoirData.getCt().getValue().toDouble();
fullParams[6] = reservoirData.getCf().getValue().toDouble();
fullParams[7] = reservoirData.getSwi().getValue().toDouble();
fullParams[4] = reservoirData.getSwi().getValue().toDouble();
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
@ -949,15 +940,15 @@ QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
fullParams[5] = fracturedWell->getDfc().getValue().toDouble();
fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
} else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
fullParams[5] = fracturedWell->getDfc().getValue().toDouble();
fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
}
}
@ -1008,28 +999,25 @@ void nmCalculationAutoFitLM::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
// - m_parameterSelected[10]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[10]:完整参数体系的搜索上下界;
// - m_parameterSelected[7]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[7]:完整参数体系的搜索上下界;
// - m_enabledParamIndices把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
m_parameterSelected.resize(10);
m_parameterSelected.resize(7);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
m_parameterSelected[3] = fittingData.getPorositySelected();
m_parameterSelected[4] = fittingData.getThicknessSelected();
m_parameterSelected[5] = fittingData.getCtSelected();
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[8] = fittingData.getFractureConductivitySelected();
m_parameterSelected[9] = fittingData.getFractureHalfLengthSelected();
m_parameterSelected[4] = fittingData.getSwiSelected();
m_parameterSelected[5] = fittingData.getFractureConductivitySelected();
m_parameterSelected[6] = fittingData.getFractureHalfLengthSelected();
// 获取参数边界
m_parameterLower.resize(10);
m_parameterUpper.resize(10);
m_parameterLower.resize(7);
m_parameterUpper.resize(7);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@ -1043,23 +1031,14 @@ void nmCalculationAutoFitLM::loadParameterBounds()
m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble();
m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getThicknessMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getThicknessMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getCtMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getCtMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getFractureConductivityMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getFractureConductivityMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getCfMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getCfMax().getValue().toDouble();
m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[8] = fittingData.getFractureConductivityMin().getValue().toDouble();
m_parameterUpper[8] = fittingData.getFractureConductivityMax().getValue().toDouble();
m_parameterLower[9] = fittingData.getFractureHalfLengthMin().getValue().toDouble();
m_parameterUpper[9] = fittingData.getFractureHalfLengthMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getFractureHalfLengthMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getFractureHalfLengthMax().getValue().toDouble();
// 更新启用参数索引
m_enabledParamIndices.clear();
@ -1256,8 +1235,8 @@ bool nmCalculationAutoFitLM::startAutoFitting()
// 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
(m_parameterSelected.size() > 5 && m_parameterSelected[5]) ||
(m_parameterSelected.size() > 6 && m_parameterSelected[6]);
if(fractureGridParameterSelected) {
nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance();
@ -1427,23 +1406,11 @@ void nmCalculationAutoFitLM::extractUserInitialValues()
initialValue = reservoirData.getPorosity().getValue().toDouble();
break;
case 4: // 储层厚度
initialValue = reservoirData.getThickness().getValue().toDouble();
break;
case 5: // 综合压缩系数
initialValue = reservoirData.getCt().getValue().toDouble();
break;
case 6: // 岩石压缩系数
initialValue = reservoirData.getCf().getValue().toDouble();
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
initialValue = reservoirData.getSwi().getValue().toDouble();
break;
case 8: // 裂缝导流能力
case 5: // 裂缝导流能力
if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
@ -1459,7 +1426,7 @@ void nmCalculationAutoFitLM::extractUserInitialValues()
}
break;
case 9: // 裂缝半长
case 6: // 裂缝半长
if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
@ -2553,8 +2520,8 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector<double>& parameters
// Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。
const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
(m_parameterSelected.size() > 9 && m_parameterSelected[9]);
(m_parameterSelected.size() > 5 && m_parameterSelected[5]) ||
(m_parameterSelected.size() > 6 && m_parameterSelected[6]);
if(fractureGridParameterSelected) {
dataManager->invalidatePebiGrid();
}
@ -2721,7 +2688,7 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector<double>& pa
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
// paramIndex 是粒子 position 中的索引i 是完整 10 个参数体系中的索引。
// paramIndex 是粒子 position 中的索引i 是完整 7 个参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@ -2738,19 +2705,7 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector<double>& pa
reservoirData.getPorosity().setValue(value);
break;
case 4: // 储层厚度
reservoirData.getThickness().setValue(value);
break;
case 5: // 综合压缩系数
reservoirData.getCt().setValue(value);
break;
case 6: // 岩石压缩系数
reservoirData.getCf().setValue(value);
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
reservoirData.getSwi().setValue(value);
break;
}
@ -2828,7 +2783,7 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector<double>& paramet
}
break;
case 8: { // 裂缝导流能力
case 5: { // 裂缝导流能力
if(pVerticalFracturedWell || pHorizontalFracturedWell) {
dfcAttr.setValue(value);
updateDfc = true;
@ -2836,7 +2791,7 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector<double>& paramet
}
break;
case 9: { // 裂缝半长
case 6: { // 裂缝半长
if(pVerticalFracturedWell || pHorizontalFracturedWell) {
fractureHalfLengthAttr.setValue(value);
updateFractureHalfLength = true;

@ -426,16 +426,13 @@ static QString findExecutableInPath(const QString& executableName)
static QStringList traceParameterNames()
{
// trace 和 trace meta 使用的完整参数名顺序
// 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-7 索引一致
// 拟合参数顺序与 m_parameterSelected 的 0-4 索引一致
// 代理输入仍使用 buildTraceParameterVector() 的物理参数顺序
QStringList names;
names << "k"
<< "skin"
<< "wellboreC"
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "Swi";
return names;
}
@ -979,7 +976,9 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
enabledNames << ((paramIndex >= 0 && paramIndex < parameterNames.size()) ? parameterNames[paramIndex] : QString::number(paramIndex));
}
QVector<double> initialFullParams = buildTraceParameterVector(m_initialValues);
QVector<double> initialPhysicalParams = buildTraceParameterVector(m_initialValues);
QVector<double> initialFullParams = initialPhysicalParams.mid(0, 4);
initialFullParams.append(initialPhysicalParams[7]); // Swi 在物理输入中的位置不变。
QVector<double> targetTime = m_targetLogLogData.size() > 0 ? m_targetLogLogData[0] : QVector<double>();
QVector<double> targetPressure = m_targetLogLogData.size() > 1 ? m_targetLogLogData[1] : QVector<double>();
QVector<double> targetDerivative = m_targetLogLogData.size() > 2 ? m_targetLogLogData[2] : QVector<double>();
@ -987,7 +986,7 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 1,\n";
out << " \"schema_version\": 2,\n";
out << " \"trace_type\": \"pso_baseline_replay_meta\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
out << " \"created_at\": " << jsonEscape(QDateTime::currentDateTime().toString(Qt::ISODate)) << ",\n";
@ -1046,8 +1045,8 @@ bool nmCalculationAutoFitPSO::writeTraceMetaFile(const QString& filePath)
QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{
// 将粒子内部使用的“启用参数向量”还原成完整 8 维参数向量
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖
// 代理输入保留 k/skin/wellboreC/phi/h/Ct/Cf/Swi 的 8 维物理参数顺序
// h/Ct/Cf 始终读取储层固定值,仅用候选值覆盖剩余拟合参数
// trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。
QVector<double> fullParams(8, 0.0);
@ -1073,8 +1072,9 @@ QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector
for(int i = 0; i < selectedParameters.size() && i < m_enabledParamIndices.size(); ++i) {
int paramIndex = m_enabledParamIndices[i];
if(paramIndex >= 0 && paramIndex < fullParams.size()) {
fullParams[paramIndex] = selectedParameters[i];
if(paramIndex >= 0 && paramIndex < m_parameterSelected.size()) {
const int physicalIndex = paramIndex == 4 ? 7 : paramIndex;
fullParams[physicalIndex] = selectedParameters[i];
}
}
@ -1993,14 +1993,9 @@ bool nmCalculationAutoFitPSO::isSurrogateRunContextSupported(QString* reason) co
return false;
}
// 参数门控:只有训练数据中 Cf 实际变化的 T2/T4 允许 Cf 参与拟合。
// 用户勾选其它参数时,代理无法可靠反映这些参数变化,直接禁用代理筛选。
// 代理支持拟合 k/skin/wellboreC/phiSwi 不在训练输入集中。
QVector<int> allowedParamIndices;
allowedParamIndices << 0 << 1 << 2 << 3 << 4;
if(parameterDomain.variableCf) {
allowedParamIndices << 6;
}
allowedParamIndices << 0 << 1 << 2 << 3;
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
if(!allowedParamIndices.contains(m_enabledParamIndices[i])) {
@ -2714,26 +2709,23 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
// - m_parameterSelected[8]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[8]:完整参数体系的搜索上下界;
// - m_parameterSelected[5]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[5]:完整参数体系的搜索上下界;
// - m_enabledParamIndices把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
m_parameterSelected.resize(8);
m_parameterSelected.resize(5);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
m_parameterSelected[3] = fittingData.getPorositySelected();
m_parameterSelected[4] = fittingData.getThicknessSelected();
m_parameterSelected[5] = fittingData.getCtSelected();
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[4] = fittingData.getSwiSelected();
// 获取参数边界
m_parameterLower.resize(8);
m_parameterUpper.resize(8);
m_parameterLower.resize(5);
m_parameterUpper.resize(5);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@ -2747,17 +2739,8 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble();
m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getThicknessMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getThicknessMax().getValue().toDouble();
m_parameterLower[5] = fittingData.getCtMin().getValue().toDouble();
m_parameterUpper[5] = fittingData.getCtMax().getValue().toDouble();
m_parameterLower[6] = fittingData.getCfMin().getValue().toDouble();
m_parameterUpper[6] = fittingData.getCfMax().getValue().toDouble();
m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble();
m_parameterLower[4] = fittingData.getSwiMin().getValue().toDouble();
m_parameterUpper[4] = fittingData.getSwiMax().getValue().toDouble();
// 更新启用参数索引
m_enabledParamIndices.clear();
@ -3400,19 +3383,7 @@ void nmCalculationAutoFitPSO::extractUserInitialValues()
initialValue = reservoirData.getPorosity().getValue().toDouble();
break;
case 4: // 储层厚度
initialValue = reservoirData.getThickness().getValue().toDouble();
break;
case 5: // 综合压缩系数
initialValue = reservoirData.getCt().getValue().toDouble();
break;
case 6: // 岩石压缩系数
initialValue = reservoirData.getCf().getValue().toDouble();
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
initialValue = reservoirData.getSwi().getValue().toDouble();
break;
}
@ -4196,7 +4167,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& p
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
// paramIndex 是粒子 position 中的索引i 是完整 8 个参数体系中的索引。
// paramIndex 是粒子 position 中的索引i 是完整 5 个拟合参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@ -4213,19 +4184,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& p
reservoirData.getPorosity().setValue(value);
break;
case 4: // 储层厚度
reservoirData.getThickness().setValue(value);
break;
case 5: // 综合压缩系数
reservoirData.getCt().setValue(value);
break;
case 6: // 岩石压缩系数
reservoirData.getCf().setValue(value);
break;
case 7: // 初始含水饱和度
case 4: // 初始含水饱和度
reservoirData.getSwi().setValue(value);
break;
}
@ -4657,14 +4616,6 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector<double>& paramete
return false;
}
break;
case 5: // 综合压缩系数:必须大于零
if(value <= 1e-8) {
DEBUG_OUT(QString("Rejecting near-zero total compressibility: %1").arg(value));
return false;
}
break;
}
}

@ -7,9 +7,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinSelected = true; // 默认选中
m_wellboreStorageSelected = true; // 默认选中
m_porositySelected = true; // 默认选中
m_thicknessSelected = true; // 默认选中
m_ctSelected = true; // 默认选中
m_cfSelected = false; // 默认不选中
m_swiSelected = false; // 默认不选中
m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中
m_fractureHalfLengthSelected = false; // 仅压裂井可用,默认不选中
@ -19,9 +16,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinMax = nmDataAttribute("Skin Max", QVariant(), "");
m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", QVariant(), "m^3/MPa");
m_porosityMax = nmDataAttribute("Porosity Max", QVariant(), "");
m_thicknessMax = nmDataAttribute("Thickness Max", QVariant(), "m");
m_ctMax = nmDataAttribute("Ct Max", QVariant(), "");
m_cfMax = nmDataAttribute("Cf Max", QVariant(), "");
m_swiMax = nmDataAttribute("Swi Max", QVariant(), "");
m_fractureConductivityMax = nmDataAttribute("Fracture Conductivity Max", QVariant(), "md.m");
m_fractureHalfLengthMax = nmDataAttribute("Fracture Half Length Max", QVariant(), "m");
@ -30,9 +24,6 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_skinMin = nmDataAttribute("Skin Min", QVariant(), "");
m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", QVariant(), "m^3/MPa");
m_porosityMin = nmDataAttribute("Porosity Min", QVariant(), "");
m_thicknessMin = nmDataAttribute("Thickness Min", QVariant(), "m");
m_ctMin = nmDataAttribute("Ct Min", QVariant(), "");
m_cfMin = nmDataAttribute("Cf Min", QVariant(), "");
m_swiMin = nmDataAttribute("Swi Min", QVariant(), "");
m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "mD.m");
m_fractureHalfLengthMin = nmDataAttribute("Fracture Half Length Min", QVariant(), "m");
@ -61,9 +52,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinSelected = other.m_skinSelected;
m_wellboreStorageSelected = other.m_wellboreStorageSelected;
m_porositySelected = other.m_porositySelected;
m_thicknessSelected = other.m_thicknessSelected;
m_ctSelected = other.m_ctSelected;
m_cfSelected = other.m_cfSelected;
m_swiSelected = other.m_swiSelected;
m_fractureConductivitySelected = other.m_fractureConductivitySelected;
m_fractureHalfLengthSelected = other.m_fractureHalfLengthSelected;
@ -73,9 +61,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinMax = other.m_skinMax;
m_wellboreStorageMax = other.m_wellboreStorageMax;
m_porosityMax = other.m_porosityMax;
m_thicknessMax = other.m_thicknessMax;
m_ctMax = other.m_ctMax;
m_cfMax = other.m_cfMax;
m_swiMax = other.m_swiMax;
m_fractureConductivityMax = other.m_fractureConductivityMax;
m_fractureHalfLengthMax = other.m_fractureHalfLengthMax;
@ -85,9 +70,6 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_skinMin = other.m_skinMin;
m_wellboreStorageMin = other.m_wellboreStorageMin;
m_porosityMin = other.m_porosityMin;
m_thicknessMin = other.m_thicknessMin;
m_ctMin = other.m_ctMin;
m_cfMin = other.m_cfMin;
m_swiMin = other.m_swiMin;
m_fractureConductivityMin = other.m_fractureConductivityMin;
m_fractureHalfLengthMin = other.m_fractureHalfLengthMin;
@ -110,9 +92,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinSelected", m_skinSelected, allocator);
fittingObject.AddMember("WellboreStorageSelected", m_wellboreStorageSelected, allocator);
fittingObject.AddMember("PorositySelected", m_porositySelected, allocator);
fittingObject.AddMember("ThicknessSelected", m_thicknessSelected, allocator);
fittingObject.AddMember("CtSelected", m_ctSelected, allocator);
fittingObject.AddMember("CfSelected", m_cfSelected, allocator);
fittingObject.AddMember("SwiSelected", m_swiSelected, allocator);
fittingObject.AddMember("FractureConductivitySelected", m_fractureConductivitySelected, allocator);
fittingObject.AddMember("FractureHalfLengthSelected", m_fractureHalfLengthSelected, allocator);
@ -122,9 +101,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinMax", m_skinMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("WellboreStorageMax", m_wellboreStorageMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("PorosityMax", m_porosityMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("ThicknessMax", m_thicknessMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CtMax", m_ctMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CfMax", m_cfMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("SwiMax", m_swiMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureConductivityMax", m_fractureConductivityMax.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureHalfLengthMax", m_fractureHalfLengthMax.ToJsonValue(allocator), allocator);
@ -134,9 +110,6 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("SkinMin", m_skinMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("WellboreStorageMin", m_wellboreStorageMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("PorosityMin", m_porosityMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("ThicknessMin", m_thicknessMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CtMin", m_ctMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("CfMin", m_cfMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("SwiMin", m_swiMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureConductivityMin", m_fractureConductivityMin.ToJsonValue(allocator), allocator);
fittingObject.AddMember("FractureHalfLengthMin", m_fractureHalfLengthMin.ToJsonValue(allocator), allocator);
@ -165,15 +138,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorositySelected") && jsonValue["PorositySelected"].IsBool()) {
m_porositySelected = jsonValue["PorositySelected"].GetBool();
}
if (jsonValue.HasMember("ThicknessSelected") && jsonValue["ThicknessSelected"].IsBool()) {
m_thicknessSelected = jsonValue["ThicknessSelected"].GetBool();
}
if (jsonValue.HasMember("CtSelected") && jsonValue["CtSelected"].IsBool()) {
m_ctSelected = jsonValue["CtSelected"].GetBool();
}
if (jsonValue.HasMember("CfSelected") && jsonValue["CfSelected"].IsBool()) {
m_cfSelected = jsonValue["CfSelected"].GetBool();
}
if (jsonValue.HasMember("SwiSelected") && jsonValue["SwiSelected"].IsBool()) {
m_swiSelected = jsonValue["SwiSelected"].GetBool();
}
@ -199,15 +163,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorosityMax") && jsonValue["PorosityMax"].IsObject()) {
m_porosityMax.FromJsonValue(jsonValue["PorosityMax"]);
}
if (jsonValue.HasMember("ThicknessMax") && jsonValue["ThicknessMax"].IsObject()) {
m_thicknessMax.FromJsonValue(jsonValue["ThicknessMax"]);
}
if (jsonValue.HasMember("CtMax") && jsonValue["CtMax"].IsObject()) {
m_ctMax.FromJsonValue(jsonValue["CtMax"]);
}
if (jsonValue.HasMember("CfMax") && jsonValue["CfMax"].IsObject()) {
m_cfMax.FromJsonValue(jsonValue["CfMax"]);
}
if (jsonValue.HasMember("SwiMax") && jsonValue["SwiMax"].IsObject()) {
m_swiMax.FromJsonValue(jsonValue["SwiMax"]);
}
@ -232,15 +187,6 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("PorosityMin") && jsonValue["PorosityMin"].IsObject()) {
m_porosityMin.FromJsonValue(jsonValue["PorosityMin"]);
}
if (jsonValue.HasMember("ThicknessMin") && jsonValue["ThicknessMin"].IsObject()) {
m_thicknessMin.FromJsonValue(jsonValue["ThicknessMin"]);
}
if (jsonValue.HasMember("CtMin") && jsonValue["CtMin"].IsObject()) {
m_ctMin.FromJsonValue(jsonValue["CtMin"]);
}
if (jsonValue.HasMember("CfMin") && jsonValue["CfMin"].IsObject()) {
m_cfMin.FromJsonValue(jsonValue["CfMin"]);
}
if (jsonValue.HasMember("SwiMin") && jsonValue["SwiMin"].IsObject()) {
m_swiMin.FromJsonValue(jsonValue["SwiMin"]);
}
@ -280,15 +226,6 @@ void nmDataAutomaticFitting::setWellboreStorageSelected(bool selected) { m_wellb
bool nmDataAutomaticFitting::getPorositySelected() const { return m_porositySelected; }
void nmDataAutomaticFitting::setPorositySelected(bool selected) { m_porositySelected = selected; }
bool nmDataAutomaticFitting::getThicknessSelected() const { return m_thicknessSelected; }
void nmDataAutomaticFitting::setThicknessSelected(bool selected) { m_thicknessSelected = selected; }
bool nmDataAutomaticFitting::getCtSelected() const { return m_ctSelected; }
void nmDataAutomaticFitting::setCtSelected(bool selected) { m_ctSelected = selected; }
bool nmDataAutomaticFitting::getCfSelected() const { return m_cfSelected; }
void nmDataAutomaticFitting::setCfSelected(bool selected) { m_cfSelected = selected; }
bool nmDataAutomaticFitting::getSwiSelected() const { return m_swiSelected; }
void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = selected; }
@ -312,15 +249,6 @@ void nmDataAutomaticFitting::setWellboreStorageMax(const nmDataAttribute& wellbo
nmDataAttribute& nmDataAutomaticFitting::getPorosityMax() { return m_porosityMax; }
void nmDataAutomaticFitting::setPorosityMax(const nmDataAttribute& porosityMax) { m_porosityMax = porosityMax; }
nmDataAttribute& nmDataAutomaticFitting::getThicknessMax() { return m_thicknessMax; }
void nmDataAutomaticFitting::setThicknessMax(const nmDataAttribute& thicknessMax) { m_thicknessMax = thicknessMax; }
nmDataAttribute& nmDataAutomaticFitting::getCtMax() { return m_ctMax; }
void nmDataAutomaticFitting::setCtMax(const nmDataAttribute& ctMax) { m_ctMax = ctMax; }
nmDataAttribute& nmDataAutomaticFitting::getCfMax() { return m_cfMax; }
void nmDataAutomaticFitting::setCfMax(const nmDataAttribute& cfMax) { m_cfMax = cfMax; }
nmDataAttribute& nmDataAutomaticFitting::getSwiMax() { return m_swiMax; }
void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax = swiMax; }
@ -343,15 +271,6 @@ void nmDataAutomaticFitting::setWellboreStorageMin(const nmDataAttribute& wellbo
nmDataAttribute& nmDataAutomaticFitting::getPorosityMin() { return m_porosityMin; }
void nmDataAutomaticFitting::setPorosityMin(const nmDataAttribute& porosityMin) { m_porosityMin = porosityMin; }
nmDataAttribute& nmDataAutomaticFitting::getThicknessMin() { return m_thicknessMin; }
void nmDataAutomaticFitting::setThicknessMin(const nmDataAttribute& thicknessMin) { m_thicknessMin = thicknessMin; }
nmDataAttribute& nmDataAutomaticFitting::getCtMin() { return m_ctMin; }
void nmDataAutomaticFitting::setCtMin(const nmDataAttribute& ctMin) { m_ctMin = ctMin; }
nmDataAttribute& nmDataAutomaticFitting::getCfMin() { return m_cfMin; }
void nmDataAutomaticFitting::setCfMin(const nmDataAttribute& cfMin) { m_cfMin = cfMin; }
nmDataAttribute& nmDataAutomaticFitting::getSwiMin() { return m_swiMin; }
void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin = swiMin; }

@ -165,7 +165,7 @@ void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row,
}
}
// 隐藏参数行后重新整理序号,让界面看起来像删除了不需要的参数
// 按当前模型和井型可用的参数行重新整理显示序号
void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
{
if(!table) {
@ -189,7 +189,7 @@ void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
}
}
// 根据当前模型类型控制 Ct/Cf/Swi并根据目标井类型控制裂缝导流能力显示。
// 拟合表仅包含可优化参数,按模型和目标井类型控制显示。
void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType)
{
if(!table) {
@ -200,35 +200,7 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
setParameterRowVisible(table, row, true);
}
bool showCt = false;
bool showCf = false;
bool showSwi = false;
switch(eType) {
case SMT_Oil_ConstPvt:
case SMT_Water_ConstPvt:
// 常量PVT使用综合压缩系数Ct。
showCt = true;
break;
case SMT_Oil_VariablePvt:
case SMT_Water_VariablePvt:
case SMT_Gas_VariablePvt:
// 变化PVT使用岩石压缩系数Cf。
showCf = true;
break;
case SMT_Oil_Water_TwoPhase:
// 油水两相使用Cf并且只有它需要初始含水饱和度Swi。
showCf = true;
showSwi = true;
break;
default:
showCf = true;
break;
}
setParameterRowVisible(table, 5, showCt); // Ct
setParameterRowVisible(table, 6, showCf); // Cf
setParameterRowVisible(table, 7, showSwi); // Swi
setParameterRowVisible(table, 4, eType == SMT_Oil_Water_TwoPhase); // Swi
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
@ -243,8 +215,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
wellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
setParameterRowVisible(table, 8, showFractureConductivity); // Dfc
setParameterRowVisible(table, 9, showFractureConductivity); // 裂缝半长
setParameterRowVisible(table, 5, showFractureConductivity); // Dfc
setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长
renumberVisibleParameterRows(table);
}
@ -255,10 +227,10 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
{
static const char* parameterNames[] = {
"Result_K", "Result_W_Skin", "Result_W_C", "Result_phi",
"Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc",
"Result_Swi", "Result_W_Dfc",
"W_FractureHalfLength"
};
if(parameterIndex < 0 || parameterIndex >= 10) {
if(parameterIndex < 0 || parameterIndex >= 7) {
return false;
}
@ -266,7 +238,7 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
return false;
}
if(parameterIndex == 7) {
if(parameterIndex == 4) {
double soi = reservoirData.getSoi().getValue().toDouble();
double sgi = reservoirData.getSgi().getValue().toDouble();
if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) {
@ -324,26 +296,14 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
automaticFittingData.getPorosityMax().setValue(maxValue);
break;
case 4:
automaticFittingData.getThicknessMin().setValue(minValue);
automaticFittingData.getThicknessMax().setValue(maxValue);
break;
case 5:
automaticFittingData.getCtMin().setValue(minValue);
automaticFittingData.getCtMax().setValue(maxValue);
break;
case 6:
automaticFittingData.getCfMin().setValue(minValue);
automaticFittingData.getCfMax().setValue(maxValue);
break;
case 7:
automaticFittingData.getSwiMin().setValue(minValue);
automaticFittingData.getSwiMax().setValue(maxValue);
break;
case 8:
case 5:
automaticFittingData.getFractureConductivityMin().setValue(minValue);
automaticFittingData.getFractureConductivityMax().setValue(maxValue);
break;
case 9:
case 6:
automaticFittingData.getFractureHalfLengthMin().setValue(minValue);
automaticFittingData.getFractureHalfLengthMax().setValue(maxValue);
break;
@ -359,7 +319,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 10
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 7
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
@ -395,17 +355,17 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
newMin = qMax(physicalMin, reference - skinHalfRange);
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0
&& !(parameterIndex == 7 && centerValue <= 0.0)
&& !(parameterIndex == 8 && centerValue <= 0.0)) {
&& !(parameterIndex == 4 && centerValue <= 0.0)
&& !(parameterIndex == 5 && centerValue <= 0.0)) {
const double lowerFactor = 0.1;
const double upperFactor = 10.0;
newMin = qMax(physicalMin, reference * lowerFactor);
newMax = qMin(physicalMax, reference * upperFactor);
} else if(parameterIndex == 7) {
} else if(parameterIndex == 4) {
// 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。
newMin = physicalMin;
newMax = physicalMax;
} else if(parameterIndex == 8) {
} else if(parameterIndex == 5) {
// Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。
newMin = physicalMin;
newMax = physicalMax;
@ -429,7 +389,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) {
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(initialItem) {
bool initialOk = false;
@ -456,7 +416,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
@ -478,7 +438,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
const bool savedRangeValid = savedMinOk && savedMaxOk
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
&& savedMax >= savedMin
&& !(parameterIndex == 8 && savedMax <= 1.0e-10);
&& !(parameterIndex == 5 && savedMax <= 1.0e-10);
if(savedRangeValid && physicalMax >= physicalMin) {
const double clippedMin = qMax(savedMin, physicalMin);
@ -507,19 +467,19 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
errorMessage = tr("The parameter table is unavailable.");
return false;
}
if(parameterIndex < -1 || parameterIndex >= 10) {
if(parameterIndex < -1 || parameterIndex >= 7) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
"Thickness", "Ct", "Cf", "Swi", "Fracture conductivity",
"Swi", "Fracture conductivity",
"Fracture half length"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? 10 : parameterIndex + 1;
const int lastParameterIndex = parameterIndex < 0 ? 7 : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
@ -581,7 +541,7 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
}
// 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。
if(currentParameterIndex == 8 && m_dfcCheckBox->isChecked()
if(currentParameterIndex == 5 && m_dfcCheckBox->isChecked()
&& (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) {
errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.")
.arg(tr(parameterNames[currentParameterIndex]));
@ -632,20 +592,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
// 自动范围始终开启用户在表格中修改上下限后itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true;
NM_SOLVER_MODEL_TYPE solverModelType = pManager->getSolverModelType();
// 未保存过配置时只保留参数选择的相态默认值,不再覆盖数据对象中的初值或范围。
if(!hasSavedFittingData) {
if(solverModelType == SMT_Oil_ConstPvt ||
solverModelType == SMT_Water_ConstPvt) {
// T1/T3 的综合压缩系数默认不参与拟合。
automaticFittingData.setCtSelected(false);
} else if(solverModelType == SMT_Oil_VariablePvt ||
solverModelType == SMT_Water_VariablePvt) {
// T2/T4 的岩石压缩系数默认参与拟合。
automaticFittingData.setCfSelected(true);
}
}
setupUI();
setWindowTitle(tr("Automatic fitting"));
setModal(true);
@ -716,7 +662,7 @@ void nmWxAutomaticFitting::setupUI()
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
m_parameterTable = new QTableWidget(10, 6, this);
m_parameterTable = new QTableWidget(7, 6, this);
// 设置表头
QStringList headers;
@ -785,66 +731,36 @@ void nmWxAutomaticFitting::setupParameterTable()
m_parameterTable->setItem(3, 4, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMax().getValue().toDouble())));
m_parameterTable->setItem(3, 5, new QTableWidgetItem(""));
// 储层厚度 (Thickness)
m_parameterTable->setItem(4, 0, new QTableWidgetItem("5"));
m_hCheckBox = new QCheckBox(tr("Thickness"));
m_hCheckBox->setChecked(automaticFittingData.getThicknessSelected());
m_parameterTable->setCellWidget(4, 1, m_hCheckBox);
m_parameterTable->setItem(4, 2, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMin().getValue().toDouble())));
m_parameterTable->setItem(4, 3, new QTableWidgetItem(QString::number(reservoirData.getThickness().getValue().toDouble())));
m_parameterTable->setItem(4, 4, new QTableWidgetItem(QString::number(automaticFittingData.getThicknessMax().getValue().toDouble())));
m_parameterTable->setItem(4, 5, new QTableWidgetItem(tr("m")));
// 综合压缩系数 (Ct)
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_ctCheckBox = new QCheckBox(tr("Ct"));
m_ctCheckBox->setChecked(automaticFittingData.getCtSelected());
m_parameterTable->setCellWidget(5, 1, m_ctCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCtMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem(QString::number(reservoirData.getCt().getValue().toDouble())));
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCtMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(""));
// 岩石压缩系数 (Cf)
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_cfCheckBox = new QCheckBox(tr("Cf"));
m_cfCheckBox->setChecked(automaticFittingData.getCfSelected());
m_parameterTable->setCellWidget(6, 1, m_cfCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getCfMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem(QString::number(reservoirData.getCf().getValue().toDouble())));
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getCfMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(""));
// 初始含水饱和度 (Swi)
m_parameterTable->setItem(7, 0, new QTableWidgetItem("8"));
m_parameterTable->setItem(4, 0, new QTableWidgetItem("5"));
m_swiCheckBox = new QCheckBox(tr("Swi"));
m_swiCheckBox->setChecked(automaticFittingData.getSwiSelected());
m_parameterTable->setCellWidget(7, 1, m_swiCheckBox);
m_parameterTable->setItem(7, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMin().getValue().toDouble())));
m_parameterTable->setItem(7, 3, new QTableWidgetItem(QString::number(reservoirData.getSwi().getValue().toDouble())));
m_parameterTable->setItem(7, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(7, 5, new QTableWidgetItem(""));
m_parameterTable->setCellWidget(4, 1, m_swiCheckBox);
m_parameterTable->setItem(4, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMin().getValue().toDouble())));
m_parameterTable->setItem(4, 3, new QTableWidgetItem(QString::number(reservoirData.getSwi().getValue().toDouble())));
m_parameterTable->setItem(4, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(4, 5, new QTableWidgetItem(""));
// 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中
// 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。
m_parameterTable->setItem(8, 0, new QTableWidgetItem("9"));
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity"));
m_dfcCheckBox->setChecked(automaticFittingData.getFractureConductivitySelected());
m_parameterTable->setCellWidget(8, 1, m_dfcCheckBox);
m_parameterTable->setItem(8, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(8, 3, new QTableWidgetItem());
m_parameterTable->setItem(8, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(8, 5, new QTableWidgetItem(tr("md.m")));
m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem());
m_parameterTable->setItem(5, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(5, 5, new QTableWidgetItem(tr("md.m")));
// 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。
m_parameterTable->setItem(9, 0, new QTableWidgetItem("10"));
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length"));
m_fractureHalfLengthCheckBox->setChecked(automaticFittingData.getFractureHalfLengthSelected());
m_parameterTable->setCellWidget(9, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(9, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(9, 3, new QTableWidgetItem());
m_parameterTable->setItem(9, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble())));
m_parameterTable->setItem(9, 5, new QTableWidgetItem(tr("m")));
m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem());
m_parameterTable->setItem(6, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble())));
m_parameterTable->setItem(6, 5, new QTableWidgetItem(tr("m")));
// 设置表格行为
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
@ -1047,12 +963,9 @@ void nmWxAutomaticFitting::onReverseSelection()
if(!m_parameterTable->isRowHidden(1)) m_sCheckBox->setChecked(!m_sCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(2)) m_cCheckBox->setChecked(!m_cCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(3)) m_phiCheckBox->setChecked(!m_phiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(4)) m_hCheckBox->setChecked(!m_hCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(5)) m_ctCheckBox->setChecked(!m_ctCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(6)) m_cfCheckBox->setChecked(!m_cfCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(8)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(9)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(4)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(5)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked());
if(!m_parameterTable->isRowHidden(6)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked());
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
@ -1169,8 +1082,6 @@ void nmWxAutomaticFitting::onAccept()
// 检查是否有参数被选中
bool hasSelectedParams = m_kCheckBox->isChecked() || m_sCheckBox->isChecked() ||
m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() ||
m_hCheckBox->isChecked() ||
m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() ||
m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked() ||
m_fractureHalfLengthCheckBox->isChecked();
@ -1186,9 +1097,6 @@ void nmWxAutomaticFitting::onAccept()
if(m_sCheckBox->isChecked()) selectedParameterNames << tr("Skin");
if(m_cCheckBox->isChecked()) selectedParameterNames << tr("Wellbore storage");
if(m_phiCheckBox->isChecked()) selectedParameterNames << tr("Porosity");
if(m_hCheckBox->isChecked()) selectedParameterNames << tr("Thickness");
if(m_ctCheckBox->isChecked()) selectedParameterNames << tr("Ct");
if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf");
if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi");
if(m_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity");
if(m_fractureHalfLengthCheckBox->isChecked()) selectedParameterNames << tr("Fracture half length");
@ -1286,19 +1194,19 @@ void nmWxAutomaticFitting::onWellSelected(int index)
// 设置井筒储集系数Wellbore storage
m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue));
if(fracturedWell && m_parameterTable->item(8, 3)) {
m_parameterTable->item(8, 3)->setText(QString::number(fractureConductivityValue));
if(fracturedWell && m_parameterTable->item(5, 3)) {
m_parameterTable->item(5, 3)->setText(QString::number(fractureConductivityValue));
}
if(fracturedWell && m_parameterTable->item(9, 3)) {
m_parameterTable->item(9, 3)->setText(QString::number(fractureHalfLengthValue));
if(fracturedWell && m_parameterTable->item(6, 3)) {
m_parameterTable->item(6, 3)->setText(QString::number(fractureHalfLengthValue));
}
if(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue);
updateRangeForParameter(2, wellboreStorageValue);
if(fracturedWell) {
updateRangeForParameter(8, fractureConductivityValue);
updateRangeForParameter(9, fractureHalfLengthValue);
updateRangeForParameter(5, fractureConductivityValue);
updateRangeForParameter(6, fractureHalfLengthValue);
}
}
}
@ -1306,14 +1214,11 @@ void nmWxAutomaticFitting::onWellSelected(int index)
void nmWxAutomaticFitting::setAutomaticFittingValue()
{
// 保存参数选择状态
// 保存剩余拟合参数选择状态LM 和 PSO 共用此配置。
automaticFittingData.setPermeabilitySelected(m_kCheckBox->isChecked());
automaticFittingData.setSkinSelected(m_sCheckBox->isChecked());
automaticFittingData.setWellboreStorageSelected(m_cCheckBox->isChecked());
automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked());
automaticFittingData.setThicknessSelected(m_hCheckBox->isChecked());
automaticFittingData.setCtSelected(m_ctCheckBox->isChecked());
automaticFittingData.setCfSelected(m_cfCheckBox->isChecked());
automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked());
@ -1335,41 +1240,26 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.getPorosityMin().setValue(m_parameterTable->item(3, 2)->text().toDouble());
automaticFittingData.getPorosityMax().setValue(m_parameterTable->item(3, 4)->text().toDouble());
// 保存储层厚度的最小值和最大值
automaticFittingData.getThicknessMin().setValue(m_parameterTable->item(4, 2)->text().toDouble());
automaticFittingData.getThicknessMax().setValue(m_parameterTable->item(4, 4)->text().toDouble());
// 保存综合压缩系数的最小值和最大值
automaticFittingData.getCtMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getCtMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存岩石压缩系数的最小值和最大值
automaticFittingData.getCfMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getCfMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存初始含水饱和度的最小值和最大值
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble());
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(4, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(4, 4)->text().toDouble());
// 保存裂缝导流能力的最小值和最大值
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble());
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(5, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(5, 4)->text().toDouble());
// 保存裂缝半长的最小值和最大值
automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(9, 2)->text().toDouble());
automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(9, 4)->text().toDouble());
automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(6, 2)->text().toDouble());
automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(6, 4)->text().toDouble());
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
// 保存储层数据的初值
// 只写回仍可编辑的储层初值,厚度和压缩系数保留原始精度和值。
reservoirData.getPermeability().setValue(m_parameterTable->item(0, 3)->text().toDouble()); // 渗透率
reservoirData.getPorosity().setValue(m_parameterTable->item(3, 3)->text().toDouble()); // 孔隙度
reservoirData.getThickness().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 储层厚度
reservoirData.getCt().setValue(m_parameterTable->item(5, 3)->text().toDouble()); // 综合压缩系数
reservoirData.getCf().setValue(m_parameterTable->item(6, 3)->text().toDouble()); // 岩石压缩系数
reservoirData.getSwi().setValue(m_parameterTable->item(7, 3)->text().toDouble()); // 初始含水饱和度
reservoirData.getSwi().setValue(m_parameterTable->item(4, 3)->text().toDouble()); // 初始含水饱和度
// 更新储层数据(全局)
nmDataAnalyzeManager::getCurrentInstance()->updateReservoirData(reservoirData);
@ -1382,8 +1272,8 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
if(!selectedWellName.isEmpty()) {
double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble();
double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble();
double newFractureConductivityValue = m_parameterTable->item(8, 3)->text().toDouble();
double newFractureHalfLengthValue = m_parameterTable->item(9, 3)->text().toDouble();
double newFractureConductivityValue = m_parameterTable->item(5, 3)->text().toDouble();
double newFractureHalfLengthValue = m_parameterTable->item(6, 3)->text().toDouble();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
@ -1689,12 +1579,9 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
if(m_sCheckBox->isChecked()) enabledParams.append(1); // 表皮系数
if(m_cCheckBox->isChecked()) enabledParams.append(2); // 井筒储集系数
if(m_phiCheckBox->isChecked()) enabledParams.append(3); // 孔隙度
if(m_hCheckBox->isChecked()) enabledParams.append(4); // 储层厚度
if(m_ctCheckBox->isChecked()) enabledParams.append(5); // 综合压缩系数
if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数
if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度
if(m_dfcCheckBox->isChecked()) enabledParams.append(8); // 裂缝导流能力
if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(9); // 裂缝半长
if(m_swiCheckBox->isChecked()) enabledParams.append(4); // 初始含水饱和度
if(m_dfcCheckBox->isChecked()) enabledParams.append(5); // 裂缝导流能力
if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(6); // 裂缝半长
// 更新参数值和范围
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {

Loading…
Cancel
Save