diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h index c67b4406..b2829efb 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h @@ -11,6 +11,7 @@ #include #include "nmCalculation_global.h" +#include "nmDataAutomaticFitting.h" #include struct nmSensitivityFitSettings; @@ -121,6 +122,8 @@ private: bool loadAllConfigFromDataManager(); void loadOptimizationConfig(); void loadParameterBounds(); + int parameterType(int index) const; + bool fitsFractureGeometry() const; void extractUserInitialValues(); // 有限差分 + LM/信赖域核心算法。 @@ -190,6 +193,7 @@ private: AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown; // 参数索引:0 k,1 skin,2 wellboreC,3 phi,4 Dfc,5 fractureHalfLength。 + QVector m_modelParameters; QVector m_parameterSelected; QVector m_parameterLower; QVector m_parameterUpper; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index f97b106f..6e32007d 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -825,7 +825,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile() QStringList enabledNames; for(int i = 0; i < m_enabledParamIndices.size(); ++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] : QString::number(paramIndex)); } @@ -1010,9 +1010,8 @@ void nmCalculationAutoFitLM::emitTrialLog(const QString& title, QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector& selectedParameters) const { - // 将 LM 内部使用的“启用参数向量”还原成完整 6 维参数向量。 + // 保留原 trace 的六列目标井摘要;多井参数名称和取值另由运行日志及进度界面显示。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 - // trace CSV 和 meta 使用该完整向量记录一次候选评价。 QVector fullParams(6, 0.0); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1051,7 +1050,10 @@ QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector< } 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()) { fullParams[paramIndex] = selectedParameters[i]; @@ -1105,6 +1107,23 @@ void nmCalculationAutoFitLM::loadParameterBounds() ? m_sensitivitySettings->parameters : 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[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[1] = fittingData.getSkinSelected(); @@ -1248,8 +1267,8 @@ bool nmCalculationAutoFitLM::startAutoFitting() emit logMessageGenerated(tr("[Initial parameters]")); for(int i = 0; i < m_userInitialSolution.size(); ++i) { const int index = m_enabledParamIndices[i]; - emit logMessageGenerated(QString("%1: %2 %3").arg(tr(names[index])) - .arg(m_userInitialSolution[i], 0, 'g', 10).arg(tr(units[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[parameterType(index)]))); } try { @@ -1362,8 +1381,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() // 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。 const bool fractureGridParameterSelected = - (m_parameterSelected.size() > 4 && m_parameterSelected[4]) || - (m_parameterSelected.size() > 5 && m_parameterSelected[5]); + fitsFractureGeometry(); if(fractureGridParameterSelected) { nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1485,6 +1503,16 @@ bool nmCalculationAutoFitLM::startAutoFitting() 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) { m_initialValues.clear(); @@ -1674,7 +1702,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() for(int i = 0; i < dimensions; ++i) { int parameterIndex = m_enabledParamIndices[i]; coordinates[i] = toTrustRegionCoordinate( - parameters[i], parameterIndex, + parameters[i], parameterType(parameterIndex), m_parameterLower[parameterIndex], m_parameterUpper[parameterIndex]); } @@ -1687,7 +1715,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() for(int i = 0; i < dimensions; ++i) { int parameterIndex = m_enabledParamIndices[i]; parameters[i] = fromTrustRegionCoordinate( - coordinates[i], parameterIndex, + coordinates[i], parameterType(parameterIndex), m_parameterLower[parameterIndex], m_parameterUpper[parameterIndex]); } @@ -1773,7 +1801,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() // 高度预调整只改变用户勾选的渗透率。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; int heightStagnation = 0; double heightScale = 1.0; @@ -1952,7 +1982,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(upper <= lower) continue; // 表皮包含零和负值,按当前物理值的局部尺度确定扰动。 double localStep = finiteDifferenceStep; - if(parameterIndex == 1) { + if(parameterType(parameterIndex) == 1) { localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower)); } double positiveRoom = 1.0 - base.coordinates[column]; @@ -2496,8 +2526,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters // Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。 const bool fractureGridParameterSelected = - (m_parameterSelected.size() > 4 && m_parameterSelected[4]) || - (m_parameterSelected.size() > 5 && m_parameterSelected[5]); + fitsFractureGeometry(); if(fractureGridParameterSelected) { dataManager->invalidatePebiGrid(); } @@ -2624,6 +2653,33 @@ void nmCalculationAutoFitLM::applyParametersToDataManager(const QVector& 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(well)) { + if(p.type == 4) fractured->getDfc().setValue(value); + else if(p.type == 5) fractured->getFractureHalfLength().setValue(value); + } else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast(well)) { + if(p.type == 4) fractured->getDfc().setValue(value); + else if(p.type == 5) fractured->getFractureHalfLength().setValue(value); + } + } + } + manager->updateReservoirData(reservoir); + return; + } + updateReservoirParameters(parameters); updateWellParameters(parameters); } @@ -4084,3 +4140,17 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co 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; +} diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index e3fb4f32..f9de1922 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -62,18 +62,17 @@ struct nmPebiSolverInputSnapshot struct SensitivityBaseline { SensitivityBaseline() - : m_nTarget(-1), m_dSceneSkin(0.0), m_dSceneStorage(0.0), - m_dWellSkin(0.0), m_dWellStorage(0.0), m_dPressure(0.0), + : m_nTarget(-1), m_dPressure(0.0), m_dScenePermeability(0.0), m_dSceneThickness(0.0), m_dScenePorosity(0.0), m_dReferencePermeability(0.0), m_dReferenceThickness(0.0), m_dReferencePorosity(0.0) { } int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。 - double m_dSceneSkin; - double m_dSceneStorage; - double m_dWellSkin; - double m_dWellStorage; + QVector m_dSceneSkin; + QVector m_dSceneStorage; + QVector m_dWellSkin; + QVector m_dWellStorage; double m_dPressure; double m_dScenePermeability; double m_dSceneThickness; @@ -817,10 +816,12 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit nmPebiSolverInputSnapshot::SensitivityBaseline& baseline = task->m_pInputSnapshot->m_oSensitivityBaseline; baseline.m_nTarget = target; - baseline.m_dSceneSkin = grid.m_oScene.CS.S[target]; - baseline.m_dSceneStorage = grid.m_oScene.CS.C[target]; - baseline.m_dWellSkin = grid.m_vecWellInputs[target].m_dSkin; - baseline.m_dWellStorage = grid.m_vecWellInputs[target].m_dWellboreStorage; + for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) { + baseline.m_dSceneSkin.append(grid.m_oScene.CS.S[i]); + baseline.m_dSceneStorage.append(grid.m_oScene.CS.C[i]); + 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_dScenePermeability = grid.m_oScene.Base.k_ref; baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref; @@ -878,15 +879,21 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun( const double value = values[i]; valid = isFiniteSolverNumber(value); if(!valid) break; - // 井筒参数按目标井槽位写入,储层参数同步到场景和静态属性计算输入。 - if(name == "Skin") { - valid = value >= 0.0; - parameters.m_dSceneSkin = value; - parameters.m_dWellSkin = value; - } else if(name == "C") { - valid = value >= 0.0; - parameters.m_dSceneStorage = value; - parameters.m_dWellStorage = value; + // 用井编码定位槽位,旧版无编码的参数仍对应分析主井。 + if(name == "Skin" || name == "C") { + int wellIndex = variable.m_wellCode.isEmpty() ? parameters.m_nTarget : -1; + for(int w = 0; w < grid.m_vecWellInputs.size(); ++w) + if(grid.m_vecWellInputs[w].m_bRealWell && + grid.m_vecWellInputs[w].m_sWellCode == variable.m_wellCode) { wellIndex = w; break; } + valid = value >= 0.0 && wellIndex >= 0; + if(!valid) break; + 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") { valid = value > 0.0; parameters.m_dPressure = value; @@ -915,11 +922,13 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun( if(errorMessage) *errorMessage = m_sFailureReason; return false; } - const int target = parameters.m_nTarget; - grid.m_oScene.CS.S[target] = parameters.m_dSceneSkin; - grid.m_oScene.CS.C[target] = parameters.m_dSceneStorage; - grid.m_vecWellInputs[target].m_dSkin = parameters.m_dWellSkin; - grid.m_vecWellInputs[target].m_dWellboreStorage = parameters.m_dWellStorage; + // 每组恢复所有井的基准,确定性单参数切换时不会继承上一口井的采样值。 + for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) { + grid.m_oScene.CS.S[i] = parameters.m_dSceneSkin[i]; + grid.m_oScene.CS.C[i] = parameters.m_dSceneStorage[i]; + 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.k_ref = parameters.m_dScenePermeability; grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness; @@ -990,6 +999,25 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit // 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。 if(improveSettings) { const nmDataAutomaticFitting& fit = improveSettings->parameters; + const QVector& 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(parameterWell)) + value = (p.type == 4 ? fractured->getDfc() : fractured->getFractureHalfLength()).getValue().toDouble(); + else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast(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(), fit.getWellboreStorageSelected(), fit.getPorositySelected(), fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; @@ -1019,7 +1047,7 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0, well->getWellboreStorage().getValue().toDouble(), 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( QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j])); task->m_pSensitivityResult->m_fitParameterCodes.append(codes[j]); @@ -1083,81 +1111,99 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector& fitt grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; } } if(target < 0) return fail(tr("The target well is unavailable.")); - const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), - fit.getWellboreStorageSelected(), fit.getPorositySelected(), - fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; - for(int i = 0; i < properties.m_vecDataSets.size(); ++i) { - const nmPropertyInterpolationDataSet& field = properties.m_vecDataSets[i]; - if(field.useForCalculation && ((selected[0] && field.property == "k") || - (selected[3] && field.property == "phi"))) - return fail(tr("The sampled parameters are invalid or use a non-uniform field.")); - } - - // 井型数组与求解槽位顺序一致,保留原端点以免反复缩放造成几何累积偏差。 - std::vector*> fractures; - std::vector > originalFractures; - if(selected[4] || selected[5]) { - size_t vertical = 0, horizontal = 0; - for(int i = 0; i <= target; ++i) { - const int type = grid.m_vecSolverWellOrder[i].m_eWellType; - if(type == Vertical_Fractured_Well) { - if(i == target) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]); - ++vertical; - } else if(type == Horizontal_Fractured_Well) { - if(i == target) { - std::vector >& well = - grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal]; - for(size_t j = 0; j < well.size(); ++j) fractures.push_back(&well[j]); - } - ++horizontal; - } - } - 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]); - } - + QVector fitted = fit.getModelParameters(); + if(fitted.isEmpty()) { + // 旧项目仍按原来的目标井和六参数顺序运行。 + const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), + fit.getWellboreStorageSelected(), fit.getPorositySelected(), + fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; + for(int type = 0; type < 6; ++type) if(selected[type]) { + nmFitParameter p; + p.type = type; + p.selected = true; + if(type != 0 && type != 3) p.wellCode = grid.m_vecWellInputs[target].m_sWellCode; + fitted.append(p); + } + } + for(int i = fitted.size() - 1; i >= 0; --i) + if(!fitted[i].selected) fitted.remove(i); + // 每项记录自己的求解槽位,联合拟合时不再共用一个目标井槽位。 + QVector wellIndices; + for(int i = 0; i < fitted.size(); ++i) { + const nmFitParameter& p = fitted[i]; + int slot = -1; + for(int w = 0; w < grid.m_vecWellInputs.size(); ++w) + if(grid.m_vecWellInputs[w].m_bRealWell && grid.m_vecWellInputs[w].m_sWellCode == p.wellCode) { slot = w; break; } + if(p.type != 0 && p.type != 3 && slot < 0) return fail(tr("The target well is unavailable.")); + wellIndices.append(slot); + for(int field = 0; field < properties.m_vecDataSets.size(); ++field) { + 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.")); + } + } + + // 保存原裂缝端点,所有候选都从原几何缩放,防止重复评价产生累计偏差。 + const std::vector > originalVertical = grid.m_oGridInput.FractureVerticalWell; + const std::vector > > originalHorizontal = grid.m_oGridInput.MultistageFracturedHorizontalWell; const QString resultWellCode = m_sAutoFitTargetWellCode; m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode; - auto evaluate = [&](const QVector& parameters) -> QVector > { + auto evaluate = [&](const QVector& values) -> QVector > { if(isCancelRequested()) return QVector >(); - QVector values = settings.initialValues; - int index = 0; - for(int i = 0; i < 6; ++i) if(selected[i]) values[i] = parameters[index++]; - if(selected[0]) { - grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(values[0]); - properties.m_dReferencePermeabilityMilliDarcy = values[0]; - } - if(selected[1]) { - grid.m_oScene.CS.S[target] = values[1]; - grid.m_vecWellInputs[target].m_dSkin = values[1]; - } - if(selected[2]) { - grid.m_oScene.CS.C[target] = values[2]; - grid.m_vecWellInputs[target].m_dWellboreStorage = values[2]; - } - if(selected[3]) { - grid.m_oScene.Base.phi_ref = values[3]; - properties.m_dReferencePorosity = values[3]; - } - for(size_t i = 0; i < fractures.size(); ++i) { - std::vector& fracture = *fractures[i]; - fracture = originalFractures[i]; - if(selected[4]) fracture[5] = values[4]; - if(selected[5]) { - 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 = values[5] / 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; + grid.m_oGridInput.FractureVerticalWell = originalVertical; + grid.m_oGridInput.MultistageFracturedHorizontalWell = originalHorizontal; + for(int i = 0; i < fitted.size(); ++i) { + const int type = fitted[i].type; + const int slot = wellIndices[i]; + const double value = values[i]; + if(type == 0) { + grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(value); + properties.m_dReferencePermeabilityMilliDarcy = value; + } else if(type == 3) { + grid.m_oScene.Base.phi_ref = value; + properties.m_dReferencePorosity = value; + } else if(type == 1) { + grid.m_oScene.CS.S[slot] = value; + grid.m_vecWellInputs[slot].m_dSkin = value; + } else if(type == 2) { + grid.m_oScene.CS.C[slot] = value; + grid.m_vecWellInputs[slot].m_dWellboreStorage = value; + } else { + std::vector*> fractures; + size_t vertical = 0, horizontal = 0; + for(int w = 0; w <= slot; ++w) { + const int wellType = grid.m_vecSolverWellOrder[w].m_eWellType; + if(wellType == Vertical_Fractured_Well) { + if(w == slot) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]); + ++vertical; + } else if(wellType == Horizontal_Fractured_Well) { + if(w == slot) { + std::vector >& stages = grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal]; + for(size_t j = 0; j < stages.size(); ++j) fractures.push_back(&stages[j]); + } + ++horizontal; + } + } + if(fractures.empty()) return QVector >(); + for(size_t j = 0; j < fractures.size(); ++j) { + std::vector& 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(); calculateSingleRun(); return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector >();