From e8563506b9eae901aad504db4caddf6bf5065a0d Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 10:26:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8E=8B=E8=A3=82=E4=BA=95?= =?UTF-8?q?=E5=AF=BC=E6=B5=81=E8=83=BD=E5=8A=9B=20Dfc=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8B=9F=E5=90=88=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 93646 -> 93895 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 8 + .../nmCalculation/nmCalculationAutoFitPSO.h | 8 +- Include/nmNum/nmData/nmDataAutomaticFitting.h | 13 ++ Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 1 + .../nmCalculation/nmCalculationAutoFitPSO.cpp | 166 +++++++++++++++--- Src/nmNum/nmData/nmDataAutomaticFitting.cpp | 27 +++ Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 122 +++++++++++-- 8 files changed, 299 insertions(+), 46 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 19bf9a383803b135f0206e61491f581566c84ea4..14a0e5f0d7b69500fcb31946cf2f7bbf3d0a7285 100644 GIT binary patch delta 6293 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 delta 6075 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 diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 465cceb..e2bafea 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -3486,6 +3486,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We On 开启 + + Fracture conductivity + 裂缝导流能力 + + + The minimum value of %1 must be greater than zero for automatic fitting. + 自动拟合时,%1 的最小值和初始值必须大于零。 + nmWxAutomaticfitting diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index b3e201a..0bf415b 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -374,10 +374,10 @@ private: // 3 phi 孔隙度;4 h 储层厚度;5 Ct 综合压缩系数; // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度。 // m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。 - QVector m_parameterSelected; // 完整 8 个参数是否被用户勾选参与拟合。 - QVector m_parameterLower; // 完整 8 个参数的搜索下界。 - QVector m_parameterUpper; // 完整 8 个参数的搜索上界。 - QVector m_enabledParamIndices; // 被勾选参数在完整 8 维体系中的索引。 + QVector m_parameterSelected; // 完整 9 个参数是否被用户勾选参与拟合。 + QVector m_parameterLower; // 完整 9 个参数的搜索下界。 + QVector m_parameterUpper; // 完整 9 个参数的搜索上界。 + QVector m_enabledParamIndices; // 被勾选参数在完整 9 维体系中的索引。 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 b2da223..18bc851 100644 --- a/Include/nmNum/nmData/nmDataAutomaticFitting.h +++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h @@ -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 diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 5b3848c..ada66d0 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -91,6 +91,7 @@ private: QCheckBox* m_ctCheckBox; // 综合压缩系数 QCheckBox* m_cfCheckBox; // 岩石压缩系数 QCheckBox* m_swiCheckBox; // 初始含水饱和度 + QCheckBox* m_dfcCheckBox; // 裂缝导流能力 // 按钮 QPushButton* m_reverseBtn; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index a4eb476..2c75f29 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-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 nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector& selectedParameters) const { - // 将粒子内部使用的“启用参数向量”还原成完整 8 维参数向量。 + // 将粒子内部使用的“启用参数向量”还原成完整 9 维参数向量。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 // trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。 - QVector fullParams(8, 0.0); + QVector fullParams(9, 0.0); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1198,8 +1212,26 @@ QVector 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(pTargetWell); + if(fracturedWell) { + fullParams[8] = fracturedWell->getDfc().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(); + } + } } } @@ -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(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getDfc().getValue().toDouble(); + } + } else if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getDfc().getValue().toDouble(); + } + } + break; } m_initialValues.append(initialValue); @@ -5461,6 +5534,17 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& 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> solverResult; @@ -5623,7 +5707,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector& 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& parame { // 更新目标井上的拟合参数。目前井级可拟合参数主要是: // - skin:写入第一个 perforation; - // - wellboreC:写入井筒储集系数。 + // - wellboreC:写入井筒储集系数; + // - Dfc:只写入垂直压裂井或多段压裂水平井的裂缝导流能力。 // 如果目标井不存在或没有射孔数据,这里只记录 debug,不抛异常。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -5705,6 +5790,27 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame pWell->setWellboreStorage(wellboreAttr); } break; + + case 8: { // 裂缝导流能力 + if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(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(pWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(value); + fracturedWell->setDfc(dfc); + } + } + } + break; } paramIndex++; @@ -6139,6 +6245,14 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector& 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; } } diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp index 4d3b6d8..a7a3462 100644 --- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp +++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp @@ -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; } diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 2a527ee..34415d0 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -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 除以 1000,h 由 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(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(pTargetWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(newFractureConductivityValue); + fracturedWell->setDfc(dfc); + } + } + if(wellType == NM_WELL_MODEL::Vertical_Well) { nmDataVerticalWell* pVerticalWell = dynamic_cast(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) {