feat(nmNum): 支持多井采样与 LM 联合拟合

- 按所属井应用采样值,每组恢复各井基准参数。

- 多井参数共同参与 LM 优化,沿用单目标井误差。

- 普通拟合按井写回,分析结果记录各井拟合参数。
feature/Sensitivity-20261012
lvjunjie 4 days ago
parent 56b93dd62c
commit 2a41333007

@ -11,6 +11,7 @@
#include <limits> #include <limits>
#include "nmCalculation_global.h" #include "nmCalculation_global.h"
#include "nmDataAutomaticFitting.h"
#include <functional> #include <functional>
struct nmSensitivityFitSettings; struct nmSensitivityFitSettings;
@ -121,6 +122,8 @@ private:
bool loadAllConfigFromDataManager(); bool loadAllConfigFromDataManager();
void loadOptimizationConfig(); void loadOptimizationConfig();
void loadParameterBounds(); void loadParameterBounds();
int parameterType(int index) const;
bool fitsFractureGeometry() const;
void extractUserInitialValues(); void extractUserInitialValues();
// 有限差分 + LM/信赖域核心算法。 // 有限差分 + LM/信赖域核心算法。
@ -190,6 +193,7 @@ private:
AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown; AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown;
// 参数索引:0 k,1 skin,2 wellboreC,3 phi,4 Dfc,5 fractureHalfLength。 // 参数索引:0 k,1 skin,2 wellboreC,3 phi,4 Dfc,5 fractureHalfLength。
QVector<nmFitParameter> m_modelParameters;
QVector<bool> m_parameterSelected; QVector<bool> m_parameterSelected;
QVector<double> m_parameterLower; QVector<double> m_parameterLower;
QVector<double> m_parameterUpper; QVector<double> m_parameterUpper;

@ -825,7 +825,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QStringList enabledNames; QStringList enabledNames;
for(int i = 0; i < m_enabledParamIndices.size(); ++i) { for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
int paramIndex = m_enabledParamIndices[i]; int paramIndex = m_enabledParamIndices[i];
enabledNames << ((paramIndex >= 0 && paramIndex < parameterNames.size()) enabledNames << (!m_modelParameters.isEmpty() ? m_modelParameters[paramIndex].name() : (paramIndex >= 0 && paramIndex < parameterNames.size())
? parameterNames[paramIndex] ? parameterNames[paramIndex]
: QString::number(paramIndex)); : QString::number(paramIndex));
} }
@ -1010,9 +1010,8 @@ void nmCalculationAutoFitLM::emitTrialLog(const QString& title,
QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<double>& selectedParameters) const QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<double>& selectedParameters) const
{ {
// 将 LM 内部使用的“启用参数向量”还原成完整 6 维参数向量。 // 保留原 trace 的六列目标井摘要;多井参数名称和取值另由运行日志及进度界面显示。
// 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。
// trace CSV 和 meta 使用该完整向量记录一次候选评价。
QVector<double> fullParams(6, 0.0); QVector<double> fullParams(6, 0.0);
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
@ -1051,7 +1050,10 @@ QVector<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<
} }
for(int i = 0; i < selectedParameters.size() && i < m_enabledParamIndices.size(); ++i) { for(int i = 0; i < selectedParameters.size() && i < m_enabledParamIndices.size(); ++i) {
int paramIndex = m_enabledParamIndices[i]; const int index = m_enabledParamIndices[i];
if(!m_modelParameters.isEmpty() && !m_modelParameters[index].wellCode.isEmpty() &&
m_modelParameters[index].wellName != m_targetWellName) continue;
int paramIndex = parameterType(index);
if(paramIndex >= 0 && paramIndex < fullParams.size()) { if(paramIndex >= 0 && paramIndex < fullParams.size()) {
fullParams[paramIndex] = selectedParameters[i]; fullParams[paramIndex] = selectedParameters[i];
@ -1105,6 +1107,23 @@ void nmCalculationAutoFitLM::loadParameterBounds()
? m_sensitivitySettings->parameters ? m_sensitivitySettings->parameters
: nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy(); : nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy();
m_modelParameters = fittingData.getModelParameters();
if(!m_modelParameters.isEmpty()) {
// 参数位置与参数类型分开,多口井的 Skin/C 可同时进入同一个 LM 向量。
m_parameterSelected.clear();
m_parameterLower.clear();
m_parameterUpper.clear();
m_enabledParamIndices.clear();
for(int i = 0; i < m_modelParameters.size(); ++i) {
const nmFitParameter& p = m_modelParameters[i];
m_parameterSelected.append(p.selected);
m_parameterLower.append(p.lower);
m_parameterUpper.append(p.upper);
if(p.selected) m_enabledParamIndices.append(i);
}
return;
}
m_parameterSelected.resize(6); m_parameterSelected.resize(6);
m_parameterSelected[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[0] = fittingData.getPermeabilitySelected();
m_parameterSelected[1] = fittingData.getSkinSelected(); m_parameterSelected[1] = fittingData.getSkinSelected();
@ -1248,8 +1267,8 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("[Initial parameters]")); emit logMessageGenerated(tr("[Initial parameters]"));
for(int i = 0; i < m_userInitialSolution.size(); ++i) { for(int i = 0; i < m_userInitialSolution.size(); ++i) {
const int index = m_enabledParamIndices[i]; const int index = m_enabledParamIndices[i];
emit logMessageGenerated(QString("%1: %2 %3").arg(tr(names[index])) emit logMessageGenerated(QString("%1: %2 %3").arg(m_modelParameters.isEmpty() ? tr(names[index]) : m_modelParameters[index].name())
.arg(m_userInitialSolution[i], 0, 'g', 10).arg(tr(units[index]))); .arg(m_userInitialSolution[i], 0, 'g', 10).arg(tr(units[parameterType(index)])));
} }
try { try {
@ -1362,8 +1381,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
// 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。 // 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。
const bool fractureGridParameterSelected = const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 4 && m_parameterSelected[4]) || fitsFractureGeometry();
(m_parameterSelected.size() > 5 && m_parameterSelected[5]);
if(fractureGridParameterSelected) { if(fractureGridParameterSelected) {
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager* dataManager =
nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager::getCurrentInstance();
@ -1485,6 +1503,16 @@ bool nmCalculationAutoFitLM::startAutoFitting()
void nmCalculationAutoFitLM::extractUserInitialValues() void nmCalculationAutoFitLM::extractUserInitialValues()
{ {
// 多井列表的初值由窗口读取当前模型并校验,按联合优化向量的顺序取值。
if(!m_modelParameters.isEmpty()) {
m_initialValues.clear();
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
const nmFitParameter& p = m_modelParameters[m_enabledParamIndices[i]];
m_initialValues.append(qBound(p.lower, p.initial, p.upper));
}
return;
}
// 按勾选顺序提取快照初值,并沿用窗口确认的上下限。 // 按勾选顺序提取快照初值,并沿用窗口确认的上下限。
if(m_sensitivitySettings) { if(m_sensitivitySettings) {
m_initialValues.clear(); m_initialValues.clear();
@ -1674,7 +1702,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int i = 0; i < dimensions; ++i) { for(int i = 0; i < dimensions; ++i) {
int parameterIndex = m_enabledParamIndices[i]; int parameterIndex = m_enabledParamIndices[i];
coordinates[i] = toTrustRegionCoordinate( coordinates[i] = toTrustRegionCoordinate(
parameters[i], parameterIndex, parameters[i], parameterType(parameterIndex),
m_parameterLower[parameterIndex], m_parameterLower[parameterIndex],
m_parameterUpper[parameterIndex]); m_parameterUpper[parameterIndex]);
} }
@ -1687,7 +1715,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int i = 0; i < dimensions; ++i) { for(int i = 0; i < dimensions; ++i) {
int parameterIndex = m_enabledParamIndices[i]; int parameterIndex = m_enabledParamIndices[i];
parameters[i] = fromTrustRegionCoordinate( parameters[i] = fromTrustRegionCoordinate(
coordinates[i], parameterIndex, coordinates[i], parameterType(parameterIndex),
m_parameterLower[parameterIndex], m_parameterLower[parameterIndex],
m_parameterUpper[parameterIndex]); m_parameterUpper[parameterIndex]);
} }
@ -1773,7 +1801,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差 // 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差
// 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。 // 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。
const int permeabilityColumn = m_enabledParamIndices.indexOf(0); int permeabilityColumn = -1;
for(int i = 0; i < m_enabledParamIndices.size(); ++i)
if(parameterType(m_enabledParamIndices[i]) == 0) { permeabilityColumn = i; break; }
double heightBaseline = current.breakdown.verticalLoss; double heightBaseline = current.breakdown.verticalLoss;
int heightStagnation = 0; int heightStagnation = 0;
double heightScale = 1.0; double heightScale = 1.0;
@ -1952,7 +1982,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(upper <= lower) continue; if(upper <= lower) continue;
// 表皮包含零和负值,按当前物理值的局部尺度确定扰动。 // 表皮包含零和负值,按当前物理值的局部尺度确定扰动。
double localStep = finiteDifferenceStep; double localStep = finiteDifferenceStep;
if(parameterIndex == 1) { if(parameterType(parameterIndex) == 1) {
localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower)); localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower));
} }
double positiveRoom = 1.0 - base.coordinates[column]; double positiveRoom = 1.0 - base.coordinates[column];
@ -2496,8 +2526,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector<double>& parameters
// Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。 // Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。
const bool fractureGridParameterSelected = const bool fractureGridParameterSelected =
(m_parameterSelected.size() > 4 && m_parameterSelected[4]) || fitsFractureGeometry();
(m_parameterSelected.size() > 5 && m_parameterSelected[5]);
if(fractureGridParameterSelected) { if(fractureGridParameterSelected) {
dataManager->invalidatePebiGrid(); dataManager->invalidatePebiGrid();
} }
@ -2624,6 +2653,33 @@ void nmCalculationAutoFitLM::applyParametersToDataManager(const QVector<double>&
return; return;
} }
if(!m_modelParameters.isEmpty()) {
// 写回每项所属井的现有属性,不更换井对象;共用储层参数只更新一次。
nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
nmDataReservoir reservoir = manager->getReservoirDataCopy();
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
const nmFitParameter& p = m_modelParameters[m_enabledParamIndices[i]];
const double value = parameters[i];
if(p.type == 0) reservoir.getPermeability().setValue(value);
else if(p.type == 3) reservoir.getPorosity().setValue(value);
else {
nmDataWellBase* well = manager->findWellByCode(p.wellCode);
if(!well) continue;
if(p.type == 1 && well->getPerforationCount() > 0) well->getPerforation(0)->getSkin().setValue(value);
else if(p.type == 2) well->getWellboreStorage().setValue(value);
else if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
if(p.type == 4) fractured->getDfc().setValue(value);
else if(p.type == 5) fractured->getFractureHalfLength().setValue(value);
} else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
if(p.type == 4) fractured->getDfc().setValue(value);
else if(p.type == 5) fractured->getFractureHalfLength().setValue(value);
}
}
}
manager->updateReservoirData(reservoir);
return;
}
updateReservoirParameters(parameters); updateReservoirParameters(parameters);
updateWellParameters(parameters); updateWellParameters(parameters);
} }
@ -4084,3 +4140,17 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co
return tr("Unknown reason"); return tr("Unknown reason");
} }
} }
int nmCalculationAutoFitLM::parameterType(int index) const
{
// 旧单井向量的下标就是类型,多井向量按配置读取类型。
return m_modelParameters.isEmpty() ? index : m_modelParameters[index].type;
}
bool nmCalculationAutoFitLM::fitsFractureGeometry() const
{
// 任一井拟合裂缝参数都需要重建网格。
for(int i = 0; i < m_enabledParamIndices.size(); ++i)
if(parameterType(m_enabledParamIndices[i]) >= 4) return true;
return false;
}

