增加压裂井导流能力 Dfc 自动拟合参数

feature/MultiWellAutoFit-20260805
lvjunjie 4 weeks ago
parent efda31f388
commit e8563506b9

Binary file not shown.

@ -3486,6 +3486,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<source>On</source>
<translation></translation>
</message>
<message>
<source>Fracture conductivity</source>
<translation></translation>
</message>
<message>
<source>The minimum value of %1 must be greater than zero for automatic fitting.</source>
<translation>%1 </translation>
</message>
</context>
<context>
<name>nmWxAutomaticfitting</name>

@ -374,10 +374,10 @@ private:
// 3 phi 孔隙度4 h 储层厚度5 Ct 综合压缩系数;
// 6 Cf 岩石压缩系数7 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; // 完整 9 个参数是否被用户勾选参与拟合。
QVector<double> m_parameterLower; // 完整 9 个参数的搜索下界。
QVector<double> m_parameterUpper; // 完整 9 个参数的搜索上界。
QVector<int> m_enabledParamIndices; // 被勾选参数在完整 9 维体系中的索引。
QVector<QVector<double> > m_targetLogLogData; // 目标井 history log-log 曲线time/pressure/derivative。
QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。

@ -75,6 +75,13 @@ public:
nmDataAttribute& getSwiMin();
void setSwiMin(const nmDataAttribute& swiMin);
// Getter and Setter for fractureConductivityMax
nmDataAttribute& getFractureConductivityMax();
void setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax);
// Getter and Setter for fractureConductivityMin
nmDataAttribute& getFractureConductivityMin();
void setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin);
// Getter and Setter for iteration count
nmDataAttribute& getIterationCount();
void setIterationCount(const nmDataAttribute& iterationCount);
@ -114,6 +121,9 @@ public:
bool getSwiSelected() const;
void setSwiSelected(bool selected);
bool getFractureConductivitySelected() const;
void setFractureConductivitySelected(bool selected);
private:
// 参数最大值
nmDataAttribute m_permeabilityMax;
@ -124,6 +134,7 @@ private:
nmDataAttribute m_ctMax;
nmDataAttribute m_cfMax;
nmDataAttribute m_swiMax;
nmDataAttribute m_fractureConductivityMax;
// 参数最小值
nmDataAttribute m_permeabilityMin;
@ -134,6 +145,7 @@ private:
nmDataAttribute m_ctMin;
nmDataAttribute m_cfMin;
nmDataAttribute m_swiMin;
nmDataAttribute m_fractureConductivityMin;
// 迭代参数
nmDataAttribute m_iterationCount; // 迭代步数
@ -150,6 +162,7 @@ private:
bool m_ctSelected; // 是否选择综合压缩系数进行拟合
bool m_cfSelected; // 是否选择岩石压缩系数进行拟合
bool m_swiSelected; // 是否选择初始含水饱和度进行拟合
bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合
};
#endif // NMDATAAUTOMATICFITTING_H

@ -91,6 +91,7 @@ private:
QCheckBox* m_ctCheckBox; // 综合压缩系数
QCheckBox* m_cfCheckBox; // 岩石压缩系数
QCheckBox* m_swiCheckBox; // 初始含水饱和度
QCheckBox* m_dfcCheckBox; // 裂缝导流能力
// 按钮
QPushButton* m_reverseBtn;

@ -432,7 +432,7 @@ static QString findExecutableInPath(const QString& executableName)
static QStringList traceParameterNames()
{
// trace 和 trace meta 使用的完整参数名顺序。
// 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-7 索引一致。
// 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-8 索引一致。
QStringList names;
names << "k"
<< "skin"
@ -441,7 +441,8 @@ static QStringList traceParameterNames()
<< "h"
<< "Ct"
<< "Cf"
<< "Swi";
<< "Swi"
<< "Dfc";
return names;
}
@ -1011,7 +1012,8 @@ void nmCalculationAutoFitPSO::closeTraceFile()
void nmCalculationAutoFitPSO::writeTraceHeader()
{
// trace CSV 字段说明:
// - 当前粒子参数只记录代理模型关心的 k/skin/wellboreC/phi/h/Ct/Cf
// - 代理模式保持原 k/skin/wellboreC/phi/h/Ct/Cf 契约;
// - 非代理模式额外记录 Swi/Dfc便于复盘信赖域对两项参数的调整
// - solver_objective 是真实求解器误差;
// - surrogate_objective 是 Python 代理评分;
// - screening_decision 说明该粒子为什么跑/不跑真实求解器;
@ -1032,8 +1034,12 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
<< "phi"
<< "h"
<< "Ct"
<< "Cf"
<< "solver_objective"
<< "Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "Swi"
<< "Dfc";
}
cols << "solver_objective"
<< "solver_success"
<< "elapsed_ms"
<< "surrogate_objective"
@ -1045,16 +1051,24 @@ void nmCalculationAutoFitPSO::writeTraceHeader()
<< "pbest_phi"
<< "pbest_h"
<< "pbest_Ct"
<< "pbest_Cf"
<< "gbest_objective"
<< "pbest_Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "pbest_Swi"
<< "pbest_Dfc";
}
cols << "gbest_objective"
<< "gbest_k"
<< "gbest_skin"
<< "gbest_wellboreC"
<< "gbest_phi"
<< "gbest_h"
<< "gbest_Ct"
<< "gbest_Cf"
<< "enabled_param_indices"
<< "gbest_Cf";
if(!isSurrogateScreeningEnabled()) {
cols << "gbest_Swi"
<< "gbest_Dfc";
}
cols << "enabled_param_indices"
<< "pressure_loss"
<< "derivative_loss";
if(!isSurrogateScreeningEnabled()) {
@ -1118,7 +1132,7 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile()
// 非代理 v5 增加带符号诊断列;代理 PSO 保留原 v3 字段和目标,避免改变
// 已有模型的训练和回放契约。
out << " \"schema_version\": "
<< (isSurrogateScreeningEnabled() ? 3 : 5) << ",\n";
<< (isSurrogateScreeningEnabled() ? 3 : 6) << ",\n";
out << " \"trace_type\": "
<< jsonEscape(isSurrogateScreeningEnabled()
? "pso_baseline_replay_meta"
@ -1179,10 +1193,10 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile()
QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{
// 将粒子内部使用的“启用参数向量”还原成完整 8 维参数向量。
// 将粒子内部使用的“启用参数向量”还原成完整 9 维参数向量。
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。
// trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。
QVector<double> fullParams(8, 0.0);
QVector<double> fullParams(9, 0.0);
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@ -1198,8 +1212,26 @@ QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
if(pTargetWell) {
fullParams[1] = pTargetWell->getPerforation(0)->getSkin().getValue().toDouble();
nmDataPerforation* perforation = pTargetWell->getPerforation(0);
if(perforation) {
fullParams[1] = perforation->getSkin().getValue().toDouble();
}
fullParams[2] = pTargetWell->getWellboreStorage().getValue().toDouble();
// Dfc 只存在于两类压裂井,普通井在完整向量中保持为 0。
if(pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
fullParams[8] = fracturedWell->getDfc().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();
}
}
}
}
@ -1255,8 +1287,12 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation,
<< traceParamAt(currentParams, 3)
<< traceParamAt(currentParams, 4)
<< traceParamAt(currentParams, 5)
<< traceParamAt(currentParams, 6)
<< traceNumber(solverObjective)
<< traceParamAt(currentParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(currentParams, 7)
<< traceParamAt(currentParams, 8);
}
cols << traceNumber(solverObjective)
<< QString::number(solverSuccess ? 1 : 0)
<< QString::number(elapsedMs)
<< traceNumber(surrogateObjective)
@ -1268,16 +1304,24 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation,
<< traceParamAt(pbestParams, 3)
<< traceParamAt(pbestParams, 4)
<< traceParamAt(pbestParams, 5)
<< traceParamAt(pbestParams, 6)
<< traceNumber(m_globalBestFitness)
<< traceParamAt(pbestParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(pbestParams, 7)
<< traceParamAt(pbestParams, 8);
}
cols << traceNumber(m_globalBestFitness)
<< traceParamAt(gbestParams, 0)
<< traceParamAt(gbestParams, 1)
<< traceParamAt(gbestParams, 2)
<< traceParamAt(gbestParams, 3)
<< traceParamAt(gbestParams, 4)
<< traceParamAt(gbestParams, 5)
<< traceParamAt(gbestParams, 6)
<< csvEscape(enabledIndices.join(";"));
<< traceParamAt(gbestParams, 6);
if(!isSurrogateScreeningEnabled()) {
cols << traceParamAt(gbestParams, 7)
<< traceParamAt(gbestParams, 8);
}
cols << csvEscape(enabledIndices.join(";"));
if(objectiveBreakdown && objectiveBreakdown->valid) {
cols << traceNumber(objectiveBreakdown->pressureLoss)
@ -2873,14 +2917,14 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
// 读取用户勾选的拟合参数及上下界。
//
// 这里构建三个核心数组:
// - m_parameterSelected[8]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[8]:完整参数体系的搜索上下界;
// - m_parameterSelected[9]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[9]:完整参数体系的搜索上下界;
// - m_enabledParamIndices把粒子内部紧凑向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
// 获取参数选择状态
m_parameterSelected.resize(8);
m_parameterSelected.resize(9);
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected();
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
@ -2889,10 +2933,11 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
m_parameterSelected[5] = fittingData.getCtSelected();
m_parameterSelected[6] = fittingData.getCfSelected();
m_parameterSelected[7] = fittingData.getSwiSelected();
m_parameterSelected[8] = fittingData.getFractureConductivitySelected();
// 获取参数边界
m_parameterLower.resize(8);
m_parameterUpper.resize(8);
m_parameterLower.resize(9);
m_parameterUpper.resize(9);
m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble();
m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble();
@ -2918,6 +2963,9 @@ void nmCalculationAutoFitPSO::loadParameterBounds()
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_enabledParamIndices.clear();
@ -3425,6 +3473,15 @@ bool nmCalculationAutoFitPSO::startAutoFitting()
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
applyParametersToDataManager(m_globalBestPosition);
// 即使用户此时停止、不再执行最终完整计算,也要把 PEBI 缓存恢复为
// 最终已接受的 Dfc避免缓存仍停留在最后一个被拒绝的候选值。
if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
throw std::runtime_error("Failed to refresh final fracture conductivity");
}
}
if(m_shouldStop) {
// 手动停止优先保持快速返回,仅写回已确认的最优参数。
emit logMessageGenerated(tr("Final full-field calculation skipped after user stop"));
@ -3638,6 +3695,22 @@ void nmCalculationAutoFitPSO::extractUserInitialValues()
case 7: // 初始含水饱和度
initialValue = reservoirData.getSwi().getValue().toDouble();
break;
case 8: // 裂缝导流能力
if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
initialValue = fracturedWell->getDfc().getValue().toDouble();
}
} else if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
initialValue = fracturedWell->getDfc().getValue().toDouble();
}
}
break;
}
m_initialValues.append(initialValue);
@ -5461,6 +5534,17 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector<double>& parameter
return 1e10;
}
// Dfc 位于 PEBI 裂缝数组 crack[5],不是每次求解都会重新组装的 Base/CS 参数。
// 因此只有勾选 Dfc 时才刷新一次网格输出参数,保证本次真实试算使用刚写入井对象的值。
if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) {
nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance();
if(!pebiGrid || !pebiGrid->generateOutputPara()) {
DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture conductivity")
.arg(funcName).arg(callCount));
return 1e10;
}
}
// 4. 运行求解器。真实求解器偶发失败时允许重试,避免一次 DLL 调用异常
// 直接让整个粒子评价失败。
QVector<QVector<double>> solverResult;
@ -5623,7 +5707,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& p
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
// paramIndex 是粒子 position 中的索引i 是完整 8 个参数体系中的索引。
// paramIndex 是粒子 position 中的索引i 是完整 9 个参数体系中的索引。
// 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。
int paramIndex = 0;
@ -5669,7 +5753,8 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parame
{
// 更新目标井上的拟合参数。目前井级可拟合参数主要是:
// - skin写入第一个 perforation
// - wellboreC写入井筒储集系数。
// - wellboreC写入井筒储集系数
// - Dfc只写入垂直压裂井或多段压裂水平井的裂缝导流能力。
// 如果目标井不存在或没有射孔数据,这里只记录 debug不抛异常。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@ -5705,6 +5790,27 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parame
pWell->setWellboreStorage(wellboreAttr);
}
break;
case 8: { // 裂缝导流能力
if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell =
dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(value);
fracturedWell->setDfc(dfc);
}
} else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell =
dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(value);
fracturedWell->setDfc(dfc);
}
}
}
break;
}
paramIndex++;
@ -6139,6 +6245,14 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector<double>& paramete
return false;
}
break;
case 8: // 裂缝导流能力0 表示无限导流,不能作为连续拟合搜索点
if(value <= 1e-10) {
DEBUG_OUT(QString("Rejecting non-positive fracture conductivity: %1").arg(value));
return false;
}
break;
}
}

@ -11,6 +11,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
m_ctSelected = true; // 默认选中
m_cfSelected = false; // 默认不选中
m_swiSelected = false; // 默认不选中
m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中
// 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。
m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy");
@ -21,6 +22,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
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_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy");
m_skinMin = nmDataAttribute("Skin Min", QVariant(), "");
@ -30,6 +32,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting()
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_iterationCount = nmDataAttribute("Iteration Count", 20, "");
@ -59,6 +62,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_ctSelected = other.m_ctSelected;
m_cfSelected = other.m_cfSelected;
m_swiSelected = other.m_swiSelected;
m_fractureConductivitySelected = other.m_fractureConductivitySelected;
// 复制参数最大值
m_permeabilityMax = other.m_permeabilityMax;
@ -69,6 +73,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_ctMax = other.m_ctMax;
m_cfMax = other.m_cfMax;
m_swiMax = other.m_swiMax;
m_fractureConductivityMax = other.m_fractureConductivityMax;
// 复制参数最小值
m_permeabilityMin = other.m_permeabilityMin;
@ -79,6 +84,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF
m_ctMin = other.m_ctMin;
m_cfMin = other.m_cfMin;
m_swiMin = other.m_swiMin;
m_fractureConductivityMin = other.m_fractureConductivityMin;
// 复制迭代参数
m_iterationCount = other.m_iterationCount;
@ -102,6 +108,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
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("PermeabilityMax", m_permeabilityMax.ToJsonValue(allocator), allocator);
@ -112,6 +119,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
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("PermeabilityMin", m_permeabilityMin.ToJsonValue(allocator), allocator);
@ -122,6 +130,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca
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("IterationCount", m_iterationCount.ToJsonValue(allocator), allocator);
@ -159,6 +168,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("SwiSelected") && jsonValue["SwiSelected"].IsBool()) {
m_swiSelected = jsonValue["SwiSelected"].GetBool();
}
if (jsonValue.HasMember("FractureConductivitySelected") && jsonValue["FractureConductivitySelected"].IsBool()) {
m_fractureConductivitySelected = jsonValue["FractureConductivitySelected"].GetBool();
}
// 反序列化参数最大值
if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) {
@ -185,6 +197,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("SwiMax") && jsonValue["SwiMax"].IsObject()) {
m_swiMax.FromJsonValue(jsonValue["SwiMax"]);
}
if (jsonValue.HasMember("FractureConductivityMax") && jsonValue["FractureConductivityMax"].IsObject()) {
m_fractureConductivityMax.FromJsonValue(jsonValue["FractureConductivityMax"]);
}
// 反序列化参数最小值
if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) {
@ -211,6 +226,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue)
if (jsonValue.HasMember("SwiMin") && jsonValue["SwiMin"].IsObject()) {
m_swiMin.FromJsonValue(jsonValue["SwiMin"]);
}
if (jsonValue.HasMember("FractureConductivityMin") && jsonValue["FractureConductivityMin"].IsObject()) {
m_fractureConductivityMin.FromJsonValue(jsonValue["FractureConductivityMin"]);
}
// 反序列化迭代参数
if (jsonValue.HasMember("IterationCount") && jsonValue["IterationCount"].IsObject()) {
@ -253,6 +271,9 @@ void nmDataAutomaticFitting::setCfSelected(bool selected) { m_cfSelected = selec
bool nmDataAutomaticFitting::getSwiSelected() const { return m_swiSelected; }
void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = selected; }
bool nmDataAutomaticFitting::getFractureConductivitySelected() const { return m_fractureConductivitySelected; }
void nmDataAutomaticFitting::setFractureConductivitySelected(bool selected) { m_fractureConductivitySelected = selected; }
// Getter and Setter implementations for Max values
nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMax() { return m_permeabilityMax; }
@ -279,6 +300,9 @@ void nmDataAutomaticFitting::setCfMax(const nmDataAttribute& cfMax) { m_cfMax =
nmDataAttribute& nmDataAutomaticFitting::getSwiMax() { return m_swiMax; }
void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax = swiMax; }
nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMax() { return m_fractureConductivityMax; }
void nmDataAutomaticFitting::setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax) { m_fractureConductivityMax = fractureConductivityMax; }
// Getter and Setter implementations for Min values
nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMin() { return m_permeabilityMin; }
void nmDataAutomaticFitting::setPermeabilityMin(const nmDataAttribute& permeabilityMin) { m_permeabilityMin = permeabilityMin; }
@ -304,6 +328,9 @@ void nmDataAutomaticFitting::setCfMin(const nmDataAttribute& cfMin) { m_cfMin =
nmDataAttribute& nmDataAutomaticFitting::getSwiMin() { return m_swiMin; }
void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin = swiMin; }
nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMin() { return m_fractureConductivityMin; }
void nmDataAutomaticFitting::setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin) { m_fractureConductivityMin = fractureConductivityMin; }
// Getter and Setter implementations for iteration parameters
nmDataAttribute& nmDataAutomaticFitting::getIterationCount() { return m_iterationCount; }
void nmDataAutomaticFitting::setIterationCount(const nmDataAttribute& iterationCount) { m_iterationCount = iterationCount; }

