diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index 14a0e5f..c9e8ed0 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index e2bafea..1bb1e95 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -3490,6 +3490,10 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
Fracture conductivity
裂缝导流能力
+
+ Fracture half length
+ 裂缝半长
+
The minimum value of %1 must be greater than zero for automatic fitting.
自动拟合时,%1 的最小值和初始值必须大于零。
diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h
index 748831d..9731259 100644
--- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h
+++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h
@@ -372,12 +372,13 @@ private:
// 参数索引约定:
// 0 k 渗透率;1 skin 表皮系数;2 wellboreC 井筒储集;
// 3 phi 孔隙度;4 h 储层厚度;5 Ct 综合压缩系数;
- // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度。
+ // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度;
+ // 8 Dfc 裂缝导流能力;9 fractureHalfLength 裂缝半长。
// m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。
- QVector m_parameterSelected; // 完整 9 个参数是否被用户勾选参与拟合。
- QVector m_parameterLower; // 完整 9 个参数的搜索下界。
- QVector m_parameterUpper; // 完整 9 个参数的搜索上界。
- QVector m_enabledParamIndices; // 被勾选参数在完整 9 维体系中的索引。
+ QVector m_parameterSelected; // 完整 10 个参数是否被用户勾选参与拟合。
+ QVector m_parameterLower; // 完整 10 个参数的搜索下界。
+ QVector m_parameterUpper; // 完整 10 个参数的搜索上界。
+ QVector m_enabledParamIndices; // 被勾选参数在完整 10 维体系中的索引。
QVector > m_targetLogLogData; // 目标井 history log-log 曲线:time/pressure/derivative。
QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。
diff --git a/Include/nmNum/nmData/nmDataAutomaticFitting.h b/Include/nmNum/nmData/nmDataAutomaticFitting.h
index 18bc851..98829eb 100644
--- a/Include/nmNum/nmData/nmDataAutomaticFitting.h
+++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h
@@ -82,6 +82,13 @@ public:
nmDataAttribute& getFractureConductivityMin();
void setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin);
+ // Getter and Setter for fractureHalfLengthMax
+ nmDataAttribute& getFractureHalfLengthMax();
+ void setFractureHalfLengthMax(const nmDataAttribute& fractureHalfLengthMax);
+ // Getter and Setter for fractureHalfLengthMin
+ nmDataAttribute& getFractureHalfLengthMin();
+ void setFractureHalfLengthMin(const nmDataAttribute& fractureHalfLengthMin);
+
// Getter and Setter for iteration count
nmDataAttribute& getIterationCount();
void setIterationCount(const nmDataAttribute& iterationCount);
@@ -124,6 +131,9 @@ public:
bool getFractureConductivitySelected() const;
void setFractureConductivitySelected(bool selected);
+ bool getFractureHalfLengthSelected() const;
+ void setFractureHalfLengthSelected(bool selected);
+
private:
// 参数最大值
nmDataAttribute m_permeabilityMax;
@@ -135,6 +145,7 @@ private:
nmDataAttribute m_cfMax;
nmDataAttribute m_swiMax;
nmDataAttribute m_fractureConductivityMax;
+ nmDataAttribute m_fractureHalfLengthMax;
// 参数最小值
nmDataAttribute m_permeabilityMin;
@@ -146,6 +157,7 @@ private:
nmDataAttribute m_cfMin;
nmDataAttribute m_swiMin;
nmDataAttribute m_fractureConductivityMin;
+ nmDataAttribute m_fractureHalfLengthMin;
// 迭代参数
nmDataAttribute m_iterationCount; // 迭代步数
@@ -163,6 +175,7 @@ private:
bool m_cfSelected; // 是否选择岩石压缩系数进行拟合
bool m_swiSelected; // 是否选择初始含水饱和度进行拟合
bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合
+ bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合
};
#endif // NMDATAAUTOMATICFITTING_H
diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
index ada66d0..a8c4f76 100644
--- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
+++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
@@ -92,6 +92,7 @@ private:
QCheckBox* m_cfCheckBox; // 岩石压缩系数
QCheckBox* m_swiCheckBox; // 初始含水饱和度
QCheckBox* m_dfcCheckBox; // 裂缝导流能力
+ QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长
// 按钮
QPushButton* m_reverseBtn;
diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp
index 7378599..0f37602 100644
--- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp
@@ -432,7 +432,7 @@ static QString findExecutableInPath(const QString& executableName)
static QStringList traceParameterNames()
{
// trace 和 trace meta 使用的完整参数名顺序。
- // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-8 索引一致。
+ // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-9 索引一致。
QStringList names;
names << "k"
<< "skin"
@@ -442,7 +442,8 @@ static QStringList traceParameterNames()
<< "Ct"
<< "Cf"
<< "Swi"
- << "Dfc";
+ << "Dfc"
+ << "fractureHalfLength";
return names;
}
@@ -1013,7 +1014,7 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
{
// trace CSV 字段说明:
// - 代理模式保持原 k/skin/wellboreC/phi/h/Ct/Cf 契约;
- // - 非代理模式额外记录 Swi/Dfc,便于复盘信赖域对两项参数的调整;
+ // - 非代理模式额外记录 Swi/Dfc/裂缝半长,便于复盘信赖域调整;
// - solver_objective 是真实求解器误差;
// - surrogate_objective 是 Python 代理评分;
// - screening_decision 说明该粒子为什么跑/不跑真实求解器;
@@ -1037,7 +1038,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
<< "Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "Swi"
- << "Dfc";
+ << "Dfc"
+ << "fractureHalfLength";
}
cols << "solver_objective"
<< "solver_success"
@@ -1054,7 +1056,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
<< "pbest_Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "pbest_Swi"
- << "pbest_Dfc";
+ << "pbest_Dfc"
+ << "pbest_fractureHalfLength";
}
cols << "gbest_objective"
<< "gbest_k"
@@ -1066,7 +1069,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
<< "gbest_Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "gbest_Swi"
- << "gbest_Dfc";
+ << "gbest_Dfc"
+ << "gbest_fractureHalfLength";
}
cols << "enabled_param_indices"
<< "pressure_loss"
@@ -1132,7 +1136,7 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile()
// 非代理 v5 增加带符号诊断列;代理 PSO 保留原 v3 字段和目标,避免改变
// 已有模型的训练和回放契约。
out << " \"schema_version\": "
- << (isSurrogateScreeningEnabled() ? 3 : 6) << ",\n";
+ << (isSurrogateScreeningEnabled() ? 3 : 7) << ",\n";
out << " \"trace_type\": "
<< jsonEscape(isSurrogateScreeningEnabled()
? "pso_baseline_replay_meta"
@@ -1193,10 +1197,10 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile()
QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector& selectedParameters) const
{
- // 将粒子内部使用的“启用参数向量”还原成完整 9 维参数向量。
+ // 将粒子内部使用的“启用参数向量”还原成完整 10 维参数向量。
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。
// trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。
- QVector fullParams(9, 0.0);
+ QVector fullParams(10, 0.0);
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@@ -1224,12 +1228,14 @@ QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector
dynamic_cast(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
+ fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
} else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().getValue().toDouble();
+ fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble();
}
}
}
@@ -1290,7 +1296,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation,
<< traceParamAt(currentParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(currentParams, 7)
- << traceParamAt(currentParams, 8);
+ << traceParamAt(currentParams, 8)
+ << traceParamAt(currentParams, 9);
}
cols << traceNumber(solverObjective)
<< QString::number(solverSuccess ? 1 : 0)
@@ -1307,7 +1314,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation,
<< traceParamAt(pbestParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(pbestParams, 7)
- << traceParamAt(pbestParams, 8);
+ << traceParamAt(pbestParams, 8)
+ << traceParamAt(pbestParams, 9);
}
cols << traceNumber(m_globalBestFitness)
<< traceParamAt(gbestParams, 0)
@@ -1319,7 +1327,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation,
<< traceParamAt(gbestParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(gbestParams, 7)
- << traceParamAt(gbestParams, 8);
+ << traceParamAt(gbestParams, 8)
+ << traceParamAt(gbestParams, 9);
}
cols << csvEscape(enabledIndices.join(";"));
@@ -2917,14 +2926,14 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
- // - m_parameterSelected[9]:完整参数体系中每个参数是否参与拟合;
- // - m_parameterLower/Upper[9]:完整参数体系的搜索上下界;
+ // - m_parameterSelected[10]:完整参数体系中每个参数是否参与拟合;
+ // - m_parameterLower/Upper[10]:完整参数体系的搜索上下界;
// - m_enabledParamIndices:把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
- m_parameterSelected.resize(9);
+ m_parameterSelected.resize(10);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
@@ -2934,10 +2943,11 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[8] = fittingData.getFractureConductivitySelected();
+ m_parameterSelected[9] = fittingData.getFractureHalfLengthSelected();
// 获取参数边界
- m_parameterLower.resize(9);
- m_parameterUpper.resize(9);
+ m_parameterLower.resize(10);
+ m_parameterUpper.resize(10);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@@ -2966,6 +2976,9 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
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_enabledParamIndices.clear();
@@ -3474,11 +3487,14 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
applyParametersToDataManager(m_globalBestPosition);
// 即使用户此时停止、不再执行最终完整计算,也要把 PEBI 缓存恢复为
- // 最终已接受的 Dfc,避免缓存仍停留在最后一个被拒绝的候选值。
- if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) {
+ // 最终已接受的裂缝参数,避免缓存仍停留在最后一个被拒绝的候选值。
+ const bool fractureGridParameterSelected =
+ (m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
+ (m_parameterSelected.size() > 9 && m_parameterSelected[9]);
+ if(fractureGridParameterSelected) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
- throw std::runtime_error("Failed to refresh final fracture conductivity");
+ throw std::runtime_error("Failed to refresh final fracture parameters");
}
}
@@ -3711,6 +3727,22 @@ void nmCalculationAutoFitPSO::extractUserInitialValues()
}
}
break;
+
+ case 9: // 裂缝半长
+ if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
+ nmDataVerticalFracturedWell* fracturedWell =
+ dynamic_cast(pTargetWell);
+ if(fracturedWell) {
+ initialValue = fracturedWell->getFractureHalfLength().getValue().toDouble();
+ }
+ } else if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
+ nmDataHorizontalFracturedWell* fracturedWell =
+ dynamic_cast(pTargetWell);
+ if(fracturedWell) {
+ initialValue = fracturedWell->getFractureHalfLength().getValue().toDouble();
+ }
+ }
+ break;
}
m_initialValues.append(initialValue);
@@ -5534,12 +5566,15 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter
return 1e10;
}
- // Dfc 位于 PEBI 裂缝数组 crack[5],不是每次求解都会重新组装的 Base/CS 参数。
- // 因此只有勾选 Dfc 时才刷新一次网格输出参数,保证本次真实试算使用刚写入井对象的值。
- if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) {
+ // Dfc 和裂缝半长都通过 PEBI 裂缝数组传入,不属于每次求解都会重新组装的 Base/CS 参数。
+ // 勾选任一裂缝参数时刷新网格输出,保证本次真实试算使用新的导流能力和端点坐标。
+ const bool fractureGridParameterSelected =
+ (m_parameterSelected.size() > 8 && m_parameterSelected[8]) ||
+ (m_parameterSelected.size() > 9 && m_parameterSelected[9]);
+ if(fractureGridParameterSelected) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
- DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture conductivity")
+ DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture parameters")
.arg(funcName).arg(callCount));
return 1e10;
}
@@ -5707,7 +5742,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector& p
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
- // paramIndex 是粒子 position 中的索引;i 是完整 9 个参数体系中的索引。
+ // paramIndex 是粒子 position 中的索引;i 是完整 10 个参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@@ -5754,7 +5789,7 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame
// 更新目标井上的拟合参数。目前井级可拟合参数主要是:
// - skin:写入第一个 perforation;
// - wellboreC:写入井筒储集系数;
- // - Dfc:只写入垂直压裂井或多段压裂水平井的裂缝导流能力。
+ // - Dfc/裂缝半长:只写入垂直压裂井或多段压裂水平井。
// 如果目标井不存在或没有射孔数据,这里只记录 debug,不抛异常。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@@ -5811,6 +5846,24 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame
}
}
break;
+
+ case 9: { // 裂缝半长
+ // 直接修改井对象中的属性,复用已有信号重算裂缝端点。
+ if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
+ nmDataVerticalFracturedWell* fracturedWell =
+ dynamic_cast(pWell);
+ if(fracturedWell) {
+ fracturedWell->getFractureHalfLength().setValue(value);
+ }
+ } else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
+ nmDataHorizontalFracturedWell* fracturedWell =
+ dynamic_cast(pWell);
+ if(fracturedWell) {
+ fracturedWell->getFractureHalfLength().setValue(value);
+ }
+ }
+ }
+ break;
}
paramIndex++;
diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp
index a7a3462..af01ce0 100644
--- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp
+++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp
@@ -12,6 +12,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_cfSelected = false; // 默认不选中
m_swiSelected = false; // 默认不选中
m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中
+ m_fractureHalfLengthSelected = false; // 仅压裂井可用,默认不选中
// 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。
m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy");
@@ -23,6 +24,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
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");
m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy");
m_skinMin = nmDataAttribute("Skin Min", QVariant(), "");
@@ -33,6 +35,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
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");
// 初始化迭代参数
m_iterationCount = nmDataAttribute("Iteration Count", 20, "");
@@ -63,6 +66,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_cfSelected = other.m_cfSelected;
m_swiSelected = other.m_swiSelected;
m_fractureConductivitySelected = other.m_fractureConductivitySelected;
+ m_fractureHalfLengthSelected = other.m_fractureHalfLengthSelected;
// 复制参数最大值
m_permeabilityMax = other.m_permeabilityMax;
@@ -74,6 +78,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_cfMax = other.m_cfMax;
m_swiMax = other.m_swiMax;
m_fractureConductivityMax = other.m_fractureConductivityMax;
+ m_fractureHalfLengthMax = other.m_fractureHalfLengthMax;
// 复制参数最小值
m_permeabilityMin = other.m_permeabilityMin;
@@ -85,6 +90,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_cfMin = other.m_cfMin;
m_swiMin = other.m_swiMin;
m_fractureConductivityMin = other.m_fractureConductivityMin;
+ m_fractureHalfLengthMin = other.m_fractureHalfLengthMin;
// 复制迭代参数
m_iterationCount = other.m_iterationCount;
@@ -109,6 +115,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
fittingObject.AddMember("CfSelected", m_cfSelected, allocator);
fittingObject.AddMember("SwiSelected", m_swiSelected, allocator);
fittingObject.AddMember("FractureConductivitySelected", m_fractureConductivitySelected, allocator);
+ fittingObject.AddMember("FractureHalfLengthSelected", m_fractureHalfLengthSelected, allocator);
// 序列化参数最大值
fittingObject.AddMember("PermeabilityMax", m_permeabilityMax.ToJsonValue(allocator), allocator);
@@ -120,6 +127,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
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);
// 序列化参数最小值
fittingObject.AddMember("PermeabilityMin", m_permeabilityMin.ToJsonValue(allocator), allocator);
@@ -131,6 +139,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
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);
// 序列化迭代参数
fittingObject.AddMember("IterationCount", m_iterationCount.ToJsonValue(allocator), allocator);
@@ -171,6 +180,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("FractureConductivitySelected") && jsonValue["FractureConductivitySelected"].IsBool()) {
m_fractureConductivitySelected = jsonValue["FractureConductivitySelected"].GetBool();
}
+ if (jsonValue.HasMember("FractureHalfLengthSelected") && jsonValue["FractureHalfLengthSelected"].IsBool()) {
+ m_fractureHalfLengthSelected = jsonValue["FractureHalfLengthSelected"].GetBool();
+ }
// 反序列化参数最大值
if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) {
@@ -200,6 +212,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("FractureConductivityMax") && jsonValue["FractureConductivityMax"].IsObject()) {
m_fractureConductivityMax.FromJsonValue(jsonValue["FractureConductivityMax"]);
}
+ if (jsonValue.HasMember("FractureHalfLengthMax") && jsonValue["FractureHalfLengthMax"].IsObject()) {
+ m_fractureHalfLengthMax.FromJsonValue(jsonValue["FractureHalfLengthMax"]);
+ }
// 反序列化参数最小值
if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) {
@@ -229,6 +244,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("FractureConductivityMin") && jsonValue["FractureConductivityMin"].IsObject()) {
m_fractureConductivityMin.FromJsonValue(jsonValue["FractureConductivityMin"]);
}
+ if (jsonValue.HasMember("FractureHalfLengthMin") && jsonValue["FractureHalfLengthMin"].IsObject()) {
+ m_fractureHalfLengthMin.FromJsonValue(jsonValue["FractureHalfLengthMin"]);
+ }
// 反序列化迭代参数
if (jsonValue.HasMember("IterationCount") && jsonValue["IterationCount"].IsObject()) {
@@ -274,6 +292,9 @@ void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = sel
bool nmDataAutomaticFitting::getFractureConductivitySelected() const { return m_fractureConductivitySelected; }
void nmDataAutomaticFitting::setFractureConductivitySelected(bool selected) { m_fractureConductivitySelected = selected; }
+bool nmDataAutomaticFitting::getFractureHalfLengthSelected() const { return m_fractureHalfLengthSelected; }
+void nmDataAutomaticFitting::setFractureHalfLengthSelected(bool selected) { m_fractureHalfLengthSelected = selected; }
+
// Getter and Setter implementations for Max values
nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMax() { return m_permeabilityMax; }
@@ -303,6 +324,9 @@ void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax
nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMax() { return m_fractureConductivityMax; }
void nmDataAutomaticFitting::setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax) { m_fractureConductivityMax = fractureConductivityMax; }
+nmDataAttribute& nmDataAutomaticFitting::getFractureHalfLengthMax() { return m_fractureHalfLengthMax; }
+void nmDataAutomaticFitting::setFractureHalfLengthMax(const nmDataAttribute& fractureHalfLengthMax) { m_fractureHalfLengthMax = fractureHalfLengthMax; }
+
// Getter and Setter implementations for Min values
nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMin() { return m_permeabilityMin; }
void nmDataAutomaticFitting::setPermeabilityMin(const nmDataAttribute& permeabilityMin) { m_permeabilityMin = permeabilityMin; }
@@ -331,6 +355,9 @@ void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin
nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMin() { return m_fractureConductivityMin; }
void nmDataAutomaticFitting::setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin) { m_fractureConductivityMin = fractureConductivityMin; }
+nmDataAttribute& nmDataAutomaticFitting::getFractureHalfLengthMin() { return m_fractureHalfLengthMin; }
+void nmDataAutomaticFitting::setFractureHalfLengthMin(const nmDataAttribute& fractureHalfLengthMin) { m_fractureHalfLengthMin = fractureHalfLengthMin; }
+
// Getter and Setter implementations for iteration parameters
nmDataAttribute& nmDataAutomaticFitting::getIterationCount() { return m_iterationCount; }
void nmDataAutomaticFitting::setIterationCount(const nmDataAttribute& iterationCount) { m_iterationCount = iterationCount; }
diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
index 34415d0..6bd7312 100644
--- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
@@ -43,13 +43,10 @@ bool nmAutoFitUiNearlyEqual(double left, double right)
return std::fabs(left - right) <= DBL_EPSILON * 8.0 * scale;
}
-// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。
+// 从系统参数表读取物理边界,所有自动拟合参数统一以参数表配置为准。
bool nmAutoFitReadPhysicalRange(const char* parameterName,
- double fallbackMin, double fallbackMax, double& minValue, double& maxValue)
+ double& minValue, double& maxValue)
{
- minValue = fallbackMin;
- maxValue = fallbackMax;
-
iSysParaHelper* paraHelper = _paraHelper;
if(!paraHelper) {
return false;
@@ -245,6 +242,7 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
}
}
setParameterRowVisible(table, 8, showFractureConductivity); // Dfc
+ setParameterRowVisible(table, 9, showFractureConductivity); // 裂缝半长
renumberVisibleParameterRows(table);
}
@@ -255,29 +253,15 @@ 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"
- };
- // KAPPA 的边界使用 md、ft、bbl/psi;自动拟合界面使用 Darcy、m、m^3/MPa,
- // 这里统一换算到界面和 PSO 实际使用的单位:K 除以 1000,h 由 ft 换成 m,
- // 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。
- // Ct/Cf/Swi/Dfc 沿用模型参数表边界。
- static const double physicalMin[] = {
- 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0, 0.0
- };
- static const double physicalMax[] = {
- 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0, 1.0e30
+ "Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc",
+ "W_FractureHalfLength"
};
-
- if(parameterIndex < 0 || parameterIndex >= 9) {
+ if(parameterIndex < 0 || parameterIndex >= 10) {
return false;
}
- minValue = physicalMin[parameterIndex];
- maxValue = physicalMax[parameterIndex];
- bool rangeRead = true;
- if(parameterIndex >= 5) {
- rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex],
- physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue);
+ if(!nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], minValue, maxValue)) {
+ return false;
}
if(parameterIndex == 7) {
@@ -294,7 +278,7 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex,
}
}
- return rangeRead;
+ return true;
}
// 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。
@@ -357,6 +341,10 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
automaticFittingData.getFractureConductivityMin().setValue(minValue);
automaticFittingData.getFractureConductivityMax().setValue(maxValue);
break;
+ case 9:
+ automaticFittingData.getFractureHalfLengthMin().setValue(minValue);
+ automaticFittingData.getFractureHalfLengthMax().setValue(maxValue);
+ break;
default:
break;
}
@@ -369,14 +357,16 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
- if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 9
+ if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 10
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
- getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
+ if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) {
+ return;
+ }
double reference = centerValue;
const bool positiveParameter = parameterIndex != 1;
@@ -437,7 +427,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
return;
}
- for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) {
+ for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(initialItem) {
bool initialOk = false;
@@ -448,8 +438,8 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
// 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。
double physicalMin = 0.0;
double physicalMax = 0.0;
- getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
- if(physicalMax >= physicalMin) {
+ if(getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)
+ && physicalMax >= physicalMin) {
setParameterRange(parameterIndex, physicalMin, physicalMax);
}
}
@@ -464,7 +454,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
return;
}
- for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) {
+ for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
@@ -474,7 +464,9 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
double physicalMin = 0.0;
double physicalMax = 0.0;
- getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax);
+ if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) {
+ continue;
+ }
bool savedMinOk = false;
bool savedMaxOk = false;
const double savedMin = minItem->text().toDouble(&savedMinOk);
@@ -513,18 +505,19 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
errorMessage = tr("The parameter table is unavailable.");
return false;
}
- if(parameterIndex < -1 || parameterIndex >= 9) {
+ if(parameterIndex < -1 || parameterIndex >= 10) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
- "Thickness", "Ct", "Cf", "Swi", "Fracture conductivity"
+ "Thickness", "Ct", "Cf", "Swi", "Fracture conductivity",
+ "Fracture half length"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
- const int lastParameterIndex = parameterIndex < 0 ? 9 : parameterIndex + 1;
+ const int lastParameterIndex = parameterIndex < 0 ? 10 : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
@@ -558,8 +551,8 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
double physicalMin = 0.0;
double physicalMax = 0.0;
- getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax);
- if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax)
+ if(!getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax)
+ || !nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax)
|| physicalMax < physicalMin) {
errorMessage = tr("The physical range of %1 is invalid.")
.arg(tr(parameterNames[currentParameterIndex]));
@@ -717,7 +710,7 @@ void nmWxAutomaticFitting::setupUI()
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
- m_parameterTable = new QTableWidget(9, 6, this);
+ m_parameterTable = new QTableWidget(10, 6, this);
// 设置表头
QStringList headers;
@@ -837,6 +830,16 @@ void nmWxAutomaticFitting::setupParameterTable()
m_parameterTable->setItem(8, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble())));
m_parameterTable->setItem(8, 5, new QTableWidgetItem(tr("md.m")));
+ // 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。
+ m_parameterTable->setItem(9, 0, new QTableWidgetItem("10"));
+ 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")));
+
// 设置表格行为
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
for(int j = 0; j < 6; ++j) {
@@ -1037,6 +1040,7 @@ void nmWxAutomaticFitting::onReverseSelection()
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());
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
@@ -1155,7 +1159,8 @@ void nmWxAutomaticFitting::onAccept()
m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() ||
m_hCheckBox->isChecked() ||
m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() ||
- m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked();
+ m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked() ||
+ m_fractureHalfLengthCheckBox->isChecked();
if(!hasSelectedParams) {
QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!"));
@@ -1174,6 +1179,7 @@ void nmWxAutomaticFitting::onAccept()
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");
// 启动自动拟合 - 传递双对数历史数据
startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName);
@@ -1194,6 +1200,7 @@ void nmWxAutomaticFitting::onWellSelected(int index)
double skinValue = 0.0;
double wellboreStorageValue = 0.0;
double fractureConductivityValue = 0.0;
+ double fractureHalfLengthValue = 0.0;
// 查找垂直井
for(int i = 0; i < m_verticalWells.size(); ++i) {
@@ -1224,6 +1231,7 @@ void nmWxAutomaticFitting::onWellSelected(int index)
skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble();
fractureConductivityValue = m_verticalFracturedWells[i].getDfc().getValue().toDouble();
+ fractureHalfLengthValue = m_verticalFracturedWells[i].getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
found = true;
break;
@@ -1238,6 +1246,7 @@ void nmWxAutomaticFitting::onWellSelected(int index)
skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble();
fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble();
+ fractureHalfLengthValue = m_horizontalFracturedWells[i].getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
found = true;
break;
@@ -1268,12 +1277,16 @@ void nmWxAutomaticFitting::onWellSelected(int index)
if(fracturedWell && m_parameterTable->item(8, 3)) {
m_parameterTable->item(8, 3)->setText(QString::number(fractureConductivityValue));
}
+ if(fracturedWell && m_parameterTable->item(9, 3)) {
+ m_parameterTable->item(9, 3)->setText(QString::number(fractureHalfLengthValue));
+ }
if(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue);
updateRangeForParameter(2, wellboreStorageValue);
if(fracturedWell) {
updateRangeForParameter(8, fractureConductivityValue);
+ updateRangeForParameter(9, fractureHalfLengthValue);
}
}
}
@@ -1291,6 +1304,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.setCfSelected(m_cfCheckBox->isChecked());
automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
+ automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked());
automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1);
// 保存渗透率的最小值和最大值
@@ -1329,6 +1343,10 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble());
+ // 保存裂缝半长的最小值和最大值
+ automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(9, 2)->text().toDouble());
+ automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(9, 4)->text().toDouble());
+
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
@@ -1353,6 +1371,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
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();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
@@ -1382,6 +1401,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
+ fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue);
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast(pTargetWell);
@@ -1389,6 +1409,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
+ fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue);
}
}
@@ -1622,6 +1643,7 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
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); // 裂缝半长
// 更新参数值和范围
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {