diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h index 3255a4f3..e4b011f4 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h @@ -13,7 +13,7 @@ #include "nmCalculation_global.h" // 双对数曲线误差分解。预调整使用纵向偏差,形状阶段使用双曲线斜率残差, -// 整体阶段只使用 total 接受候选;两个 LM 目标均联合调整有效参数。 +// 总误差部分只使用 total 接受候选;两个 LM 目标均联合调整有效参数。 struct AutoFitObjectiveBreakdownLM { bool valid; double total; @@ -61,7 +61,6 @@ struct AutoFitObjectiveBreakdownLM { enum StopReasonLM { LM_CONTINUE_OPTIMIZATION = 0, LM_TARGET_ACHIEVED = 1, - LM_TRUE_CONVERGENCE = 2, LM_LOCAL_OPTIMUM = 3, LM_MAX_ITERATIONS = 4, LM_USER_STOPPED = 5, @@ -100,7 +99,7 @@ signals: QVector > bestData, int iteration, double fitness); - /** @brief 优化迭代结束,开始用当前最优参数生成正式结果。 */ + // 优化迭代结束,开始用当前最优参数生成正式结果。 void finalizingStarted(); void logMessageGenerated(const QString& message); @@ -130,10 +129,8 @@ private: void updateReservoirParameters(const QVector& parameters); void updateWellParameters(const QVector& parameters); - QVector > runSolver(); QVector > runSolverDll(); bool runFinalFullSolver(); - void saveOptimizationResult(); void validateAndProtectFinalResult(); QString getStopReasonDescription(StopReasonLM reason) const; int getEnabledParameterCount() const; @@ -177,12 +174,11 @@ private: AutoFitObjectiveBreakdownLM m_globalBestObjectiveBreakdown; QVector > m_lastEvaluatedLogLogData; QVector > m_globalBestLogLogData; - mutable AutoFitObjectiveBreakdownLM m_lastObjectiveBreakdown; + AutoFitObjectiveBreakdownLM m_lastObjectiveBreakdown; QVector > m_userInitialLogLogData; AutoFitObjectiveBreakdownLM m_userInitialObjectiveBreakdown; - // 参数索引:0 k,1 skin,2 wellboreC,3 phi,4 Swi, - // 5 Dfc,6 fractureHalfLength。 + // 参数索引:0 k,1 skin,2 wellboreC,3 phi,4 Dfc,5 fractureHalfLength。 QVector m_parameterSelected; QVector m_parameterLower; QVector m_parameterUpper; @@ -200,7 +196,6 @@ private: int m_successfulEvaluations; volatile int m_evaluationInProgress; - int m_consecutiveFailures; int m_maxConsecutiveFailures; QVector m_userInitialSolution; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 384c1dca..4629cb79 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -9,7 +9,6 @@ #include "nmDataAutomaticFitting.h" #include -#include #include #include #include @@ -26,7 +25,6 @@ #define DEBUG_OUT(msg) OutputDebugStringA(QString("[AutoFitLM] %1\n").arg(msg).toLocal8Bit().data()) #endif -static const bool kAutoFitDiagnosticTraceEnabled = true; static const int kAutoFitIntervalsPerDecade = 20; static int autoFitShapeLag(int pointCount) @@ -137,10 +135,10 @@ static QString jsonStringArray(const QStringList& values) // 信赖域搜索统一在 [0, 1] 内部坐标工作。正值参数使用对数坐标,使内部相同步长 // 表示近似相同的相对变化,避免 k、C、Dfc 等跨数量级参数被线性尺度支配; -// skin 可为负数、Swi 的物理意义是线性比例,因此二者保持有界线性坐标。 +// 表皮可为负数,使用线性坐标。 static bool useTrustRegionLogScale(int parameterIndex, double lower, double upper) { - return parameterIndex != 1 && parameterIndex != 4 && + return parameterIndex != 1 && lower > 0.0 && upper > lower; } @@ -150,7 +148,7 @@ static double toTrustRegionCoordinate(double value, double upper) { // 所有进入优化器的物理值先投影到用户上下界,再转换成无量纲坐标。 - // 这样有限差分步长、信赖半径和参数间相关性可以在统一尺度上比较。 + // 有限差分步长和信赖半径因此不受参数物理单位的直接影响。 value = qMax(lower, qMin(upper, value)); if(useTrustRegionLogScale(parameterIndex, lower, upper)) { @@ -262,7 +260,7 @@ static double trustRegionDotProduct(const QVector& left, return sum; } -// 求解选中参数对应的阻尼正规方程。参数最多七维,使用带部分主元的 +// 求解选中参数对应的阻尼正规方程。当前最多六个拟合参数,使用带部分主元的 // 高斯消元处理该小矩阵,并在主元退化时明确返回失败。 static bool solveTrustRegionLinearSystem( QVector > matrix, @@ -490,7 +488,6 @@ static QStringList traceParameterNames() << "skin" << "wellboreC" << "phi" - << "Swi" << "Dfc" << "fractureHalfLength"; return names; @@ -511,7 +508,6 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) , m_solverCallCount(0) , m_successfulEvaluations(0) , m_evaluationInProgress(0) - , m_consecutiveFailures(0) , m_userInitialFitness(1e10) , m_maxConsecutiveFailures(3) , m_hasValidUserSolution(false) @@ -524,9 +520,8 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) DEBUG_OUT("LM automatic fitting calculator initialized"); } -// 析构函数:停止仍在进行的拟合、关闭 trace 文件并清理临时目录。 -// 自动拟合可能在 UI 线程中被窗口关闭打断,因此析构时要尽量温和地等待当前评价结束; -// 如果等待超时,再强制清除运行标志,避免对象销毁后还有信号回调访问成员变量。 +// 请求停止并限时等待,然后关闭轨迹文件、清理临时目录和信号连接。 +// 等待超时后仅清除运行标志,不代表外层同步调用已经退出。 nmCalculationAutoFitLM::~nmCalculationAutoFitLM() { if(m_isRunning) { @@ -548,8 +543,7 @@ nmCalculationAutoFitLM::~nmCalculationAutoFitLM() disconnect(this, nullptr, nullptr, nullptr); } -// ===== 临时目录工具 ===== -// +// ==================== 临时目录工具 ==================== // 真实求解器和井曲线 CSV 共用一个受控的单次运行目录。创建失败时不允许 // 回退到程序目录,否则后续递归清理可能删除安装文件。 bool nmCalculationAutoFitLM::initializeTemporaryDirectory() @@ -665,7 +659,7 @@ QVector nmCalculationAutoFitLM::getBestSolution() const double nmCalculationAutoFitLM::getBestFitness() const { - // 当前全局最优真实误差。越小越好,1e10 附近通常表示尚无有效解。 + // 当前已接受工作点的总误差;高度和形状阶段不保证总误差单调下降。 return m_globalBestFitness; } @@ -724,9 +718,6 @@ void nmCalculationAutoFitLM::initializeTraceFile() closeTraceFile(); m_traceFilePath.clear(); m_traceMetaFilePath.clear(); - if(!kAutoFitDiagnosticTraceEnabled) { - return; - } // 诊断轨迹在所有构建中启用,并与源码、安装目录完全分离。 if(m_traceRunId.isEmpty()) { @@ -788,7 +779,6 @@ void nmCalculationAutoFitLM::writeTraceHeader() << "skin" << "wellboreC" << "phi" - << "Swi" << "Dfc" << "fractureHalfLength" << "solver_objective" @@ -848,7 +838,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile() QTextStream out(&metaFile); out << "{\n"; - out << " \"schema_version\": 55,\n"; + out << " \"schema_version\": 56,\n"; out << " \"strategy\": \"permeability_height_then_joint_lm_total_for_vertical_well_else_shape_then_total\",\n"; out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n"; out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n"; @@ -930,7 +920,7 @@ void nmCalculationAutoFitLM::writeTraceRow( << QString::number(iteration) << QString::number(parameterIndex) << csvEscape(phase); - for(int i = 0; i < 7; ++i) { + for(int i = 0; i < 6; ++i) { cols << traceParamAt(fullParams, i); } cols << traceNumber(solverObjective) @@ -968,7 +958,7 @@ void nmCalculationAutoFitLM::writeTraceRow( m_traceFile.flush(); } -// 窗口统一显示普通小数;失败评价不展示惩罚值或非有限数。 +// 拟合界面统一显示普通小数;失败评价不展示惩罚值或非有限数。 static QString lmLogError(double value) { return isFiniteNumber(value) && value < 1.0e9 @@ -985,7 +975,7 @@ static QString lmLogImprovement(double beforeError, double afterError) void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReason) { - // 最终只汇总总误差,耗时由进程窗口在收到结束信号后追加。 + // 最终只汇总总误差,耗时由进度窗口在收到结束信号后追加。 emit logMessageGenerated(success ? tr("[Fitting completed]") : tr("[Fitting ended]")); emit logMessageGenerated(tr("Stop reason: %1").arg(success ? getStopReasonDescription(finalReason) @@ -1018,10 +1008,10 @@ void nmCalculationAutoFitLM::emitTrialLog(const QString& title, QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector& selectedParameters) const { - // 将 LM 内部使用的“启用参数向量”还原成完整 7 维参数向量。 + // 将 LM 内部使用的“启用参数向量”还原成完整 6 维参数向量。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 // trace CSV 和 meta 使用该完整向量记录一次候选评价。 - QVector fullParams(7, 0.0); + QVector fullParams(6, 0.0); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1029,7 +1019,6 @@ QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector< nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); fullParams[0] = reservoirData.getPermeability().getValue().toDouble(); fullParams[3] = reservoirData.getPorosity().getValue().toDouble(); - fullParams[4] = reservoirData.getSwi().getValue().toDouble(); nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); @@ -1045,15 +1034,15 @@ QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector< nmDataVerticalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); if(fracturedWell) { - fullParams[5] = fracturedWell->getDfc().getValue().toDouble(); - fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble(); + fullParams[4] = fracturedWell->getDfc().getValue().toDouble(); + fullParams[5] = fracturedWell->getFractureHalfLength().getValue().toDouble(); } } else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); if(fracturedWell) { - fullParams[5] = fracturedWell->getDfc().getValue().toDouble(); - fullParams[6] = fracturedWell->getFractureHalfLength().getValue().toDouble(); + fullParams[4] = fracturedWell->getDfc().getValue().toDouble(); + fullParams[5] = fracturedWell->getFractureHalfLength().getValue().toDouble(); } } } @@ -1079,7 +1068,7 @@ bool nmCalculationAutoFitLM::loadAllConfigFromDataManager() try { loadOptimizationConfig(); loadParameterBounds(); - extractUserInitialValues(); // 直接提取初始值,无需条件判断 + extractUserInitialValues(); return true; } catch(...) { m_lastError = tr("Failed to load configuration from data manager"); @@ -1104,25 +1093,22 @@ void nmCalculationAutoFitLM::loadParameterBounds() // 读取用户勾选的拟合参数及上下界。 // // 这里构建三个核心数组: - // - m_parameterSelected[7]:完整参数体系中每个参数是否参与拟合; - // - m_parameterLower/Upper[7]:完整参数体系的搜索上下界; - // - m_enabledParamIndices:把粒子内部紧凑向量映射回完整参数索引。 + // - m_parameterSelected[6]:完整参数体系中每个参数是否参与拟合; + // - m_parameterLower/Upper[6]:完整参数体系的搜索上下界; + // - m_enabledParamIndices:将勾选参数向量映射回完整参数索引。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); - // 获取参数选择状态 - m_parameterSelected.resize(7); + m_parameterSelected.resize(6); m_parameterSelected[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[1] = fittingData.getSkinSelected(); m_parameterSelected[2] = fittingData.getWellboreStorageSelected(); m_parameterSelected[3] = fittingData.getPorositySelected(); - m_parameterSelected[4] = false; // 含水饱和度固定,保留索引以兼容物理参数和追踪记录。 - m_parameterSelected[5] = fittingData.getFractureConductivitySelected(); - m_parameterSelected[6] = fittingData.getFractureHalfLengthSelected(); + m_parameterSelected[4] = fittingData.getFractureConductivitySelected(); + m_parameterSelected[5] = fittingData.getFractureHalfLengthSelected(); - // 获取参数边界 - m_parameterLower.resize(7); - m_parameterUpper.resize(7); + m_parameterLower.resize(6); + m_parameterUpper.resize(6); m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble(); m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble(); @@ -1136,16 +1122,12 @@ void nmCalculationAutoFitLM::loadParameterBounds() m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble(); m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble(); - m_parameterLower[4] = dataManager->getReservoirDataCopy().getSwi().getValue().toDouble(); - m_parameterUpper[4] = m_parameterLower[4]; + m_parameterLower[4] = fittingData.getFractureConductivityMin().getValue().toDouble(); + m_parameterUpper[4] = fittingData.getFractureConductivityMax().getValue().toDouble(); - m_parameterLower[5] = fittingData.getFractureConductivityMin().getValue().toDouble(); - m_parameterUpper[5] = fittingData.getFractureConductivityMax().getValue().toDouble(); + m_parameterLower[5] = fittingData.getFractureHalfLengthMin().getValue().toDouble(); + m_parameterUpper[5] = fittingData.getFractureHalfLengthMax().getValue().toDouble(); - m_parameterLower[6] = fittingData.getFractureHalfLengthMin().getValue().toDouble(); - m_parameterUpper[6] = fittingData.getFractureHalfLengthMax().getValue().toDouble(); - - // 更新启用参数索引 m_enabledParamIndices.clear(); for(int i = 0; i < m_parameterSelected.size(); ++i) { @@ -1222,7 +1204,6 @@ bool nmCalculationAutoFitLM::startAutoFitting() m_shouldStop = false; m_isFinalizing = false; m_currentIteration = -1; - m_consecutiveFailures = 0; m_initialValues = savedInitialValues; initializeTraceFile(); // 开头只展示一次初始参数和总误差,不展示形状误差。 @@ -1237,9 +1218,9 @@ bool nmCalculationAutoFitLM::startAutoFitting() DEBUG_OUT(tr("=== Evaluating Initial Solution (Elite Protection) ===")); const char* names[] = { QT_TR_NOOP("Permeability"), QT_TR_NOOP("Skin"), - QT_TR_NOOP("Wellbore storage"), QT_TR_NOOP("Porosity"), "", + QT_TR_NOOP("Wellbore storage"), QT_TR_NOOP("Porosity"), QT_TR_NOOP("Fracture conductivity"), QT_TR_NOOP("Fracture half length") }; - const char* units[] = { "mD", "", "m^3/MPa", "", "", "mD.m", "m" }; + const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" }; emit logMessageGenerated(tr("[Initial parameters]")); for(int i = 0; i < m_userInitialSolution.size(); ++i) { const int index = m_enabledParamIndices[i]; @@ -1350,8 +1331,8 @@ bool nmCalculationAutoFitLM::startAutoFitting() // 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。 const bool fractureGridParameterSelected = - (m_parameterSelected.size() > 5 && m_parameterSelected[5]) || - (m_parameterSelected.size() > 6 && m_parameterSelected[6]); + (m_parameterSelected.size() > 4 && m_parameterSelected[4]) || + (m_parameterSelected.size() > 5 && m_parameterSelected[5]); if(fractureGridParameterSelected) { nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1376,7 +1357,6 @@ bool nmCalculationAutoFitLM::startAutoFitting() tr("ERROR: Final full-field calculation failed")); } - saveOptimizationResult(); DEBUG_OUT(tr("=== Optimization Results ===")); DEBUG_OUT( tr("Final error: %1").arg(m_globalBestFitness, 0, 'e', 4)); @@ -1424,13 +1404,6 @@ bool nmCalculationAutoFitLM::startAutoFitting() .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); DEBUG_OUT(tr("=== LM AUTOMATIC FITTING SUCCESSFUL ===")); - } else if(finalReason == LM_TRUE_CONVERGENCE) { - success = true; - message = QString( - tr("Automatic fitting converged to a stable solution. Best error: %1, Iterations: %2")) - .arg(m_globalBestFitness, 0, 'e', 4) - .arg(m_currentIteration + 1); - DEBUG_OUT(tr("=== LM AUTOMATIC FITTING CONVERGED ===")); } else if(finalReason == LM_LOCAL_OPTIMUM) { success = true; message = QString( @@ -1486,7 +1459,6 @@ void nmCalculationAutoFitLM::extractUserInitialValues() // 这些值用于初始解真实评价、LM 起点和最终精英保护。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - //QVector wells = dataManager->getWellDataList(); nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); @@ -1520,11 +1492,7 @@ void nmCalculationAutoFitLM::extractUserInitialValues() initialValue = reservoirData.getPorosity().getValue().toDouble(); break; - case 4: // 初始含水饱和度 - initialValue = reservoirData.getSwi().getValue().toDouble(); - break; - - case 5: // 裂缝导流能力 + case 4: // 裂缝导流能力 if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { nmDataVerticalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); @@ -1540,7 +1508,7 @@ void nmCalculationAutoFitLM::extractUserInitialValues() } break; - case 6: // 裂缝半长 + case 5: // 裂缝半长 if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { nmDataVerticalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); @@ -1566,7 +1534,6 @@ void nmCalculationAutoFitLM::extractUserInitialValues() DEBUG_OUT(QString(" Initial[%1] = %2").arg(i).arg(m_initialValues[i], 0, 'e', 3)); } - // 验证初始值 if(!validateInitialValues()) { DEBUG_OUT("Warning: Some initial values are outside parameter bounds"); } @@ -1616,15 +1583,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return LM_OPTIMIZATION_FAILED; } - // 高度、形状、总误差共用迭代上限;灵敏度求解仍使用原来的各段评价额度。 + // 高度、形状、总误差共用迭代上限;两个 LM 目标分别设置求解评价额度。 const int phaseEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3)); int maximumEvaluations = 0; int phaseIterations = 0; int shapeIterations = 0; int totalIterations = 0; int completedIterations = 0; - // 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径 - // 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。 + // 步长使用归一化内部坐标:0.04 表示内部范围的 4%,并非物理值变化 4%。 + // 信赖半径限制所有参数一次联合移动的二范数。 const double sensitivityStep = 0.04; const double minimumCoordinateStep = 1.0e-5; const double minimumTrustRadius = 2.0e-3; @@ -1771,7 +1738,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(permeabilityColumn >= 0) { emit fittingStageChanged(1); emit logMessageGenerated(tr("[Stage 1: Height alignment]")); - // 高度调整也占用总迭代额度,仍保留原来的对齐、停滞和可行步长判断。 + // 高度调整计入总迭代数;对齐、停滞或无可行步长时结束。 while(completedIterations < m_maxIterations && processPauseAndStop()) { const double bias = current.breakdown.verticalCommonBias; if(!current.breakdown.verticalReliable || qAbs(bias) <= 0.1 || heightStagnation >= 2) break; @@ -1852,7 +1819,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return point.valid && stageError(point) < stageError(base); }; auto acceptPoint = [&](const TrustRegionEvaluation& point) { - // 参数、曲线和诊断同步发布,两个目标仅改变候选验收指标。 + // 同步发布当前目标下已接受的参数、曲线和诊断。 current = point; publishAcceptedPoint(current); restoreEvaluationState(current); @@ -1877,7 +1844,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return true; }; - // 两个目标均沿用原 LM 的有效改善门槛及重建后停滞确认。 + // 两个 LM 目标共用有效改善门槛和重建后的停滞确认规则。 auto recordIneffectiveStep = [&]() -> bool { ++consecutiveIneffectiveSteps; if(consecutiveIneffectiveSteps < maximumIneffectiveSteps) { @@ -1909,7 +1876,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() // 在同一个真实工作点逐参数做单边差分。失败先在原方向缩步,再反向尝试; // 正常情况下每列仍只需一次真实求解,重试也计入总评价预算。 auto rebuildSensitivity = [&]() -> bool { - // 形状段也受原 LM 求解额度约束,差分范围及重试规则与总误差段相同。 + // 灵敏度试算计入当前目标的评价额度,形状和总误差使用相同差分规则。 const int evaluationLimit = maximumEvaluations; const TrustRegionEvaluation base = current; const QVector baseResidual = trustRegionFullResidual(base.breakdown); @@ -1925,7 +1892,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() TrustRegionEvaluation bestProbe; int bestProbeColumn = -1; double bestProbeDelta = 0.0; - // 两段共用原 LM 的局部差分尺度,避免目标切换同时改变灵敏度算法。 + // 两个目标共用局部差分尺度,信赖半径缩小时同步减小扰动。 const double finiteDifferenceStep = qMin( sensitivityStep, qMax(5.0e-3, trustRadius * 0.5)); @@ -1939,7 +1906,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() const double lower = m_parameterLower[parameterIndex]; const double upper = m_parameterUpper[parameterIndex]; if(upper <= lower) continue; - // 表皮包含零和负值,沿用原 LM 按局部物理尺度扰动的方式。 + // 表皮包含零和负值,按当前物理值的局部尺度确定扰动。 double localStep = finiteDifferenceStep; if(parameterIndex == 1) { localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower)); @@ -2019,7 +1986,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() jacobianColumnValid[column] = true; columnBuilt = true; - // 沿用原 LM 的缓存探针验收,仅按当前段的目标选择更优探针。 + // 缓存当前目标下更优的探针,全部灵敏度列建完后再接受。 if(acceptable(probe, base) && (!bestProbe.valid || stageError(probe) < stageError(bestProbe))) { bestProbe = probe; @@ -2037,14 +2004,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() ++validColumnCount; } } - // 两段都要求至少一列有效灵敏度,零梯度仍交给原 LM 停滞判断。 + // 至少需要一列有效灵敏度;零梯度由后续可行方向和停滞判断处理。 if(validColumnCount == 0 || m_shouldStop) { restoreEvaluationState(base); return false; } - // 两段均保留原 LM 接受更优缓存探针的行为。 - // 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。 + // 所有列基于同一工作点建立,再接受更优探针并用割线更新 Jacobian。 if(bestProbe.valid && bestProbeColumn >= 0) { QVector acceptedStep(dimensions, 0.0); acceptedStep[bestProbeColumn] = bestProbeDelta; @@ -2172,7 +2138,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() selectedColumns.clear(); } - // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 + // 没有可行下降步时收缩半径,按当前停滞状态重建灵敏度或结束。 if(selectedColumns.isEmpty()) { emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(completedIterations)); if(trustRadius <= minimumTrustRadius * 1.01 && @@ -2296,7 +2262,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() ++acceptedSinceRebuild; consecutiveRejectedSteps = 0; - // 两段共用原 LM 阻尼与信赖半径更新,预测比不替代目标误差验收。 + // 预测比只控制阻尼和信赖半径,接受候选仍以当前目标误差下降为准。 if(reductionRatio > 0.75) { damping = qMax(1.0e-8, damping * 0.5); if(stepNorm >= trustRadius * 0.8) { @@ -2317,8 +2283,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } modelRebuiltAtMinimumRadius = false; } else { - // 拒绝时参数恢复到 current;沿用原 LM 的阻尼和半径收缩规则, - // 模型是否重建仍由预测质量判断,不因更换目标而改变。 + // 拒绝后恢复已接受参数、增大阻尼并缩小半径;按预测质量判断是否重建。 ++consecutiveRejectedSteps; damping = qMin(1.0e8, damping * 4.0); trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); @@ -2399,14 +2364,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters, bool retrySolver) { - // LM 候选评价函数,也是自动拟合最核心的闭环: - // 1. 校验候选参数是否在用户设置的上下界和基本物理范围内; - // 2. 将参数写入 DataManager 的储层/目标井对象; - // 3. 调用真实数值求解器,生成模拟结果; - // 4. 从本次求解任务读取目标井 result log-log 曲线; - // 5. 与目标 history log-log 曲线计算误差,误差越小代表拟合越好。 - // - // 返回 1e10 表示候选评价失败或结果不可用。 + // 校验并应用候选参数,调用求解器后比较目标井曲线;失败返回惩罚值 1e10。 const QString funcName = QString("evaluateError[%1]").arg(m_currentIteration); static int callCount = 0; callCount++; @@ -2417,7 +2375,6 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters") .arg(funcName).arg(callCount).arg(parameters.size())); - // 打印参数值 QString paramStr = "Parameters: "; for(int i = 0; i < parameters.size(); ++i) { @@ -2426,12 +2383,10 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString("%1: %2").arg(funcName).arg(paramStr)); - // 1. 参数有效性检查。这里先拦截明显非法的候选, - // 避免把非有限数、越界值或极端危险值传给求解器。 + // 拦截维度不匹配、非有限数或超出用户范围的参数。 if(!validateParameters(parameters)) { DEBUG_OUT(QString("%1: Call #%2 - VALIDATION FAILED").arg(funcName).arg(callCount)); - // 详细检查每个参数 for(int i = 0; i < parameters.size(); ++i) { if(!isFiniteNumber(parameters[i])) { DEBUG_OUT(QString(" -> Param[%1] is NOT finite: %2").arg(i).arg(parameters[i])); @@ -2453,31 +2408,6 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString(" -> Param[%1]=%2 > upper bound %3") .arg(i).arg(parameters[i]).arg(upper)); } - - // 检查危险值。这些条件不是严格物理模型定义, - // 而是工程保护:避免求解器在明显异常输入下崩溃或返回无意义曲线。 - switch(paramIndex) { - case 0: // 渗透率 - if(parameters[i] <= 1e-6) { - DEBUG_OUT(QString(" -> REJECTED: Permeability too small: %1").arg(parameters[i])); - } - - break; - - case 2: // 井筒储集系数 - if(parameters[i] <= 1e-8) { - DEBUG_OUT(QString(" -> REJECTED: Wellbore storage too small: %1").arg(parameters[i])); - } - - break; - - case 3: // 孔隙度 - if(parameters[i] <= 1e-4 || parameters[i] >= 0.95) { - DEBUG_OUT(QString(" -> REJECTED: Unrealistic porosity: %1").arg(parameters[i])); - } - - break; - } } } } @@ -2487,7 +2417,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString("%1: Call #%2 - Parameters validated OK").arg(funcName).arg(callCount)); - // 2. 数据管理器检查。后续参数写回和求解器组装都依赖当前 DataManager。 + // 参数写回和求解任务依赖当前数据管理器。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); if(!dataManager) { @@ -2497,8 +2427,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString("%1: Call #%2 - DataManager OK").arg(funcName).arg(callCount)); - // 3. 应用参数。parameters 的顺序与 m_enabledParamIndices 对齐, - // applyParametersToDataManager() 会把它们拆分写入储层参数和目标井参数。 + // 按勾选顺序将候选值写入储层和目标井。 try { DEBUG_OUT(QString("%1: Call #%2 - Applying parameters...").arg(funcName).arg(callCount)); applyParametersToDataManager(parameters); @@ -2511,14 +2440,13 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters // Dfc 和裂缝半长属于网格输入。标记网格失效,使下一次任务基于当前参数快照重建。 const bool fractureGridParameterSelected = - (m_parameterSelected.size() > 5 && m_parameterSelected[5]) || - (m_parameterSelected.size() > 6 && m_parameterSelected[6]); + (m_parameterSelected.size() > 4 && m_parameterSelected[4]) || + (m_parameterSelected.size() > 5 && m_parameterSelected[5]); if(fractureGridParameterSelected) { dataManager->invalidatePebiGrid(); } - // 4. 运行求解器。真实求解器偶发失败时允许重试,避免一次 DLL 调用异常 - // 直接让初始评价失败;迭代候选不重复相同参数,交给信赖域缩步。 + // 初始评价失败可重试;LM 候选失败则交给信赖域缩步,不重复相同参数。 QVector> solverResult; const int maxRetries = retrySolver ? 2 : 0; bool solverSuccess = false; @@ -2538,9 +2466,8 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters msleep(1000); } - solverResult = runSolver(); + solverResult = runSolverDll(); - // 详细检查求解器结果 if(solverResult.isEmpty()) { DEBUG_OUT(QString("%1: Call #%2 - Solver returned EMPTY result").arg(funcName).arg(callCount)); } else { @@ -2573,8 +2500,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters return 1e10; } - // 5. 获取 LogLog 数据。runSolverDll() 直接从求解任务复制目标井曲线, - // 不再依赖 DataManager 中可能被其它井或上一粒子改写的共享结果。 + // 使用求解任务复制的目标井曲线,避免读取其他井或上一候选的共享结果。 QVector> resultLogLogData = m_lastEvaluatedLogLogData; try { @@ -2582,30 +2508,6 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters DEBUG_OUT(QString("%1: Call #%2 - LogLog data VALIDATION FAILED") .arg(funcName).arg(callCount)); - // 详细输出LogLog数据问题 - if(resultLogLogData.size() < 3) { - DEBUG_OUT(QString(" -> LogLog arrays count: %1 (need 3)") - .arg(resultLogLogData.size())); - } else { - DEBUG_OUT(QString(" -> LogLog array sizes: X=%1, Y1=%2, Y2=%3") - .arg(resultLogLogData[0].size()) - .arg(resultLogLogData[1].size()) - .arg(resultLogLogData[2].size())); - - // 检查数据有效性 - for(int i = 0; i < qMin(5, resultLogLogData[0].size()); ++i) { - if(!isFiniteNumber(resultLogLogData[0][i]) || - !isFiniteNumber(resultLogLogData[1][i]) || - !isFiniteNumber(resultLogLogData[2][i])) { - DEBUG_OUT(QString(" -> Invalid data at index %1: X=%2, Y1=%3, Y2=%4") - .arg(i) - .arg(resultLogLogData[0][i]) - .arg(resultLogLogData[1][i]) - .arg(resultLogLogData[2][i])); - } - } - } - return 1e10; } @@ -2618,7 +2520,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters return 1e10; } - // 6. 计算误差。这里比较的是目标井 history log-log 与当前模拟 result log-log。 + // 比较目标井的实测与模拟双对数曲线。 double error; try { @@ -2661,9 +2563,7 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters void nmCalculationAutoFitLM::applyParametersToDataManager(const QVector& parameters) { - // 将粒子的“启用参数向量”写回项目数据。 - // parameters 的维度必须等于用户勾选的参数数量,顺序由 m_enabledParamIndices 决定。 - // 这里不直接跑求解器,只负责把 DataManager 调整到该粒子对应的模型状态。 + // 按 m_enabledParamIndices 对应的顺序,将勾选参数写入储层和目标井。 if(parameters.size() != getEnabledParameterCount()) { return; } @@ -2679,7 +2579,7 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector& pa nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - // paramIndex 是粒子 position 中的索引;i 是完整 7 个参数体系中的索引。 + // paramIndex 是勾选参数向量中的索引;i 是完整 6 个参数体系中的索引。 // 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。 int paramIndex = 0; @@ -2702,7 +2602,6 @@ void nmCalculationAutoFitLM::updateReservoirParameters(const QVector& pa } } - // 更新数据管理器 dataManager->updateReservoirData(reservoirData); } @@ -2712,11 +2611,10 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector& paramet // - skin:写入第一个 perforation; // - wellboreC:写入井筒储集系数; // - Dfc/裂缝半长:只写入垂直压裂井或多段压裂水平井。 - // 如果目标井不存在或没有射孔数据,这里只记录 debug,不抛异常。 + // 目标井不存在时返回;没有射孔数据时仅跳过表皮更新。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); // 只更新当前目标井的井参数,避免多井项目中误改其他井。 - //QVector wells = dataManager->getWellDataList(); nmDataWellBase* pWell = dataManager->findWellByName(m_targetWellName); if(!pWell) return; @@ -2771,7 +2669,7 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector& paramet } break; - case 5: { // 裂缝导流能力 + case 4: { // 裂缝导流能力 if(pVerticalFracturedWell || pHorizontalFracturedWell) { dfcAttr.setValue(value); updateDfc = true; @@ -2779,7 +2677,7 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector& paramet } break; - case 6: { // 裂缝半长 + case 5: { // 裂缝半长 if(pVerticalFracturedWell || pHorizontalFracturedWell) { fractureHalfLengthAttr.setValue(value); updateFractureHalfLength = true; @@ -2817,27 +2715,8 @@ void nmCalculationAutoFitLM::updateWellParameters(const QVector& paramet } } -// ==================== 求解器相关方法 ==================== - -QVector> nmCalculationAutoFitLM::runSolver() -{ - // 真实求解器统一走 DLL 方式,返回值由 evaluateFitness() 继续校验。 - return runSolverDll(); -} - -// ==================== 数据处理方法 ==================== // ==================== 算法辅助方法 ==================== -void nmCalculationAutoFitLM::saveOptimizationResult() -{ - // 当前函数只做日志记录。真正把最优参数写回项目数据的是 - // startAutoFitting() 结束阶段的 applyParametersToDataManager(m_globalBestPosition)。 - DEBUG_OUT(QString("Optimization result: error=%1, evaluations=%2/%3") - .arg(m_globalBestFitness, 0, 'e', 4) - .arg(m_successfulEvaluations) - .arg(m_totalEvaluations)); -} - void nmCalculationAutoFitLM::validateAndProtectFinalResult() { // 最终精英保护只阻止无效结果或真正变差的结果。任何真实误差下降都应保留, @@ -2849,7 +2728,6 @@ void nmCalculationAutoFitLM::validateAndProtectFinalResult() DEBUG_OUT(tr("=== Final Result Validation (Elite Protection) ===")); - // 使用已有的评估结果 double finalFitness = m_globalBestFitness; double initialFitness = m_userInitialFitness; @@ -2893,7 +2771,7 @@ void nmCalculationAutoFitLM::validateAndProtectFinalResult() int nmCalculationAutoFitLM::getEnabledParameterCount() const { - // 返回粒子维度,即用户勾选参与拟合的参数数量。 + // 返回用户勾选参与拟合的参数数量。 int count = 0; for(int i = 0; i < m_parameterSelected.size(); ++i) { @@ -2918,7 +2796,6 @@ bool nmCalculationAutoFitLM::validateParameters(const QVector& parameter return false; } - // 检查参数范围 if(i < m_enabledParamIndices.size()) { int paramIndex = m_enabledParamIndices[i]; @@ -2938,13 +2815,12 @@ bool nmCalculationAutoFitLM::validateLogLogData(const QVector>& { // 校验双对数曲线结构。约定: // logLogData[0]=time,logLogData[1]=pressure,logLogData[2]=pressure derivative。 - // 三列必须长度一致,且至少有足够点数用于插值和误差计算。 + // 三列必须长度一致、至少包含五个点,且所有数值有限。 if(logLogData.size() < 3) { DEBUG_OUT("LogLog data has less than 3 arrays"); return false; } - // 检查数组大小一致性 int size = logLogData[0].size(); if(size == 0) { @@ -2960,13 +2836,11 @@ bool nmCalculationAutoFitLM::validateLogLogData(const QVector>& return false; } - // 检查最小数据点数 if(size < 5) { DEBUG_OUT(QString("Too few LogLog data points: %1").arg(size)); return false; } - // 数据有效性检查 for(int i = 0; i < size; ++i) { if(!isFiniteNumber(logLogData[0][i]) || !isFiniteNumber(logLogData[1][i]) || @@ -3034,13 +2908,11 @@ bool nmCalculationAutoFitLM::validateSolverResult(const QVector> return false; } - // 检查最小数据点数 if(result[0].size() < 10) { DEBUG_OUT(QString("Too few data points: %1").arg(result[0].size())); return false; } - // 数据有效性检查 for(int i = 0; i < result[0].size(); ++i) { if(!isFiniteNumber(result[0][i]) || !isFiniteNumber(result[1][i])) { DEBUG_OUT(QString("Invalid data at index %1: X=%2, Y=%3") @@ -3049,7 +2921,7 @@ bool nmCalculationAutoFitLM::validateSolverResult(const QVector> } } - // 检查X值单调性 + // 单调性仅作诊断;误差计算会重新排序,不在此拒绝结果。 bool isMonotonic = true; for(int i = 1; i < result[0].size(); ++i) { @@ -3216,49 +3088,6 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( // 比较区间在本轮固定,因此点数也固定;微小容差避免整数量级因舍入多取一点。 const double timeDecades = (qLn(overlapMaxX) - qLn(overlapMinX)) / qLn(10.0); const int numPoints = qMax(3, qCeil(timeDecades * kAutoFitIntervalsPerDecade - 1.0e-10) + 1); - auto populateStageMetrics = [&](AutoFitObjectiveBreakdownLM* objective) -> bool { - // 阶段指标沿用相同网格,仅做插值评价,不增加求解点数或 DLL 调用。 - // 斜率跨度保持约为全对数时域的 10%,压力和导数仍等权。 - const int count = numPoints; - const int lag = autoFitShapeLag(count); - const double span = qLn(overlapMaxX) - qLn(overlapMinX); - QVector residuals[2]; - double biases[2] = {0.0, 0.0}; - for(int component = 0; component < 2; ++component) { - for(int i = 0; i < count; ++i) { - const double time = i == 0 ? overlapMinX : (i == count - 1 ? overlapMaxX - : qExp(qLn(overlapMinX) + span * i / (count - 1))); - double targetValue = 0.0, resultValue = 0.0; - if(!interpolateLogValue(component == 0 ? targetPressure : targetDerivative, - time, &targetValue) || - !interpolateLogValue(component == 0 ? resultPressure : resultDerivative, - time, &resultValue)) return false; - const double residual = resultValue - targetValue; - residuals[component].append(residual); - // 使用对数时间梯形权重,避免密集段主导高度。 - biases[component] += residual * ((i == 0 || i == count - 1) ? 0.5 : 1.0) - / (count - 1); - } - } - objective->pressureVerticalBias = biases[0]; - objective->derivativeVerticalBias = biases[1]; - objective->verticalCommonBias = 0.5 * (biases[0] + biases[1]); - objective->verticalLoss = qAbs(objective->verticalCommonBias); - objective->verticalReliable = !(biases[0] * biases[1] < 0.0 && - qMin(qAbs(biases[0]), qAbs(biases[1])) > 0.01); - objective->shapeResiduals.clear(); - const double scale = qSqrt(0.5 / (count - lag)); - for(int component = 0; component < 2; ++component) { - for(int i = 0; i < count - lag; ++i) { - objective->shapeResiduals.append(scale * - (residuals[component][i + lag] - residuals[component][i]) / - (span * lag / (count - 1))); - } - } - objective->shapeLoss = qSqrt(trustRegionSquaredNorm(objective->shapeResiduals)); - return isFiniteNumber(objective->shapeLoss); - }; - QVector commonX(numPoints); QVector commonLogX(numPoints); QVector targetLogPressure(numPoints); @@ -3273,14 +3102,14 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( (comparisonLogMaxX - comparisonLogMinX) / (numPoints - 1); commonLogX[i] = logX; - // 首尾直接使用原始端点,避免 exp(log(t)) 的舍入误差越过严格插值边界。 - if(i == 0) { - commonX[i] = overlapMinX; - } else if(i == numPoints - 1) { - commonX[i] = overlapMaxX; - } else { - commonX[i] = qExp(logX); - } + // 首尾直接使用原始端点,避免 exp(log(t)) 的舍入误差越过严格插值边界。 + if(i == 0) { + commonX[i] = overlapMinX; + } else if(i == numPoints - 1) { + commonX[i] = overlapMaxX; + } else { + commonX[i] = qExp(logX); + } if(!interpolateLogValue( targetPressure, commonX[i], @@ -3317,6 +3146,41 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( resultLogDerivative - targetLogDerivative[i]; } + auto populateStageMetrics = [&](AutoFitObjectiveBreakdownLM* objective) -> bool { + // 复用同一网格上已算出的原始残差,避免为高度和形状再次插值。 + // 斜率跨度保持约为全对数时域的 10%,压力和导数仍等权。 + const int count = numPoints; + const int lag = autoFitShapeLag(count); + const double span = qLn(overlapMaxX) - qLn(overlapMinX); + const QVector* residuals[] = { &pressureResidual, &derivativeResidual }; + double biases[2] = {0.0, 0.0}; + for(int component = 0; component < 2; ++component) { + for(int i = 0; i < count; ++i) { + const double residual = (*residuals[component])[i]; + // 使用对数时间梯形权重,避免密集段主导高度。 + biases[component] += residual * ((i == 0 || i == count - 1) ? 0.5 : 1.0) + / (count - 1); + } + } + objective->pressureVerticalBias = biases[0]; + objective->derivativeVerticalBias = biases[1]; + objective->verticalCommonBias = 0.5 * (biases[0] + biases[1]); + objective->verticalLoss = qAbs(objective->verticalCommonBias); + objective->verticalReliable = !(biases[0] * biases[1] < 0.0 && + qMin(qAbs(biases[0]), qAbs(biases[1])) > 0.01); + objective->shapeResiduals.clear(); + const double scale = qSqrt(0.5 / (count - lag)); + for(int component = 0; component < 2; ++component) { + for(int i = 0; i < count - lag; ++i) { + objective->shapeResiduals.append(scale * + ((*residuals[component])[i + lag] - (*residuals[component])[i]) / + (span * lag / (count - 1))); + } + } + objective->shapeLoss = qSqrt(trustRegionSquaredNorm(objective->shapeResiduals)); + return isFiniteNumber(objective->shapeLoss); + }; + AutoFitObjectiveBreakdownLM breakdown; // 在指定中心附近计算普通均方根误差。 @@ -3674,7 +3538,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( return invalidLoss; } - // horizontalGain 是“允许水平位移”相对“固定零位移”减少的均方能量。 + // horizontalGain 是允许水平位移后消除的均方误差贡献的平方根。 // 只有改善足够明显且最优点不是边界,才把位移解释为可靠左右偏差。 double horizontalGain = nestedRmsContribution( zeroShiftCenteredLoss, bestCenteredLoss); @@ -3709,8 +3573,8 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( bool flatRegistrationProfile = profileContrast <= horizontalSignalThreshold; - // 平台曲线的 profile 也可能很平,但公共 bias 在各个位移下保持稳定, - // 此时仍能可靠判断上下。只有近优位移会明显改变 bias 才说明上下/左右不可辨识。 + // 平坦的配准误差不一定表示歧义;只有近优位移显著改变公共偏差时, + // 才在旧配准诊断中标记高度和水平位移无法区分。 double minimumNearOptimalBias = std::numeric_limits::infinity(); double maximumNearOptimalBias = @@ -3738,7 +3602,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( halfShiftStepCount > 0 && qAbs(bestShiftStep) == halfShiftStepCount; // 压力和导数通道分别求出的最佳位移若方向相反或相差过大,说明一个 - // 单一水平平移无法解释两条曲线,此时标记配准歧义并禁用左右引导。 + // 单一水平平移无法解释两条曲线,此时将水平诊断标记为不可靠。 bool pressureShiftDetected = qAbs(bestPressureShift) >= 0.5 * physicalShiftStep; @@ -3772,7 +3636,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( ? bestPhysicalShift : 0.0; - // 可信水平位移确定后,在该位移实际覆盖的最大区间重新计算上下和形状。 + // 在可靠水平位移覆盖的区间计算旧配准诊断,保留其有效性检查。 int diagnosticBegin = 0; int diagnosticEnd = numPoints; while(diagnosticBegin < diagnosticEnd && @@ -3821,8 +3685,8 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( return invalidLoss; } - // 原始对齐误差减去公共中心后的能量差定义为上下误差贡献。只有它相对 - // 当前对齐误差足够明显,且配准无歧义时,公共 bias 才可用于有符号选参。 + // 此处保留旧配准诊断的高度有效性检查。真正用于高度调整的指标, + // 在 populateStageMetrics() 中按未平移曲线的公共偏差重新计算。 breakdown.verticalCommonBias = commonBias; breakdown.verticalLoss = nestedRmsContribution( rawAlignedLoss, centeredAlignedLoss); @@ -3846,7 +3710,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( } } - // shapeLoss 是去除可信左右位移和公共均值中心后的剩余误差。 + // 此处检查配准后的剩余误差;LM 使用的斜率形状误差在后面覆盖写入。 double shapePressureLoss = rmse( shapePressure, diagnosticBegin, diagnosticEnd); double shapeDerivativeLoss = rmse( @@ -3877,7 +3741,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError( } // LM 总目标等于固定残差向量的二范数;压力和导数各占一半能量。 - // total 的定义不变;阶段形状和高度指标在下方以固定网格单独计算。 + // 高度和斜率形状指标随后由同一固定网格的原始残差计算。 breakdown.total = qSqrt( 0.5 * breakdown.pressureLoss * breakdown.pressureLoss + 0.5 * breakdown.derivativeLoss * breakdown.derivativeLoss); @@ -3969,14 +3833,13 @@ QVector> nmCalculationAutoFitLM::runSolverDll() dllTask->start(); // 等待完成。最大等待 1 小时;用户停止使用任务已有的协作取消接口。 - const int maxWait = 3600000; // 1h超时 + const int maxWait = 3600000; const int checkInterval = 50; QTime waitTimer; waitTimer.start(); while(waitTimer.elapsed() < maxWait) { - // wait(timeout) 会在线程一完成时立即返回,避免原来固定 msleep(500) - // 带来的每次 0~500ms 额外等待;50ms 间隔仍可及时处理停止请求和界面事件。 + // 求解完成时立即返回;等待期间每 50 ms 处理停止请求和界面事件。 if(dllTask->wait(checkInterval)) { DEBUG_OUT("DLL solver task completed"); break; @@ -3991,7 +3854,6 @@ QVector> nmCalculationAutoFitLM::runSolverDll() } } - // 超时处理 if(dllTask->isRunning()) { DEBUG_OUT("DLL solver task timeout, terminating..."); dllTask->terminate(); @@ -4000,11 +3862,10 @@ QVector> nmCalculationAutoFitLM::runSolverDll() delete dllTask; dllTask = nullptr; --m_evaluationInProgress; - m_consecutiveFailures++; return result; } - // 线程结束后检查真实执行结果,防止失败时复用上一粒子的旧曲线。 + // 线程结束后检查真实执行结果,防止失败时复用上一候选的旧曲线。 dllTask->wait(); if(m_shouldStop) { DEBUG_OUT("DLL solver evaluation cancelled by user"); @@ -4018,7 +3879,6 @@ QVector> nmCalculationAutoFitLM::runSolverDll() delete dllTask; dllTask = nullptr; --m_evaluationInProgress; - m_consecutiveFailures++; return result; } @@ -4042,7 +3902,6 @@ QVector> nmCalculationAutoFitLM::runSolverDll() } } - // 数据有效性检查 if(pressureResult.size() >= 2 && pressureResult[0].size() > 0 && pressureResult[1].size() > 0 @@ -4050,30 +3909,6 @@ QVector> nmCalculationAutoFitLM::runSolverDll() result = pressureResult; m_lastEvaluatedLogLogData = logLogResult; DEBUG_OUT(QString("Got DLL solver result: %1 points").arg(result[0].size())); - m_consecutiveFailures = 0; - - // 检查结果是否与之前不同。若连续粒子得到完全相同的压力曲线, - // 可能说明参数没有正确写入 DataManager,或求解器缓存/状态没有刷新。 - static QVector lastPressureResult; - bool isDifferentFromLast = false; - - if(lastPressureResult.isEmpty() || lastPressureResult.size() != pressureResult[1].size()) { - isDifferentFromLast = true; - } else { - for(int i = 0; i < qMin(5, pressureResult[1].size()); ++i) { - if(qAbs(lastPressureResult[i] - pressureResult[1][i]) > 1e-12) { - isDifferentFromLast = true; - break; - } - } - } - - if(isDifferentFromLast) { - DEBUG_OUT("RESULT VERIFICATION: Got NEW result data from DLL"); - lastPressureResult = pressureResult[1]; - } else { - DEBUG_OUT("!!! WARNING: Result data appears to be identical to previous run !!!"); - } } else { DEBUG_OUT("DLL solver result is empty or invalid"); @@ -4081,21 +3916,17 @@ QVector> nmCalculationAutoFitLM::runSolverDll() .arg(pressureResult.size()) .arg(pressureResult.size() > 0 ? pressureResult[0].size() : 0) .arg(pressureResult.size() > 1 ? pressureResult[1].size() : 0)); - m_consecutiveFailures++; } } catch(const std::bad_alloc& e) { DEBUG_OUT(QString("Memory allocation failed in DLL solver: %1").arg(e.what())); - m_consecutiveFailures++; } catch(const std::exception& e) { DEBUG_OUT(QString("Exception in DLL solver: %1").arg(e.what())); - m_consecutiveFailures++; } catch(...) { DEBUG_OUT("Unknown exception in DLL solver"); - m_consecutiveFailures++; } - // 清理DLL任务 + // 释放求解任务,退出评价前处理待派发的界面事件。 if(dllTask) { if(dllTask->isRunning()) { dllTask->terminate(); @@ -4146,7 +3977,7 @@ bool nmCalculationAutoFitLM::runFinalFullSolver() break; } - // 最终完整计算可能持续较长时间,此处需处理停止按钮事件。 + // 最终完整计算期间处理界面事件,保持窗口响应。 QApplication::processEvents(QEventLoop::AllEvents, checkInterval); if(!dllTask.isRunning()) { @@ -4178,9 +4009,6 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co case LM_TARGET_ACHIEVED: return tr("Target error achieved"); - case LM_TRUE_CONVERGENCE: - return tr("Algorithm converged to stable solution"); - case LM_LOCAL_OPTIMUM: return tr("Local optimum detected");