feat(nmNum): 接入第四种计算方式的 LM 批量拟合

- 按预览样本逐组拟合,每组恢复独立输入。

- 拟合设置固定使用 LM,复用进度条和停止流程。

- 保留各组曲线和误差,不修改主模型。
feature/Sensitivity-20261012
lvjunjie 1 week ago
parent cf9859c83f
commit 67101e5747

@ -11,6 +11,9 @@
#include <limits> #include <limits>
#include "nmCalculation_global.h" #include "nmCalculation_global.h"
#include <functional>
struct nmSensitivityFitSettings;
// 双对数曲线误差分解。预调整使用纵向偏差,形状阶段使用双曲线斜率残差, // 双对数曲线误差分解。预调整使用纵向偏差,形状阶段使用双曲线斜率残差,
// 总误差部分只使用 total 接受候选;两个 LM 目标均联合调整有效参数。 // 总误差部分只使用 total 接受候选;两个 LM 目标均联合调整有效参数。
@ -78,6 +81,10 @@ public:
void setTargetLogLogData(const QVector<QVector<double> >& targetData); void setTargetLogLogData(const QVector<QVector<double> >& targetData);
bool startAutoFitting(); bool startAutoFitting();
// 在样本副本上复用拟合算法,评价和停止由敏感性任务负责,不写回当前模型。
bool startSensitivityFitting(const nmSensitivityFitSettings& settings,
const std::function<QVector<QVector<double> >(const QVector<double>&)>& evaluate,
const std::function<bool()>& cancelled, bool verticalWell);
void stopFitting(); void stopFitting();
QVector<double> getBestSolution() const; QVector<double> getBestSolution() const;
double getBestFitness() const; double getBestFitness() const;
@ -162,6 +169,10 @@ private:
const QVector<QVector<double> >& result); const QVector<QVector<double> >& result);
private: private:
const nmSensitivityFitSettings* m_sensitivitySettings;
std::function<QVector<QVector<double> >(const QVector<double>&)> m_sensitivityEvaluate;
std::function<bool()> m_sensitivityCancelled;
bool m_sensitivityVerticalWell;
bool m_isRunning; bool m_isRunning;
bool m_shouldStop; bool m_shouldStop;
bool m_isFinalizing; bool m_isFinalizing;

@ -19,6 +19,7 @@ class nmDataAnalyzeManager;
class nmPebiResultSnapshot; class nmPebiResultSnapshot;
class vtkDoubleArray; class vtkDoubleArray;
struct nmPebiSolverInputSnapshot; struct nmPebiSolverInputSnapshot;
struct nmSensitivityFitSettings;
struct nmPebiManualCaptureState; struct nmPebiManualCaptureState;
struct nmPebiStaticPropertyValues; struct nmPebiStaticPropertyValues;
// DLL 类型只用于私有函数的引用参数,界面模块无需包含求解器内部头文件。 // DLL 类型只用于私有函数的引用参数,界面模块无需包含求解器内部头文件。
@ -122,9 +123,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
std::vector<SensitivityRunResult>& results, std::vector<SensitivityRunResult>& results,
QString* errorMessage = nullptr) const; QString* errorMessage = nullptr) const;
/** @brief 在源输入捕获线程创建独立批量任务,随后由 start() 在后台执行。 */ /** @brief 创建后台分析任务;传入拟合设置时对每组样本执行 LM 拟合。 */
nmCalculationDllPebiSolverTask* createSensitivityBatch( nmCalculationDllPebiSolverTask* createSensitivityBatch(
const nmDataSensitive& sampling, QString* errorMessage = nullptr) const; const nmDataSensitive& sampling, QString* errorMessage = nullptr,
const nmSensitivityFitSettings* improveSettings = nullptr) const;
/** @brief 线程结束后读取临时批量结果,引用在任务析构或重新运行后失效。 */ /** @brief 线程结束后读取临时批量结果,引用在任务析构或重新运行后失效。 */
const std::vector<SensitivityRunResult>& getSensitivityResults() const; const std::vector<SensitivityRunResult>& getSensitivityResults() const;
/** @brief 仅在启动前或线程结束后获取批次数据,供主线程保存及显示。 */ /** @brief 仅在启动前或线程结束后获取批次数据,供主线程保存及显示。 */
@ -146,6 +148,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
void calculateSingleRun(); void calculateSingleRun();
/** @brief 复用当前独立副本串行计算,保留进度、停止状态及每组失败原因。 */ /** @brief 复用当前独立副本串行计算,保留进度、停止状态及每组失败原因。 */
bool executeSensitivityBatch(); bool executeSensitivityBatch();
/** @brief 固定本组采样参数,在独立输入上拟合并生成最终曲线。 */
bool improveSensitivityRun();
/** @brief 执行当前唯一的 PEBI 求解模式。 */ /** @brief 执行当前唯一的 PEBI 求解模式。 */
bool execute(); bool execute();
/** @brief 成对释放构造时登记的 DataManager 后台使用权。 */ /** @brief 成对释放构造时登记的 DataManager 后台使用权。 */
@ -222,6 +226,7 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。 bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。
QAtomicInt m_nCancelRequested; ///< 跨线程协作式停止标记,1 表示请求停止。 QAtomicInt m_nCancelRequested; ///< 跨线程协作式停止标记,1 表示请求停止。
QAtomicInt m_nWasCancelled; ///< run() 已按停止请求丢弃本次局部结果。 QAtomicInt m_nWasCancelled; ///< run() 已按停止请求丢弃本次局部结果。
QSharedPointer<const nmSensitivityFitSettings> m_pSensitivityFitSettings; ///< 第四种批量拟合的确认设置。
QSharedPointer<const nmDataSensitive> m_pSensitivitySampling; ///< 批量启动前冻结的配置。 QSharedPointer<const nmDataSensitive> m_pSensitivitySampling; ///< 批量启动前冻结的配置。
QSharedPointer<nmDataSensitive::BatchResult> m_pSensitivityResult; ///< 后台独占的批次数据。 QSharedPointer<nmDataSensitive::BatchResult> m_pSensitivityResult; ///< 后台独占的批次数据。
QString m_sSensitivityFailureReason; ///< 整次批量无法继续执行的原因。 QString m_sSensitivityFailureReason; ///< 整次批量无法继续执行的原因。

@ -4,6 +4,7 @@
#include "nmData_global.h" #include "nmData_global.h"
#include "nmDataBase.h" #include "nmDataBase.h"
#include "nmDataAttribute.h" #include "nmDataAttribute.h"
#include <QVector>
class NM_DATA_EXPORT nmDataAutomaticFitting : public nmDataBase class NM_DATA_EXPORT nmDataAutomaticFitting : public nmDataBase
{ {
@ -127,4 +128,15 @@ private:
bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合 bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合
}; };
// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。
struct nmSensitivityFitSettings
{
nmSensitivityFitSettings() : confirmed(false) {}
bool confirmed;
nmDataAutomaticFitting parameters;
QString targetWell;
QString snapshotId;
QVector<double> initialValues;
};
#endif // NMDATAAUTOMATICFITTING_H #endif // NMDATAAUTOMATICFITTING_H

@ -29,17 +29,6 @@
#include "nmSubWxs_global.h" #include "nmSubWxs_global.h"
#include "nmPebiResultSnapshot.h" #include "nmPebiResultSnapshot.h"
// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。
struct nmSensitivityFitSettings
{
nmSensitivityFitSettings() : confirmed(false) {}
bool confirmed;
nmDataAutomaticFitting parameters;
QString targetWell;
QString snapshotId;
QVector<double> initialValues;
};
class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase
{ {
Q_OBJECT Q_OBJECT

@ -495,6 +495,8 @@ static QStringList traceParameterNames()
nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent)
: QObject(parent) : QObject(parent)
, m_sensitivitySettings(nullptr)
, m_sensitivityVerticalWell(false)
, m_isRunning(false) , m_isRunning(false)
, m_shouldStop(false) , m_shouldStop(false)
, m_isFinalizing(false) , m_isFinalizing(false)
@ -1079,8 +1081,10 @@ bool nmCalculationAutoFitLM::loadAllConfigFromDataManager()
void nmCalculationAutoFitLM::loadOptimizationConfig() void nmCalculationAutoFitLM::loadOptimizationConfig()
{ {
// LM 只读取迭代次数和目标误差,其他数值控制保留在现有算法实现中。 // LM 只读取迭代次数和目标误差,其他数值控制保留在现有算法实现中。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); // 敏感性运行只读取确认过的设置,普通拟合保留原来的配置来源。
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); nmDataAutomaticFitting fittingData = m_sensitivitySettings
? m_sensitivitySettings->parameters
: nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy();
m_maxIterations = fittingData.getIterationCount().getValue().toInt(); m_maxIterations = fittingData.getIterationCount().getValue().toInt();
m_targetError = fittingData.getErrorTolerance().getValue().toDouble(); m_targetError = fittingData.getErrorTolerance().getValue().toDouble();
@ -1096,8 +1100,10 @@ void nmCalculationAutoFitLM::loadParameterBounds()
// - m_parameterSelected[6]:完整参数体系中每个参数是否参与拟合; // - m_parameterSelected[6]:完整参数体系中每个参数是否参与拟合;
// - m_parameterLower/Upper[6]:完整参数体系的搜索上下界; // - m_parameterLower/Upper[6]:完整参数体系的搜索上下界;
// - m_enabledParamIndices:将勾选参数向量映射回完整参数索引。 // - m_enabledParamIndices:将勾选参数向量映射回完整参数索引。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); // 敏感性运行只读取确认过的设置,普通拟合保留原来的配置来源。
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); nmDataAutomaticFitting fittingData = m_sensitivitySettings
? m_sensitivitySettings->parameters
: nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy();
m_parameterSelected.resize(6); m_parameterSelected.resize(6);
m_parameterSelected[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[0] = fittingData.getPermeabilitySelected();
@ -1141,6 +1147,24 @@ void nmCalculationAutoFitLM::loadParameterBounds()
} }
// ==================== 自动拟合核心方法 ==================== // ==================== 自动拟合核心方法 ====================
bool nmCalculationAutoFitLM::startSensitivityFitting(
const nmSensitivityFitSettings& settings,
const std::function<QVector<QVector<double> >(const QVector<double>&)>& evaluate,
const std::function<bool()>& cancelled, bool verticalWell)
{
// 同步复用原算法;返回前释放回调,避免持有任务局部对象。
m_sensitivitySettings = &settings;
m_sensitivityEvaluate = evaluate;
m_sensitivityCancelled = cancelled;
m_sensitivityVerticalWell = verticalWell;
m_targetWellName = settings.targetWell;
const bool succeeded = startAutoFitting();
m_sensitivitySettings = nullptr;
m_sensitivityEvaluate = std::function<QVector<QVector<double> >(const QVector<double>&)>();
m_sensitivityCancelled = std::function<bool()>();
return succeeded;
}
bool nmCalculationAutoFitLM::startAutoFitting() bool nmCalculationAutoFitLM::startAutoFitting()
{ {
// 总入口只负责准备数据、调用有限差分 + LM/信赖域,并写回最终结果。 // 总入口只负责准备数据、调用有限差分 + LM/信赖域,并写回最终结果。
@ -1195,7 +1219,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
resetOptimizer(); resetOptimizer();
// 输入校验通过后,为本次拟合创建新的临时计算目录。 // 输入校验通过后,为本次拟合创建新的临时计算目录。
cleanupTemporaryDirectory(); cleanupTemporaryDirectory();
if(!initializeTemporaryDirectory()) { if(!m_sensitivitySettings && !initializeTemporaryDirectory()) {
m_lastError = tr("Cannot create the automatic fitting temporary directory"); m_lastError = tr("Cannot create the automatic fitting temporary directory");
emit logMessageGenerated(tr("ERROR: %1").arg(m_lastError)); emit logMessageGenerated(tr("ERROR: %1").arg(m_lastError));
return false; return false;
@ -1205,7 +1229,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
m_isFinalizing = false; m_isFinalizing = false;
m_currentIteration = -1; m_currentIteration = -1;
m_initialValues = savedInitialValues; m_initialValues = savedInitialValues;
initializeTraceFile(); if(!m_sensitivitySettings) initializeTraceFile();
// 开头只展示一次初始参数和总误差,不展示形状误差。 // 开头只展示一次初始参数和总误差,不展示形状误差。
emit logMessageGenerated(tr("========== LM fitting started ==========")); emit logMessageGenerated(tr("========== LM fitting started =========="));
emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName)); emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName));
@ -1317,7 +1341,14 @@ bool nmCalculationAutoFitLM::startAutoFitting()
return false; return false;
} }
// 只有最终完整求解实际执行并提交快照后才能置为成功。 // 敏感性任务自己用最优参数收尾,不能进入普通拟合的模型回写流程。
if(m_sensitivitySettings) {
m_isRunning = false;
return !m_shouldStop && !m_sensitivityCancelled() &&
!m_globalBestPosition.isEmpty() && m_globalBestFitness < 1.0e9;
}
// 普通拟合只有完整求解并提交快照后才算成功。
bool finalFullSolverSucceeded = false; bool finalFullSolverSucceeded = false;
bool finalFullSolverExecuted = false; bool finalFullSolverExecuted = false;
@ -1454,6 +1485,17 @@ bool nmCalculationAutoFitLM::startAutoFitting()
void nmCalculationAutoFitLM::extractUserInitialValues() void nmCalculationAutoFitLM::extractUserInitialValues()
{ {
// 按勾选顺序提取快照初值,并沿用窗口确认的上下限。
if(m_sensitivitySettings) {
m_initialValues.clear();
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
const int index = m_enabledParamIndices[i];
m_initialValues.append(qBound(m_parameterLower[index],
m_sensitivitySettings->initialValues[index], m_parameterUpper[index]));
}
return;
}
// 从当前项目模型读取用户已有初始参数。 // 从当前项目模型读取用户已有初始参数。
// 只提取用户勾选的参数,并按 m_enabledParamIndices 的顺序写入 m_initialValues。 // 只提取用户勾选的参数,并按 m_enabledParamIndices 的顺序写入 m_initialValues。
// 这些值用于初始解真实评价、LM 起点和最终精英保护。 // 这些值用于初始解真实评价、LM 起点和最终精英保护。
@ -1574,9 +1616,10 @@ bool nmCalculationAutoFitLM::evaluateTrustRegionPoint(
StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
{ {
// 本次拟合开始时按目标井的实际井型确定流程,普通直井跳过形状调整。 // 本次拟合开始时按目标井的实际井型确定流程,普通直井跳过形状调整。
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* dataManager = m_sensitivitySettings ? nullptr : nmDataAnalyzeManager::getCurrentInstance();
nmDataWellBase* targetWell = dataManager ? dataManager->findWellByName(m_targetWellName) : nullptr; nmDataWellBase* targetWell = dataManager ? dataManager->findWellByName(m_targetWellName) : nullptr;
const bool skipShapeStage = targetWell && targetWell->getWellType() == NM_WELL_MODEL::Vertical_Well; const bool skipShapeStage = m_sensitivitySettings ? m_sensitivityVerticalWell
: targetWell && targetWell->getWellType() == NM_WELL_MODEL::Vertical_Well;
const int dimensions = getEnabledParameterCount(); const int dimensions = getEnabledParameterCount();
if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) { if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) {
m_lastError = tr("No valid parameters are available for trust-region fitting"); m_lastError = tr("No valid parameters are available for trust-region fitting");
@ -1654,7 +1697,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
auto restoreEvaluationState = [&](const TrustRegionEvaluation& evaluation) { auto restoreEvaluationState = [&](const TrustRegionEvaluation& evaluation) {
// evaluateFitness() 会把试算参数写入 DataManager。无论候选是否接受, // evaluateFitness() 会把试算参数写入 DataManager。无论候选是否接受,
// 下一次计算前都恢复到唯一的已接受工作点,防止失败试算污染后续求解。 // 下一次计算前都恢复到唯一的已接受工作点,防止失败试算污染后续求解。
applyParametersToDataManager(evaluation.parameters); if(!m_sensitivitySettings) applyParametersToDataManager(evaluation.parameters);
m_lastObjectiveBreakdown = evaluation.breakdown; m_lastObjectiveBreakdown = evaluation.breakdown;
m_lastEvaluatedLogLogData = evaluation.curve; m_lastEvaluatedLogLogData = evaluation.curve;
}; };
@ -1673,6 +1716,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}; };
auto processPauseAndStop = [&]() -> bool { auto processPauseAndStop = [&]() -> bool {
if(m_sensitivitySettings) m_shouldStop = m_sensitivityCancelled();
QApplication::processEvents(); QApplication::processEvents();
return !m_shouldStop; return !m_shouldStop;
}; };
@ -2371,6 +2415,18 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector<double>& parameters
m_lastEvaluatedLogLogData.clear(); m_lastEvaluatedLogLogData.clear();
m_lastObjectiveBreakdown = AutoFitObjectiveBreakdownLM(); m_lastObjectiveBreakdown = AutoFitObjectiveBreakdownLM();
// 候选求解在冻结输入副本上执行,误差口径仍使用本算法现有实现。
if(m_sensitivitySettings) {
m_shouldStop = m_sensitivityCancelled();
if(m_shouldStop || !validateParameters(parameters)) return 1.0e10;
const QVector<QVector<double> > curve = m_sensitivityEvaluate(parameters);
m_shouldStop = m_sensitivityCancelled();
if(m_shouldStop || !validateLogLogData(curve)) return 1.0e10;
const double fitness = calculateLogLogCurveError(m_targetLogLogData, curve);
if(fitness < 1.0e9) m_lastEvaluatedLogLogData = curve;
return fitness;
}
try { try {
DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters") DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters")
.arg(funcName).arg(callCount).arg(parameters.size())); .arg(funcName).arg(callCount).arg(parameters.size()));

@ -22,6 +22,8 @@
#include "nmCalculationPebiGrid.h" #include "nmCalculationPebiGrid.h"
#include "nmCalculationUtils.h" #include "nmCalculationUtils.h"
#include "nmCalculationAutoFitLM.h"
#include "nmDataAutomaticFitting.h"
#include "nmDataAnalyzeManager.h" #include "nmDataAnalyzeManager.h"
#include "nmDataPvtParaForPebi.h" #include "nmDataPvtParaForPebi.h"
@ -50,7 +52,7 @@
#include <vtkUnstructuredGrid.h> #include <vtkUnstructuredGrid.h>
/** /**
* @brief PEBI 求解任务独占的完整值输入,敏感性任务仅更新采样标量。 * @brief PEBI 求解任务独占的完整值输入,敏感性采样和拟合只修改此副本。
*/ */
struct nmPebiSolverInputSnapshot struct nmPebiSolverInputSnapshot
{ {
@ -948,7 +950,8 @@ bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch(
} }
nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityBatch( nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityBatch(
const nmDataSensitive& sampling, QString* errorMessage) const const nmDataSensitive& sampling, QString* errorMessage,
const nmSensitivityFitSettings* improveSettings) const
{ {
if(errorMessage) errorMessage->clear(); if(errorMessage) errorMessage->clear();
try { try {
@ -975,6 +978,9 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
if(errorMessage) *errorMessage = creationError; if(errorMessage) *errorMessage = creationError;
return nullptr; return nullptr;
} }
// 第四种的拟合配置按值冻结,整批样本共用确认过的初值和范围。
if(improveSettings) task->m_pSensitivityFitSettings =
QSharedPointer<const nmSensitivityFitSettings>(new nmSensitivityFitSettings(*improveSettings));
task->m_pSensitivitySampling = QSharedPointer<const nmDataSensitive>( task->m_pSensitivitySampling = QSharedPointer<const nmDataSensitive>(
new nmDataSensitive(frozenSampling)); new nmDataSensitive(frozenSampling));
// 批次结果单独持有冻结配置与历史曲线,关闭参数窗口后仍可保存和显示。 // 批次结果单独持有冻结配置与历史曲线,关闭参数窗口后仍可保存和显示。
@ -1059,6 +1065,116 @@ bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength(
return false; return false;
} }
bool nmCalculationDllPebiSolverTask::improveSensitivityRun()
{
// 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。
const nmSensitivityFitSettings& settings = *m_pSensitivityFitSettings;
const nmDataAutomaticFitting& fit = settings.parameters;
nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput;
nmPebiStaticPropertyCalculationInput& properties = m_pInputSnapshot->m_oStaticPropertyInput;
int target = -1;
for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) {
if(grid.m_vecWellInputs[i].m_bRealWell &&
grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; }
}
if(target < 0) return fail(tr("Target well is unavailable in the solved model."));
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<std::vector<double>*> fractures;
std::vector<std::vector<double> > 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<std::vector<double> >& 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]);
}
const QString resultWellCode = m_sAutoFitTargetWellCode;
m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode;
auto evaluate = [&](const QVector<double>& parameters) -> QVector<QVector<double> > {
if(isCancelRequested()) return QVector<QVector<double> >();
QVector<double> 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<double>& 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;
}
}
if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4];
m_pInputSnapshot->m_bRequiresGridCalculation = !fractures.empty();
m_sFailureReason.clear();
calculateSingleRun();
return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >();
};
auto cancelled = [&]() -> bool { return isCancelRequested(); };
const bool verticalWell = grid.m_vecSolverWellOrder[target].m_eWellType == Vertical_Well;
// 敏感性改善仅使用 LM,直接复用其拟合流程。
nmCalculationAutoFitLM fitter;
fitter.setTargetLogLogData(grid.m_vecWellInputs[target].m_vecHistoryLogLog);
const bool succeeded = fitter.startSensitivityFitting(settings, evaluate, cancelled, verticalWell);
const QVector<double> best = fitter.getBestSolution();
const QString error = fitter.getLastError();
// 算法最后评价的候选不一定最优;恢复分析井后用最优参数求解并保留三条曲线。
m_sAutoFitTargetWellCode = resultWellCode;
if(isCancelRequested()) return false;
if(!succeeded) {
m_sFailureReason.clear();
return fail(error.isEmpty() ? tr("No valid fitting result was obtained.") : error);
}
evaluate(best);
return m_lastRunSucceeded;
}
bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
{ {
m_pSensitivityResult->m_runs.clear(); m_pSensitivityResult->m_runs.clear();
@ -1069,9 +1185,15 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
progressTimer.start(); progressTimer.start();
emit sigSensitivityProgress(0, count, 0); emit sigSensitivityProgress(0, count, 0);
try { try {
// 拟合还会改动其他井的参数、裂缝几何及网格,整批只保留一份初始输入供逐组恢复。
QScopedPointer<nmPebiSolverInputSnapshot> fitBaseline;
if(!m_pSensitivityFitSettings.isNull())
fitBaseline.reset(new nmPebiSolverInputSnapshot(*m_pInputSnapshot));
for(quint64 runIndex = 0; runIndex < count; ++runIndex) { for(quint64 runIndex = 0; runIndex < count; ++runIndex) {
if(isCancelRequested()) return false; if(isCancelRequested()) return false;
// 先还原输入,再由 updateSensitivityRun 填入本组采样值;停止标记不随输入重置。
if(!fitBaseline.isNull() && runIndex > 0) *m_pInputSnapshot = *fitBaseline;
SensitivityRunResult result; SensitivityRunResult result;
result.m_nRunIndex = runIndex; result.m_nRunIndex = runIndex;
// 确定性组合和 Monte-Carlo 样本均按预览顺序读取,后台不重新采样。 // 确定性组合和 Monte-Carlo 样本均按预览顺序读取,后台不重新采样。
@ -1084,8 +1206,12 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
&result.m_sFailureReason); &result.m_sFailureReason);
} }
if(inputReady) { if(inputReady) {
calculateSingleRun(); if(m_pSensitivityFitSettings.isNull()) {
result.m_bSucceeded = m_lastRunSucceeded; calculateSingleRun();
result.m_bSucceeded = m_lastRunSucceeded;
} else {
result.m_bSucceeded = improveSensitivityRun();
}
if(result.m_bSucceeded) { if(result.m_bSucceeded) {
result.m_vecPressure = m_autoFitResultPressure; result.m_vecPressure = m_autoFitResultPressure;
result.m_vecLogLog = m_autoFitResultLogLog; result.m_vecLogLog = m_autoFitResultLogLog;

@ -460,7 +460,8 @@ void nmWxAutomaticFitting::loadSensitivitySettings()
m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell));
automaticFittingData = m_sensitivitySettings->parameters; automaticFittingData = m_sensitivitySettings->parameters;
m_autoParameterRanges = false; m_autoParameterRanges = false;
m_algorithmCombo->setCurrentIndex(automaticFittingData.getAlgorithmName() == "LM" ? 1 : 0); // 敏感性设置恢复时也固定使用 LM。
m_algorithmCombo->setCurrentIndex(1);
m_iterationEdit->setText(QString::number(automaticFittingData.getIterationCount().getValue().toInt())); m_iterationEdit->setText(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
m_errorLimitEdit->setText(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble(), 'g', 10)); m_errorLimitEdit->setText(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble(), 'g', 10));
const int rows[] = {0, 1, 2, 3, 5, 6}; const int rows[] = {0, 1, 2, 3, 5, 6};
@ -711,7 +712,9 @@ void nmWxAutomaticFitting::setupControlPanel()
m_algorithmCombo = new QComboBox(); m_algorithmCombo = new QComboBox();
m_algorithmCombo->addItem(tr("PSO (Particle Swarm)")); m_algorithmCombo->addItem(tr("PSO (Particle Swarm)"));
m_algorithmCombo->addItem(tr("LM")); m_algorithmCombo->addItem(tr("LM"));
m_algorithmCombo->setCurrentIndex(0); // 从敏感性分析打开时仅使用 LM,普通自动拟合仍可选择算法。
m_algorithmCombo->setCurrentIndex(m_sensitivitySettings ? 1 : 0);
m_algorithmCombo->setEnabled(!m_sensitivitySettings);
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int))); connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
@ -1066,7 +1069,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble());
// 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。 // 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。
if(m_sensitivitySettings) { if(m_sensitivitySettings) {
automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO"); automaticFittingData.setAlgorithmName("LM");
return; return;
} }

@ -2330,12 +2330,6 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column)
void nmWxSensitive::onGenerateClicked() void nmWxSensitive::onGenerateClicked()
{ {
// 第四种的批量拟合尚未接入,不能误用普通 Monte-Carlo 生成结果。
if(calculationTypeCombo->currentIndex() == 3) {
QMessageBox::information(this, tr("Sensitivity"),
tr("Monte-Carlo + Improve calculation is not implemented yet."));
return;
}
if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return; if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return;
QString error; QString error;
QScopedPointer<nmCalculationDllPebiSolverTask> task; QScopedPointer<nmCalculationDllPebiSolverTask> task;
@ -2350,7 +2344,9 @@ void nmWxSensitive::onGenerateClicked()
} }
// 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。 // 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。
nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true); nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true);
task.reset(source.createSensitivityBatch(m_sensitiveData, &error)); // 第四种直接交给后台任务逐组拟合,仍使用主窗口原有进度框。
task.reset(source.createSensitivityBatch(m_sensitiveData, &error,
calculationTypeCombo->currentIndex() == 3 ? m_improveSettings.data() : nullptr));
} catch(const std::bad_alloc&) { } catch(const std::bad_alloc&) {
error = tr("Insufficient memory to prepare solver input."); error = tr("Insufficient memory to prepare solver input.");
} }

Loading…
Cancel
Save