@ -62,18 +62,17 @@ struct nmPebiSolverInputSnapshot
struct SensitivityBaseline struct SensitivityBaseline
{ {
SensitivityBaseline() SensitivityBaseline()
: m_nTarget(-1), m_dSceneSkin(0.0), m_dSceneStorage(0.0), : m_nTarget(-1), m_dPressure(0.0),
m_dWellSkin(0.0), m_dWellStorage(0.0), m_dPressure(0.0),
m_dScenePermeability(0.0), m_dSceneThickness(0.0), m_dScenePermeability(0.0), m_dSceneThickness(0.0),
m_dScenePorosity(0.0), m_dReferencePermeability(0.0), m_dScenePorosity(0.0), m_dReferencePermeability(0.0),
m_dReferenceThickness(0.0), m_dReferencePorosity(0.0) m_dReferenceThickness(0.0), m_dReferencePorosity(0.0)
{ {
} }
int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。 int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。
double m_dSceneSkin; QVector<double> m_dSceneSkin;
double m_dSceneStorage; QVector<double> m_dSceneStorage;
double m_dWellSkin; QVector<double> m_dWellSkin;
double m_dWellStorage; QVector<double> m_dWellStorage;
double m_dPressure; double m_dPressure;
double m_dScenePermeability; double m_dScenePermeability;
double m_dSceneThickness; double m_dSceneThickness;
@ -817,10 +816,12 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
nmPebiSolverInputSnapshot::SensitivityBaseline& baseline = nmPebiSolverInputSnapshot::SensitivityBaseline& baseline =
task->m_pInputSnapshot->m_oSensitivityBaseline; task->m_pInputSnapshot->m_oSensitivityBaseline;
baseline.m_nTarget = target; baseline.m_nTarget = target;
baseline.m_dSceneSkin = grid.m_oScene.CS.S[target]; for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
baseline.m_dSceneStorage = grid.m_oScene.CS.C[target]; baseline.m_dSceneSkin.append(grid.m_oScene.CS.S[i]);
baseline.m_dWellSkin = grid.m_vecWellInputs[target].m_dSkin; baseline.m_dSceneStorage.append(grid.m_oScene.CS.C[i]);
baseline.m_dWellStorage = grid.m_vecWellInputs[target].m_dWellboreStorage; baseline.m_dWellSkin.append(grid.m_vecWellInputs[i].m_dSkin);
baseline.m_dWellStorage.append(grid.m_vecWellInputs[i].m_dWellboreStorage);
}
baseline.m_dPressure = grid.m_oScene.Base.Pi; baseline.m_dPressure = grid.m_oScene.Base.Pi;
baseline.m_dScenePermeability = grid.m_oScene.Base.k_ref; baseline.m_dScenePermeability = grid.m_oScene.Base.k_ref;
baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref; baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref;
@ -878,15 +879,21 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun(
const double value = values[i]; const double value = values[i];
valid = isFiniteSolverNumber(value); valid = isFiniteSolverNumber(value);
if(!valid) break; if(!valid) break;
// 井筒参数按目标井槽位写入,储层参数同步到场景和静态属性计算输入。 // 用井编码定位槽位,旧版无编码的参数仍对应分析主井。
if(name == "Skin") { if(name == "Skin" || name == "C") {
valid = value >= 0.0; int wellIndex = variable.m_wellCode.isEmpty() ? parameters.m_nTarget : -1;
parameters.m_dSceneSkin = value; for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
parameters.m_dWellSkin = value; if(grid.m_vecWellInputs[w].m_bRealWell &&
} else if(name == "C") { grid.m_vecWellInputs[w].m_sWellCode == variable.m_wellCode) { wellIndex = w; break; }
valid = value >= 0.0; valid = value >= 0.0 && wellIndex >= 0;
parameters.m_dSceneStorage = value; if(!valid) break;
parameters.m_dWellStorage = value; if(name == "Skin") {
parameters.m_dSceneSkin[wellIndex] = value;
parameters.m_dWellSkin[wellIndex] = value;
} else {
parameters.m_dSceneStorage[wellIndex] = value;
parameters.m_dWellStorage[wellIndex] = value;
}
} else if(name == "Pi") { } else if(name == "Pi") {
valid = value > 0.0; valid = value > 0.0;
parameters.m_dPressure = value; parameters.m_dPressure = value;
@ -915,11 +922,13 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun(
if(errorMessage) *errorMessage = m_sFailureReason; if(errorMessage) *errorMessage = m_sFailureReason;
return false; return false;
} }
const int target = parameters.m_nTarget; // 每组恢复所有井的基准,确定性单参数切换时不会继承上一口井的采样值。
grid.m_oScene.CS.S[target] = parameters.m_dSceneSkin; for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
grid.m_oScene.CS.C[target] = parameters.m_dSceneStorage; grid.m_oScene.CS.S[i] = parameters.m_dSceneSkin[i];
grid.m_vecWellInputs[target].m_dSkin = parameters.m_dWellSkin; grid.m_oScene.CS.C[i] = parameters.m_dSceneStorage[i];
grid.m_vecWellInputs[target].m_dWellboreStorage = parameters.m_dWellStorage; grid.m_vecWellInputs[i].m_dSkin = parameters.m_dWellSkin[i];
grid.m_vecWellInputs[i].m_dWellboreStorage = parameters.m_dWellStorage[i];
}
grid.m_oScene.Base.Pi = parameters.m_dPressure; grid.m_oScene.Base.Pi = parameters.m_dPressure;
grid.m_oScene.Base.k_ref = parameters.m_dScenePermeability; grid.m_oScene.Base.k_ref = parameters.m_dScenePermeability;
grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness; grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness;
@ -990,6 +999,25 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
// 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。 // 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。
if(improveSettings) { if(improveSettings) {
const nmDataAutomaticFitting& fit = improveSettings->parameters; const nmDataAutomaticFitting& fit = improveSettings->parameters;
const QVector<nmFitParameter>& modelParameters = fit.getModelParameters();
for(int i = 0; i < modelParameters.size(); ++i) {
const nmFitParameter& p = modelParameters[i];
if(!p.selected) continue;
double value = p.type == 0 ? m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy
: m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity;
nmDataWellBase* parameterWell = m_pDataManager->findWellByCode(p.wellCode);
if(p.type != 0 && p.type != 3) {
if(!parameterWell) { if(errorMessage) *errorMessage = tr("The target well is unavailable."); return nullptr; }
if(p.type == 1) value = parameterWell->getPerforation(0)->getSkin().getValue().toDouble();
else if(p.type == 2) value = parameterWell->getWellboreStorage().getValue().toDouble();
else if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(parameterWell))
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(parameterWell))
value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble();
}
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(p.name(), value, p.unit()));
task->m_pSensitivityResult->m_fitParameterCodes.append(p.wellCode.isEmpty() ? p.code() : p.wellCode + "/" + p.code());
}
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
fit.getWellboreStorageSelected(), fit.getPorositySelected(), fit.getWellboreStorageSelected(), fit.getPorositySelected(),
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
@ -1019,7 +1047,7 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0, well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0,
well->getWellboreStorage().getValue().toDouble(), well->getWellboreStorage().getValue().toDouble(),
m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, conductivity, halfLength }; m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, conductivity, halfLength };
for(int j = 0; j < 6; ++j) if(selected[j]) { for(int j = 0; j < 6; ++j) if(modelParameters.isEmpty() && selected[j]) {
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute( task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(
QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j])); QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j]));
task->m_pSensitivityResult->m_fitParameterCodes.append(codes[j]); task->m_pSensitivityResult->m_fitParameterCodes.append(codes[j]);
@ -1083,81 +1111,99 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fitt
grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; } grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; }
} }
if(target < 0) return fail(tr("The target well is unavailable.")); if(target < 0) return fail(tr("The target well is unavailable."));
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), QVector<nmFitParameter> fitted = fit.getModelParameters();
fit.getWellboreStorageSelected(), fit.getPorositySelected(), if(fitted.isEmpty()) {
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; // 旧项目仍按原来的目标井和六参数顺序运行。
for(int i = 0; i < properties.m_vecDataSets.size(); ++i) { const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
const nmPropertyInterpolationDataSet& field = properties.m_vecDataSets[i]; fit.getWellboreStorageSelected(), fit.getPorositySelected(),
if(field.useForCalculation && ((selected[0] && field.property == "k") || fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
(selected[3] && field.property == "phi"))) for(int type = 0; type < 6; ++type) if(selected[type]) {
return fail(tr("The sampled parameters are invalid or use a non-uniform field.")); nmFitParameter p;
} p.type = type;
p.selected = true;
// 井型数组与求解槽位顺序一致,保留原端点以免反复缩放造成几何累积偏差。 if(type != 0 && type != 3) p.wellCode = grid.m_vecWellInputs[target].m_sWellCode;
std::vector<std::vector<double>*> fractures; fitted.append(p);
std::vector<std::vector<double> > originalFractures; }
if(selected[4] || selected[5]) { }
size_t vertical = 0, horizontal = 0; for(int i = fitted.size() - 1; i >= 0; --i)
for(int i = 0; i <= target; ++i) { if(!fitted[i].selected) fitted.remove(i);
const int type = grid.m_vecSolverWellOrder[i].m_eWellType; // 每项记录自己的求解槽位,联合拟合时不再共用一个目标井槽位。
if(type == Vertical_Fractured_Well) { QVector<int> wellIndices;
if(i == target) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]); for(int i = 0; i < fitted.size(); ++i) {
++vertical; const nmFitParameter& p = fitted[i];
} else if(type == Horizontal_Fractured_Well) { int slot = -1;
if(i == target) { for(int w = 0; w < grid.m_vecWellInputs.size(); ++w)
std::vector<std::vector<double> >& well = if(grid.m_vecWellInputs[w].m_bRealWell && grid.m_vecWellInputs[w].m_sWellCode == p.wellCode) { slot = w; break; }
grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal]; if(p.type != 0 && p.type != 3 && slot < 0) return fail(tr("The target well is unavailable."));
for(size_t j = 0; j < well.size(); ++j) fractures.push_back(&well[j]); wellIndices.append(slot);
} for(int field = 0; field < properties.m_vecDataSets.size(); ++field) {
++horizontal; const nmPropertyInterpolationDataSet& data = properties.m_vecDataSets[field];
} if(data.useForCalculation && ((p.type == 0 && data.property == "k") || (p.type == 3 && data.property == "phi")))
} return fail(tr("The sampled parameters are invalid or use a non-uniform field."));
if(fractures.empty()) return fail(tr("Fracture parameters are unavailable for the target well.")); }
for(size_t i = 0; i < fractures.size(); ++i) originalFractures.push_back(*fractures[i]); }
}
// 保存原裂缝端点,所有候选都从原几何缩放,防止重复评价产生累计偏差。
const std::vector<std::vector<double> > originalVertical = grid.m_oGridInput.FractureVerticalWell;
const std::vector<std::vector<std::vector<double> > > originalHorizontal = grid.m_oGridInput.MultistageFracturedHorizontalWell;
const QString resultWellCode = m_sAutoFitTargetWellCode; const QString resultWellCode = m_sAutoFitTargetWellCode;
m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode; m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode;
auto evaluate = [&](const QVector<double>& parameters) -> QVector<QVector<double> > { auto evaluate = [&](const QVector<double>& values) -> QVector<QVector<double> > {
if(isCancelRequested()) return QVector<QVector<double> >(); if(isCancelRequested()) return QVector<QVector<double> >();
QVector<double> values = settings.initialValues; grid.m_oGridInput.FractureVerticalWell = originalVertical;
int index = 0; grid.m_oGridInput.MultistageFracturedHorizontalWell = originalHorizontal;
for(int i = 0; i < 6; ++i) if(selected[i]) values[i] = parameters[index++]; for(int i = 0; i < fitted.size(); ++i) {
if(selected[0]) { const int type = fitted[i].type;
grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(values[0]); const int slot = wellIndices[i];
properties.m_dReferencePermeabilityMilliDarcy = values[0]; const double value = values[i];
} if(type == 0) {
if(selected[1]) { grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(value);
grid.m_oScene.CS.S[target] = values[1]; properties.m_dReferencePermeabilityMilliDarcy = value;
grid.m_vecWellInputs[target].m_dSkin = values[1]; } else if(type == 3) {
} grid.m_oScene.Base.phi_ref = value;
if(selected[2]) { properties.m_dReferencePorosity = value;
grid.m_oScene.CS.C[target] = values[2]; } else if(type == 1) {
grid.m_vecWellInputs[target].m_dWellboreStorage = values[2]; grid.m_oScene.CS.S[slot] = value;
} grid.m_vecWellInputs[slot].m_dSkin = value;
if(selected[3]) { } else if(type == 2) {
grid.m_oScene.Base.phi_ref = values[3]; grid.m_oScene.CS.C[slot] = value;
properties.m_dReferencePorosity = values[3]; grid.m_vecWellInputs[slot].m_dWellboreStorage = value;
} } else {
for(size_t i = 0; i < fractures.size(); ++i) { std::vector<std::vector<double>*> fractures;
std::vector<double>& fracture = *fractures[i]; size_t vertical = 0, horizontal = 0;
fracture = originalFractures[i]; for(int w = 0; w <= slot; ++w) {
if(selected[4]) fracture[5] = values[4]; const int wellType = grid.m_vecSolverWellOrder[w].m_eWellType;
if(selected[5]) { if(wellType == Vertical_Fractured_Well) {
const double dx = (fracture[2] - fracture[0]) * 0.5; if(w == slot) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]);
const double dy = (fracture[3] - fracture[1]) * 0.5; ++vertical;
const double centerX = (fracture[0] + fracture[2]) * 0.5; } else if(wellType == Horizontal_Fractured_Well) {
const double centerY = (fracture[1] + fracture[3]) * 0.5; if(w == slot) {
const double scale = values[5] / std::sqrt(dx * dx + dy * dy); std::vector<std::vector<double> >& stages = grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal];
fracture[0] = centerX - dx * scale; for(size_t j = 0; j < stages.size(); ++j) fractures.push_back(&stages[j]);
fracture[1] = centerY - dy * scale; }
fracture[2] = centerX + dx * scale; ++horizontal;
fracture[3] = centerY + dy * scale; }
}
if(fractures.empty()) return QVector<QVector<double> >();
for(size_t j = 0; j < fractures.size(); ++j) {
std::vector<double>& fracture = *fractures[j];
if(type == 4) fracture[5] = value;
else {
const double dx = (fracture[2] - fracture[0]) * 0.5;
const double dy = (fracture[3] - fracture[1]) * 0.5;
const double centerX = (fracture[0] + fracture[2]) * 0.5;
const double centerY = (fracture[1] + fracture[3]) * 0.5;
const double scale = value / std::sqrt(dx * dx + dy * dy);
fracture[0] = centerX - dx * scale;
fracture[1] = centerY - dy * scale;
fracture[2] = centerX + dx * scale;
fracture[3] = centerY + dy * scale;
}
}
if(type == 4) grid.m_vecWellInputs[slot].m_dDfc = value;
m_pInputSnapshot->m_bRequiresGridCalculation = true;
} }
} }
if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4];
m_pInputSnapshot->m_bRequiresGridCalculation =
m_pInputSnapshot->m_bRequiresGridCalculation || !fractures.empty();
m_sFailureReason.clear(); m_sFailureReason.clear();
calculateSingleRun(); calculateSingleRun();
return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >(); return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >();

Loading…
Cancel
Save