@ -190,7 +190,7 @@ void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table)
}
}
// 根据当前模型类型控制Ct/Cf/Swi参数显示。
// 根据当前模型类型控制 Ct/Cf/Swi并根据目标井类型控制裂缝导流能力显示。
void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType)
{
if(!table) {
@ -231,6 +231,21 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
setParameterRowVisible(table, 6, showCf); // Cf
setParameterRowVisible(table, 7, showSwi); // Swi
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
bool showFractureConductivity = false;
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(manager && m_targetWellCombo) {
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->currentText());
if(targetWell) {
NM_WELL_MODEL wellType = targetWell->getWellType();
showFractureConductivity =
wellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
wellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
setParameterRowVisible(table, 8, showFractureConductivity); // Dfc
renumberVisibleParameterRows(table);
}
@ -240,20 +255,20 @@ 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_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc"
};
// KAPPA 的边界使用 md、ft、bbl/psi自动拟合界面使用 Darcy、m、m^3/MPa
// 这里统一换算到界面和 PSO 实际使用的单位K 除以 1000h 由 ft 换成 m
// 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。
// Ct/Cf/Swi 沿用模型参数表边界。
// 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
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.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0, 1.0e30
};
if(parameterIndex < 0 || parameterIndex >= 8) {
if(parameterIndex < 0 || parameterIndex >= 9) {
return false;
}
@ -338,6 +353,10 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
automaticFittingData.getSwiMin().setValue(minValue);
automaticFittingData.getSwiMax().setValue(maxValue);
break;
case 8:
automaticFittingData.getFractureConductivityMin().setValue(minValue);
automaticFittingData.getFractureConductivityMax().setValue(maxValue);
break;
default:
break;
}
@ -350,7 +369,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue)
{
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8
if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 9
|| !nmAutoFitUiIsFinite(centerValue)) {
return;
}
@ -383,7 +402,9 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
const double skinHalfRange = 10.0;
newMin = qMax(physicalMin, reference - skinHalfRange);
newMax = qMin(physicalMax, reference + skinHalfRange);
} else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) {
} else if(reference > 0.0
&& !(parameterIndex == 7 && centerValue <= 0.0)
&& !(parameterIndex == 8 && centerValue <= 0.0)) {
const double lowerFactor = 0.1;
const double upperFactor = 10.0;
newMin = qMax(physicalMin, reference * lowerFactor);
@ -392,6 +413,10 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
// 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。
newMin = physicalMin;
newMax = physicalMax;
} else if(parameterIndex == 8) {
// Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。
newMin = physicalMin;
newMax = physicalMax;
}
// 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。
@ -412,7 +437,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) {
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(initialItem) {
bool initialOk = false;
@ -439,7 +464,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
return;
}
for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) {
for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
@ -454,9 +479,12 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges()
bool savedMaxOk = false;
const double savedMin = minItem->text().toDouble(&savedMinOk);
const double savedMax = maxItem->text().toDouble(&savedMaxOk);
// 旧项目没有 Dfc 字段时nmDataAttribute 会表现为 0~0。Dfc=0 在求解器中
// 表示无限导流,不能作为连续拟合区间,因此按当前压裂井初值重新建范围。
const bool savedRangeValid = savedMinOk && savedMaxOk
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
&& savedMax >= savedMin;
&& savedMax >= savedMin
&& !(parameterIndex == 8 && savedMax <= 1.0e-10);
if(savedRangeValid && physicalMax >= physicalMin) {
const double clippedMin = qMax(savedMin, physicalMin);
@ -485,18 +513,18 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
errorMessage = tr("The parameter table is unavailable.");
return false;
}
if(parameterIndex < -1 || parameterIndex >= 8) {
if(parameterIndex < -1 || parameterIndex >= 9) {
errorMessage = tr("The parameter row is invalid.");
return false;
}
static const char* parameterNames[] = {
"Permeability", "Skin", "Wellbore storage", "Porosity",
"Thickness", "Ct", "Cf", "Swi"
"Thickness", "Ct", "Cf", "Swi", "Fracture conductivity"
};
const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex;
const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1;
const int lastParameterIndex = parameterIndex < 0 ? 9 : parameterIndex + 1;
for(int currentParameterIndex = firstParameterIndex;
currentParameterIndex < lastParameterIndex; ++currentParameterIndex) {
// 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。
@ -556,6 +584,14 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par
.arg(tr(parameterNames[currentParameterIndex]));
return false;
}
// 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。
if(currentParameterIndex == 8 && 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]));
return false;
}
}
return true;
@ -681,7 +717,7 @@ void nmWxAutomaticFitting::setupUI()
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
m_parameterTable = new QTableWidget(8, 6, this);
m_parameterTable = new QTableWidget(9, 6, this);
// 设置表头
QStringList headers;
@ -790,6 +826,17 @@ void nmWxAutomaticFitting::setupParameterTable()
m_parameterTable->setItem(7, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble())));
m_parameterTable->setItem(7, 5, new QTableWidgetItem(""));
// 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中
// 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。
m_parameterTable->setItem(8, 0, new QTableWidgetItem("9"));
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")));
// 设置表格行为
for(int i = 0; i < m_parameterTable->rowCount(); ++i) {
for(int j = 0; j < 6; ++j) {
@ -989,6 +1036,7 @@ void nmWxAutomaticFitting::onReverseSelection()
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());
}
void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item)
@ -1107,7 +1155,7 @@ void nmWxAutomaticFitting::onAccept()
m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() ||
m_hCheckBox->isChecked() ||
m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() ||
m_swiCheckBox->isChecked();
m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked();
if(!hasSelectedParams) {
QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!"));
@ -1125,6 +1173,7 @@ void nmWxAutomaticFitting::onAccept()
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");
// 启动自动拟合 - 传递双对数历史数据
startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName);
@ -1141,8 +1190,10 @@ void nmWxAutomaticFitting::onWellSelected(int index)
// 在分类的井数据中查找匹配的井
bool found = false;
bool fracturedWell = false;
double skinValue = 0.0;
double wellboreStorageValue = 0.0;
double fractureConductivityValue = 0.0;
// 查找垂直井
for(int i = 0; i < m_verticalWells.size(); ++i) {
@ -1172,6 +1223,8 @@ void nmWxAutomaticFitting::onWellSelected(int index)
if(m_verticalFracturedWells[i].getWellName() == selectedWellName) {
skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble();
fractureConductivityValue = m_verticalFracturedWells[i].getDfc().getValue().toDouble();
fracturedWell = true;
found = true;
break;
}
@ -1184,6 +1237,8 @@ void nmWxAutomaticFitting::onWellSelected(int index)
if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) {
skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble();
wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble();
fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble();
fracturedWell = true;
found = true;
break;
}
@ -1191,6 +1246,11 @@ void nmWxAutomaticFitting::onWellSelected(int index)
}
if (found) {
// 先按目标井类型刷新可见行,再写入当前井的井级初值。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(manager) {
updateParameterVisibility(m_parameterTable, manager->getSolverModelType());
}
// 更新表格数据
// 确保表格项存在
if(!m_parameterTable->item(1, 3)) {
@ -1205,10 +1265,16 @@ 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(m_autoParameterRanges) {
updateRangeForParameter(1, skinValue);
updateRangeForParameter(2, wellboreStorageValue);
if(fracturedWell) {
updateRangeForParameter(8, fractureConductivityValue);
}
}
}
}
@ -1224,6 +1290,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.setCtSelected(m_ctCheckBox->isChecked());
automaticFittingData.setCfSelected(m_cfCheckBox->isChecked());
automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1);
// 保存渗透率的最小值和最大值
@ -1258,6 +1325,10 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble());
automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble());
// 保存裂缝导流能力的最小值和最大值
automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble());
automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble());
// 保存迭代参数
automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt());
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
@ -1281,6 +1352,7 @@ 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();
// 直接从数据管理器获取目标井
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
@ -1303,6 +1375,23 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
// 根据井类型单独更新这一口井
NM_WELL_MODEL wellType = pTargetWell->getWellType();
// Dfc 属于压裂井对象;只修改这一项,裂缝位置、长度和段数保持原值。
if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) {
nmDataVerticalFracturedWell* fracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pTargetWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
}
} else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) {
nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pTargetWell);
if(fracturedWell) {
nmDataAttribute dfc = fracturedWell->getDfc();
dfc.setValue(newFractureConductivityValue);
fracturedWell->setDfc(dfc);
}
}
if(wellType == NM_WELL_MODEL::Vertical_Well) {
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(pTargetWell);
if(pVerticalWell != nullptr) {
@ -1532,6 +1621,7 @@ void nmWxAutomaticFitting::updateBestParametersToTable()
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); // 裂缝导流能力
// 更新参数值和范围
for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) {

Loading…
Cancel
Save