From eb6ef39007083daf1e6f942a484e1eb6936ec764 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Wed, 5 Aug 2026 11:34:25 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=9A=E4=BA=95?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8B=9F=E5=90=88=E6=B1=82=E8=A7=A3=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nmCalculation/nmCalculationAutoFitPSO.h | 1 + .../nmCalculationDllPebiSolverTask.h | 13 ++ .../nmCalculation/nmCalculationAutoFitPSO.cpp | 187 +++++++++++++----- .../nmCalculationDllPebiSolverTask.cpp | 80 ++++++-- 4 files changed, 217 insertions(+), 64 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index e59db09..3418b04 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -166,6 +166,7 @@ private: // ===== 求解器相关 ===== QVector > runSolver(); QVector> runSolverDll(); + bool runFinalFullSolver(); QVector> runSolverExe(); // ===== 数据处理 ===== diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 1de9f71..3efc33f 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -2,6 +2,8 @@ #define NMCALCULATIONDLLPEBISOLVERTASK_H #include +#include +#include #include #include #include @@ -22,6 +24,13 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { // 返回 false 时调用方会丢弃本次结果, 防止复用上一粒子留下的旧曲线. bool wasSuccessful() const; + // 自动拟合粒子评价只提取目标井曲线,不写回共享数据和网格压力场。 + // 井名为空时保持原有完整结果保存模式。 + void setAutoFitTargetWell(const QString& wellName); + QVector > getAutoFitResultPressure() const; + QVector > getAutoFitResultLogLog() const; + QVector > getAutoFitResultSemiLog() const; + private: bool execute(); @@ -37,6 +46,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { QString m_sPostprocessingDir; // run() 在线程内保存 execute() 结果, 等待线程结束的调用方只读取该状态. bool m_lastRunSucceeded; + QString m_autoFitTargetWellName; + QVector > m_autoFitResultPressure; + QVector > m_autoFitResultLogLog; + QVector > m_autoFitResultSemiLog; private slots: //void slotTaskUpdateProgress(); diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index d0deda2..936d9fc 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -721,8 +721,8 @@ void nmCalculationAutoFitPSO::setTargetLogLogData(const QVector void nmCalculationAutoFitPSO::stopFitting() { // 用户点击停止时走这里。停止策略是“请求式停止”: - // 先置 m_shouldStop,让主循环/求解器等待逻辑自然退出;短时间内还在评价时再重置计数。 - // 这样可以减少 DLL 任务被硬中断导致的数据状态残留。 + // 只置 m_shouldStop,让主循环/求解器等待逻辑退出并自行维护任务计数。 + // 不在这里清理临时目录或强制清零计数,避免与正在返回的 DLL 任务竞争。 if(m_simulationMode && m_simulationTimer) { m_simulationTimer->stop(); } @@ -736,25 +736,28 @@ void nmCalculationAutoFitPSO::stopFitting() m_shouldStop = true; - // 等待当前评估完成,缩短超时时间 + if(m_simulationMode) { + m_isRunning = false; + closeTraceFile(); + cleanupTemporaryDirectory(); + emit logMessageGenerated(tr("PSO simulation stop request processed")); + return; + } + + // 给当前评价一个短暂的自然退出时间。若仍在运行, + // runSolverDll() 会在下一个等待周期检查 m_shouldStop 并结束任务。 int waitCount = 0; - while(m_evaluationInProgress > 0 && waitCount < 30) { // 减少等待时间 + while(m_evaluationInProgress > 0 && waitCount < 30) { QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 50); msleep(50); waitCount++; } - // 超时时强制重置 if(m_evaluationInProgress > 0) { - DEBUG_OUT("Force resetting evaluation counter"); - emit logMessageGenerated(tr("Force stopping current evaluation...")); - m_evaluationInProgress = 0; + emit logMessageGenerated(tr("Waiting for current solver evaluation to stop...")); } - // 确保运行标志被清除 - m_isRunning = false; - emit logMessageGenerated(tr("PSO optimization stop request processed")); DEBUG_OUT("Stop request processed"); } else { @@ -762,8 +765,6 @@ void nmCalculationAutoFitPSO::stopFitting() emit logMessageGenerated(tr("Stop request received but optimization is not running")); } - closeTraceFile(); - cleanupTemporaryDirectory(); } bool nmCalculationAutoFitPSO::isRunning() const @@ -2903,6 +2904,15 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(tr("Target data validation passed (%1 data points)").arg(m_targetLogLogData[0].size())); + if(m_targetWellName.isEmpty()) { + m_lastError = "Target well name is empty"; + emit logMessageGenerated(tr("ERROR: Target well name is empty")); + return false; + } + + emit logMessageGenerated(tr("Particle evaluation mode: solve all wells, retain target well '%1' only") + .arg(m_targetWellName)); + // 使用保存的初始值进行精英保护。resetOptimizer() 会清空部分运行状态, // 所以先把用户当前模型参数缓存下来,后面再恢复用于初始解评价和粒子初始化。 QVector savedInitialValues = m_initialValues; @@ -3286,13 +3296,37 @@ bool nmCalculationAutoFitPSO::startAutoFitting() return false; } - m_isRunning = false; + bool finalFullSolverSucceeded = true; + bool finalFullSolverExecuted = false; // 应用最终参数 if(!m_globalBestPosition.isEmpty()) { try { emit logMessageGenerated(tr("Applying optimized parameters to model...")); applyParametersToDataManager(m_globalBestPosition); + + if(m_shouldStop) { + // 手动停止优先保持快速返回,仅写回已确认的最优参数。 + emit logMessageGenerated(tr("Final full-field calculation skipped after user stop")); + } else { + // 粒子阶段只保留目标井临时曲线。正常结束后用最优参数完整计算一次, + // 将全部井曲线和网格压力场写回项目,该次不计入 PSO 粒子评价数。 + emit logMessageGenerated(tr("Running final full-field calculation with optimized parameters...")); + finalFullSolverExecuted = true; + finalFullSolverSucceeded = runFinalFullSolver(); + + if(finalFullSolverSucceeded) { + emit logMessageGenerated(tr("Final full-field calculation completed successfully")); + } else if(m_shouldStop) { + finalFullSolverExecuted = false; + finalFullSolverSucceeded = true; + emit logMessageGenerated(tr("Final full-field calculation stopped by user")); + } else { + emit logMessageGenerated(tr("ERROR: Final full-field calculation failed")); + m_lastError = tr("Optimized parameters were found, but the final full-field calculation failed"); + } + } + saveOptimizationResult(); // 输出最终优化结果 @@ -3311,16 +3345,24 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(finalParams); - emit logMessageGenerated(tr("Parameters applied successfully to data manager")); + if(finalFullSolverExecuted && finalFullSolverSucceeded) { + emit logMessageGenerated(tr("Parameters and full-field results applied successfully to data manager")); + } else if(!finalFullSolverExecuted) { + emit logMessageGenerated(tr("Optimized parameters applied to data manager")); + } } catch(const std::exception& e) { + finalFullSolverSucceeded = false; emit logMessageGenerated(tr("ERROR: Failed to apply final parameters: %1").arg(e.what())); m_lastError = QString("Failed to apply final parameters: %1").arg(e.what()); } catch(...) { + finalFullSolverSucceeded = false; emit logMessageGenerated(tr("ERROR: Unknown error applying final parameters")); m_lastError = "Failed to apply final parameters due to unknown error"; } } + m_isRunning = false; + // 判断系统确定最终结果 bool success; QString message; @@ -3363,6 +3405,11 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(tr("=== PSO OPTIMIZATION - UNKNOWN END ===")); } + if(!finalFullSolverSucceeded) { + success = false; + message = m_lastError; + } + emitRunSummary(success, finalReason); // 先发送最终进度更新,确保进度条达到100% @@ -3961,7 +4008,7 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter // 1. 校验粒子参数是否在用户设置的上下界和基本物理范围内; // 2. 将参数写入 DataManager 的储层/目标井对象; // 3. 调用真实数值求解器,生成模拟结果; - // 4. 从目标井读取模拟后的 result log-log 曲线; + // 4. 从本次求解任务读取目标井 result log-log 曲线; // 5. 与目标 history log-log 曲线计算误差,误差越小代表拟合越好。 // // 返回 1e10 表示该粒子评价失败或结果不可用。PSO 会把它当成很差的解。 @@ -4122,24 +4169,11 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter return 1e10; } - // 5. 获取 LogLog 数据。runSolver() 会更新 DataManager 中目标井的计算结果, - // 这里再从目标井读取 resultLogLogData 作为模拟曲线。 - QVector> resultLogLogData; + // 5. 获取 LogLog 数据。runSolverDll() 直接从求解任务复制目标井曲线, + // 不再依赖 DataManager 中可能被其它井或上一粒子改写的共享结果。 + QVector> resultLogLogData = m_lastEvaluatedLogLogData; try { - nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); - - if(!pTargetWell) { - DEBUG_OUT(QString("%1: Call #%2 - Target well '%3' NOT FOUND") - .arg(funcName).arg(callCount).arg(m_targetWellName)); - return 1e10; - } - - DEBUG_OUT(QString("%1: Call #%2 - Target well found: %3") - .arg(funcName).arg(callCount).arg(m_targetWellName)); - - resultLogLogData = pTargetWell->getResultLogLog(); - if(!validateLogLogData(resultLogLogData)) { DEBUG_OUT(QString("%1: Call #%2 - LogLog data VALIDATION FAILED") .arg(funcName).arg(callCount)); @@ -5145,7 +5179,7 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() { // DLL 求解器路径。 // 这个函数负责把当前 DataManager 中的项目状态交给底层数值求解器, - // 并让目标井生成 result log-log 数据。evaluateFitness() 后续会从目标井读取该结果。 + // 数值求解仍包含全部计算井,以保留井间干扰;后处理只提取目标井曲线。 // // 如果这里失败,通常需要优先检查:HX_NWTM.dll、license、网格/井数据是否完整、 // 目标井是否存在,以及 DataManager 中刚写入的参数是否导致求解器异常。 @@ -5157,6 +5191,7 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() } ++m_evaluationInProgress; + m_lastEvaluatedLogLogData.clear(); QVector> result; nmCalculationDllPebiSolverTask* dllTask = nullptr; @@ -5164,6 +5199,15 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() DEBUG_OUT("Creating DLL solver task"); dllTask = new nmCalculationDllPebiSolverTask(m_tempDirectory); + if(m_targetWellName.isEmpty()) { + DEBUG_OUT("Target well name is empty - target-only solver cannot start"); + delete dllTask; + --m_evaluationInProgress; + return result; + } + + dllTask->setAutoFitTargetWell(m_targetWellName); + if(m_shouldStop) { DEBUG_OUT("Should stop - cleaning up and returning empty result"); delete dllTask; @@ -5223,22 +5267,9 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() return result; } - // 验证结果数据是否已更新。DLL 任务会把结果写回 DataManager 中的目标井对象。 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - //QVector wells = dataManager->getWellDataList(); - nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); - - if(!pTargetWell) { - DEBUG_OUT("No wells found in data manager after DLL execution"); - delete dllTask; - --m_evaluationInProgress; - return result; - } - - // 验证结果数据。evaluateFitness() 最终用的是 logLogResult, - // 但这里返回 pressureResult 给 validateSolverResult() 做基本求解成功判断。 - QVector> pressureResult = pTargetWell->getResultPressure(); - QVector> logLogResult = pTargetWell->getResultLogLog(); + // 任务结束后复制其局部结果,删除任务前不再持有任务内部引用。 + QVector> pressureResult = dllTask->getAutoFitResultPressure(); + QVector> logLogResult = dllTask->getAutoFitResultLogLog(); DEBUG_OUT(QString("DLL result verification - Pressure arrays: %1, LogLog arrays: %2") .arg(pressureResult.size()).arg(logLogResult.size())); @@ -5257,8 +5288,12 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() } // 数据有效性检查 - if(pressureResult.size() >= 2 && pressureResult[0].size() > 0 && pressureResult[1].size() > 0) { + if(pressureResult.size() >= 2 + && pressureResult[0].size() > 0 + && pressureResult[1].size() > 0 + && validateLogLogData(logLogResult)) { result = pressureResult; + m_lastEvaluatedLogLogData = logLogResult; DEBUG_OUT(QString("Got DLL solver result: %1 points").arg(result[0].size())); m_consecutiveFailures = 0; @@ -5326,6 +5361,58 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() return result; } +bool nmCalculationAutoFitPSO::runFinalFullSolver() +{ + // 不设置目标井名,任务按原完整模式保存全部井和网格结果。 + if(m_evaluationInProgress > 0) { + DEBUG_OUT("Cannot start final full-field solver while another evaluation is running"); + return false; + } + + ++m_evaluationInProgress; + nmCalculationDllPebiSolverTask dllTask(m_tempDirectory); + dllTask.start(); + + const int maxWait = 3600000; + const int checkInterval = 50; + QTime waitTimer; + waitTimer.start(); + + while(waitTimer.elapsed() < maxWait) { + if(dllTask.wait(checkInterval)) { + break; + } + + // 最终完整计算可能持续较长时间,此处需处理停止按钮事件。 + QApplication::processEvents(QEventLoop::AllEvents, checkInterval); + + if(!dllTask.isRunning()) { + break; + } + + if(m_shouldStop) { + DEBUG_OUT("Final full-field solver terminated by user"); + dllTask.terminate(); + dllTask.wait(2000); + --m_evaluationInProgress; + return false; + } + } + + if(dllTask.isRunning()) { + DEBUG_OUT("Final full-field solver timeout, terminating task"); + dllTask.terminate(); + dllTask.wait(2000); + --m_evaluationInProgress; + return false; + } + + dllTask.wait(); + const bool succeeded = dllTask.wasSuccessful(); + --m_evaluationInProgress; + return succeeded; +} + //QVector> nmCalculationAutoFitPSO::runSolverExe() //{ // DEBUG_OUT("SOLVER EXE START"); diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index e8db3a5..9f87c0b 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -227,6 +227,26 @@ bool nmCalculationDllPebiSolverTask::wasSuccessful() const return m_lastRunSucceeded; } +void nmCalculationDllPebiSolverTask::setAutoFitTargetWell(const QString& wellName) +{ + m_autoFitTargetWellName = wellName; +} + +QVector > nmCalculationDllPebiSolverTask::getAutoFitResultPressure() const +{ + return m_autoFitResultPressure; +} + +QVector > nmCalculationDllPebiSolverTask::getAutoFitResultLogLog() const +{ + return m_autoFitResultLogLog; +} + +QVector > nmCalculationDllPebiSolverTask::getAutoFitResultSemiLog() const +{ + return m_autoFitResultSemiLog; +} + bool nmCalculationDllPebiSolverTask::execute() { return this->execPebiMode(); @@ -582,6 +602,15 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult( int modelType) { nmDataAnalyzeManager* pDataInstance = nmDataAnalyzeManager::getCurrentInstance(); + if(pDataInstance == nullptr) { + return false; + } + + const bool autoFitTargetOnly = !m_autoFitTargetWellName.isEmpty(); + bool autoFitTargetFound = false; + m_autoFitResultPressure.clear(); + m_autoFitResultLogLog.clear(); + m_autoFitResultSemiLog.clear(); QVector> vvecPressure; QVector> vvecLogLog; @@ -599,16 +628,22 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult( } // 获取参与求解的井的顺序 - QVector> vecWellsOrder = nmDataAnalyzeManager::getCurrentInstance()->getCalculationWells(); + QVector> vecWellsOrder = pDataInstance->getCalculationWells(); - // 清空井名和二维位置的映射 - pDataInstance->clearWellLocations(); + // 粒子评价不修改全局井位置,完整求解仍按原流程重建映射. + if(!autoFitTargetOnly) { + pDataInstance->clearWellLocations(); + } // 遍历每口井,处理其数据 for(int wellIdx = 0; wellIdx < vecWellsOrder.size(); ++wellIdx) { NM_WELL_MODEL eWellType = vecWellsOrder[wellIdx].first; // 获取井的类型 QString sWellName = vecWellsOrder[wellIdx].second; // 获取井的名称 + if(autoFitTargetOnly && sWellName != m_autoFitTargetWellName) { + continue; + } + // 跳过裂缝(或未知井类型) if(eWellType == NM_WELL_MODEL::Unknow_Well) { continue; @@ -760,17 +795,34 @@ bool nmCalculationDllPebiSolverTask::savePebiModeResult( } } - // 将计算结果保存到对应的井数据里 - // 压力 - pWellData->setResultPressure(vvecPressure); - // 双对数 - pWellData->setResultLogLog(vvecLogLog); - // 半对数 - pWellData->setResultSemiLog(vvecSemiLog); - - // 存储当前井名称和二维位置到映射 - QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble()); - pDataInstance->addWellLocation(sWellName, ptWellCoords); + if(autoFitTargetOnly) { + // 粒子评价结果保存在任务对象内,避免反复改写 DataManager. + m_autoFitResultPressure = vvecPressure; + m_autoFitResultLogLog = vvecLogLog; + m_autoFitResultSemiLog = vvecSemiLog; + autoFitTargetFound = true; + break; + } else { + // 完整模式保持原行为:写入全部井曲线和井位置. + pWellData->setResultPressure(vvecPressure); + pWellData->setResultLogLog(vvecLogLog); + pWellData->setResultSemiLog(vvecSemiLog); + + QPointF ptWellCoords(pWellData->getX().getValue().toDouble(), pWellData->getY().getValue().toDouble()); + pDataInstance->addWellLocation(sWellName, ptWellCoords); + } + } + + if(autoFitTargetOnly) { + const bool pressureValid = autoFitTargetFound + && m_autoFitResultPressure.size() >= 2 + && !m_autoFitResultPressure[0].isEmpty() + && m_autoFitResultPressure[0].size() == m_autoFitResultPressure[1].size(); + const bool logLogValid = m_autoFitResultLogLog.size() >= 3 + && !m_autoFitResultLogLog[0].isEmpty() + && m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[1].size() + && m_autoFitResultLogLog[0].size() == m_autoFitResultLogLog[2].size(); + return pressureValid && logLogValid; } // 计算有效单元数量 From 6e34d44f0b7cd7e2c4458660cd381e755cea3697 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Wed, 5 Aug 2026 16:11:30 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E9=80=89=E4=BA=95=E5=8A=A0=E8=BD=BD=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用集合计算井图元差集,并在批量增删期间合并 Map 重绘。 合并井列表变更触发的参数面板刷新,避免逐井完整重建。 --- Include/nmNum/nmSubWxs/nmWxParaPropertyPebi.h | 10 +++++++ Src/nmNum/nmSubWnd/nmSubWndMain.cpp | 26 ++++++++++++++--- Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp | 28 ++++++++++++++++++- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/Include/nmNum/nmSubWxs/nmWxParaPropertyPebi.h b/Include/nmNum/nmSubWxs/nmWxParaPropertyPebi.h index 5488a07..d4d6938 100644 --- a/Include/nmNum/nmSubWxs/nmWxParaPropertyPebi.h +++ b/Include/nmNum/nmSubWxs/nmWxParaPropertyPebi.h @@ -5,6 +5,7 @@ class nmGridRowUtils; class nmDataAnalyzeManager; +class QTimer; /// @brief PEBI求解器专用的参数输入/编辑属性对话框窗体 /// @note 与 nmWxParaProperty 结构一致,区别在于内部使用 nmGridRowUtils(NM子类) @@ -80,6 +81,8 @@ public slots: void slotWellRemoved(QString wellCode, QString wellName); /// @brief 几何对象新增、删除或改名后重建参数面板 void slotGeometryListChanged(); + /// @brief 合并同一轮事件中的多次井列表变化,只重建一次参数面板 + void slotDeferredWellListRebuild(); protected: @@ -100,6 +103,13 @@ protected: // 数据管理器引用(用于全量重建面板时获取井和几何对象列表) nmDataAnalyzeManager* m_pDataManager; + // 批量增删井时合并参数面板刷新 + QTimer* m_pWellListRebuildTimer; + +private: + + void scheduleWellListRebuild(); + signals: /// @brief 参数值变更信号(由 slotParaCtrlValueChanged 转发) diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index f207478..b33b517 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -64,6 +64,7 @@ #include "iAnalRun.h" #include +#include #include "nmSingalCenter.h" @@ -986,8 +987,18 @@ void nmSubWndMain::updateSelectedWells(QList wellObjList) } } - // 当前在画布上的所有 ZxDataWell* 数据列表 (Key Set) + // 用集合保存当前井和选中井,避免井数增加后反复调用 QList::contains 造成平方级查找。 QList pWellPlotDataList = pWellDataToPlotMap.keys(); + QSet pWellPlotDataSet; + for(int i = 0; i < pWellPlotDataList.count(); ++i) { + pWellPlotDataSet.insert(pWellPlotDataList[i]); + } + QSet selectedWellDataSet; + for(int i = 0; i < wellObjList.count(); ++i) { + if(wellObjList[i] != nullptr) { + selectedWellDataSet.insert(wellObjList[i]); + } + } // setp 2,找出需要 REMOVE (删除) 的图元 QVector vDeleteWellPlotList; @@ -996,7 +1007,7 @@ void nmSubWndMain::updateSelectedWells(QList wellObjList) for(int i = 0; i < pWellPlotDataList.count(); ++i) { ZxDataWell *pWellDataOnPlot = pWellPlotDataList[i]; // 如果该井数据不在用户选中的列表 wellObjList 中,则需要删除 - if (!wellObjList.contains(pWellDataOnPlot)) { + if (!selectedWellDataSet.contains(pWellDataOnPlot)) { // 从 Map 中获取对应的图元对象 vDeleteWellPlotList.append(pWellDataToPlotMap.value(pWellDataOnPlot)); } @@ -1009,12 +1020,16 @@ void nmSubWndMain::updateSelectedWells(QList wellObjList) for(int i = 0; i < wellObjList.count(); ++i) { ZxDataWell *pWellDataSelected = wellObjList[i]; // 确保井数据有效 - if (pWellDataSelected && !pWellPlotDataList.contains(pWellDataSelected)) { + if (pWellDataSelected && !pWellPlotDataSet.contains(pWellDataSelected)) { // 如果该井数据不在当前画布上的数据列表中,则需要添加 vAddWellPlotList.append(pWellDataSelected); } } + // 单井增删过程中会多次请求重绘;批量阶段先暂停,全部处理完成后再统一刷新。 + const bool updatesEnabled = m_pWxPlot->updatesEnabled(); + m_pWxPlot->setUpdatesEnabled(false); + // setp 4,执行删除操作 for(int i = 0; i < vDeleteWellPlotList.count(); ++i) { nmObjPointWell* pWellPlot = vDeleteWellPlotList[i]; @@ -1056,7 +1071,10 @@ void nmSubWndMain::updateSelectedWells(QList wellObjList) // 添加完毕后,重新渲染 - m_pWxPlot->update(); + m_pWxPlot->setUpdatesEnabled(updatesEnabled); + if(updatesEnabled) { + m_pWxPlot->update(); + } } // 地质图导入 diff --git a/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp b/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp index 7eb051f..f687416 100644 --- a/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp +++ b/Src/nmNum/nmSubWxs/nmWxParaPropertyPebi.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "nmWxParaPropertyPebi.h" @@ -195,6 +196,11 @@ nmWxParaPropertyPebi::nmWxParaPropertyPebi(QWidget* parent) : m_pGridItemUtils = nullptr; m_pHelpBox = nullptr; m_pDataManager = nullptr; + // 0ms 单次定时器用于合并同一轮事件中的多次井增删通知。 + m_pWellListRebuildTimer = new QTimer(this); + m_pWellListRebuildTimer->setSingleShot(true); + connect(m_pWellListRebuildTimer, SIGNAL(timeout()), + this, SLOT(slotDeferredWellListRebuild())); setWindowTitle(tr("Numerical para property")); } @@ -376,6 +382,12 @@ void nmWxParaPropertyPebi::slotParaCtrlValueChanged(QString sPara, QVariant o) void nmWxParaPropertyPebi::rebuildAllParas() { + // 已有显式全量重建时,取消尚未执行的延迟刷新,避免重复重建面板。 + if (m_pWellListRebuildTimer != nullptr && m_pWellListRebuildTimer->isActive()) + { + m_pWellListRebuildTimer->stop(); + } + if (m_pMainLayout == nullptr || m_pDataManager == nullptr) return; @@ -779,13 +791,27 @@ void nmWxParaPropertyPebi::slotWellAdded(QString wellCode, QString wellName, QSt Q_UNUSED(wellCode); Q_UNUSED(wellName); Q_UNUSED(paras); - rebuildAllParas(); + scheduleWellListRebuild(); } void nmWxParaPropertyPebi::slotWellRemoved(QString wellCode, QString wellName) { Q_UNUSED(wellCode); Q_UNUSED(wellName); + scheduleWellListRebuild(); +} + +void nmWxParaPropertyPebi::scheduleWellListRebuild() +{ + if (m_pWellListRebuildTimer != nullptr) + { + // 批量循环内重复 start 会重置同一个单次定时器,返回事件循环后只触发一次。 + m_pWellListRebuildTimer->start(0); + } +} + +void nmWxParaPropertyPebi::slotDeferredWellListRebuild() +{ rebuildAllParas(); } From f0d90f951370cff62b4ca8334cbcc58c46328cee Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Thu, 6 Aug 2026 17:20:06 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=B8=97=E9=80=8F?= =?UTF-8?q?=E7=8E=87=E7=9A=84=E5=9F=BA=E5=87=86=E5=8D=95=E4=BD=8D=E4=B8=BA?= =?UTF-8?q?Darcy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Common/ModelParaDefinesNM_cn.dat | 2 +- Bin/Config/Common/ModelParaDefinesNM_en.dat | 2 +- Bin/Config/Common/UnitDefault_cn.dat | 2 +- Bin/Config/Common/UnitDefault_en.dat | 2 +- Bin/Config/Unit/UnitDefault.dat | 2 +- Bin/XmlFiles/ModelParaDefinesNM_cn.xml | 4 ++-- Bin/XmlFiles/ModelParaDefinesNM_en.xml | 4 ++-- Bin/XmlFiles/UnitDefault_cn.xml | 2 +- Bin/XmlFiles/UnitDefault_en.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Bin/Config/Common/ModelParaDefinesNM_cn.dat b/Bin/Config/Common/ModelParaDefinesNM_cn.dat index 3b97d08..4ad2fde 100644 --- a/Bin/Config/Common/ModelParaDefinesNM_cn.dat +++ b/Bin/Config/Common/ModelParaDefinesNM_cn.dat @@ -1 +1 @@ -PD94bWwgdmVycup_mpac2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCg0KPCEtLSoqKioqKioqKioqKiwzyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCjwhLS0gICAg5pys5paH5qGj55qE55uu55qE5piv5bu656uLV1RBSeaVsOWAvOinoyhOTSnmqKHlnZfnmoTlj4LmlbDlrprkuYkgICAgICAgICAgICAgICAgLS0+DQo8IS0tICAgIOS7heWMheWQq1BFQknmsYLop6PlmajmiYDpnIDnmoTlgqjlsYLjgIHkupXlkozlh6DkvZXlr7nosaHlj4LmlbAgICAgICAgICAgICAgICAgICAtLT4NCjwhLS0gICAgRGF0ZTogMjAyNjA2MTUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0tPg0KPCEtLSoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCg0KPCEtLQ0KICAgIOWPguaVsOiMg+WbtO+8mlBFQknmsYLop6PlmajmiYDpnIDnmoTlgqjlsYLjgIHkupXlkozlh6DkvZXlr7nosaHlj4LmlbANCiAgICDlr7nlupQgSFhfTldUTV9NT0RFTF9JTlBVVCDnu5PmnoTkvZPlrZfmrrUNCiAgICDpgJrov4cgX3BhcmFIZWxwZXItPmxvYWRQYXJhRGVmaW5lcygpIOWKoOi9ve+8jHNLZXkg5qCH6K+G5Li6IHNfTm1fU2VyaWUNCi0tPg0KDQo8Um9vdD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuWCqOWxguWPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9ImgiICAgQWxpYXM9IuWCqOWxguWOmuW6piIgICAgICAgVW5pdD0ibSIgICAgIERlZmF1bHQ9IjEwIiAgICBNYXg9IjEwMDAwIiAgTWluPSIxZS0wNSIgIERpZ2l0PSI2IiAgRGVzYz0icmVzZXJ2b2lyIHRoaWNrbmVzcyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUGkiICBBbGlhcz0i5Yid5aeL5Y6L5YqbIiAgICAgICBVbml0PSJNUGEiICAgRGVmYXVsdD0iNDAiICAgIE1heD0iNTAwIiAgICBNaW49IjAuMSIgICAgRGlnaXQ9IjYiICBEZXNjPSJpbml0aWFsIGZvcm1hdGlvbiBwcmVzc3VyZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iSyIgICBBbGlhcz0i5riX6YCP546HIiAgICAgICAgIFVuaXQ9Im1EIiAgICBEZWZhdWx0PSIxIiAgICAgTWF4PSIxMDAwMCIgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InBlcm1lYWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0icGhpIiBBbGlhcz0i5a2U6ZqZ5bqmIiAgICAgICAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjEiICAgTWF4PSIxIiAgICAgIE1pbj0iMWUtMDUiICBEaWdpdD0iNiIgIERlc2M9InBvcm9zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJDdGkiIEFsaWFzPSLnu7zlkIjljovnvKnns7vmlbAiICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAxIiBNYXg9IjEwIiAgICAgTWluPSIxZS0zMCIgIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0idG90YWwgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJDZiIgIEFsaWFzPSLlsqnnn7PljovnvKnns7vmlbAiICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAwMSIgTWF4PSIxMCIgICAgTWluPSIxZS0zMCIgIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0icm9jayBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlNvaSIgQWxpYXM9IuWIneWni+WQq+ayuemlseWSjOW6piIgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuOCIgICBNYXg9IjEiICAgICAgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0iaW5pdGlhbCBvaWwgc2F0dXJhdGlvbiIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iU3dpIiBBbGlhcz0i5Yid5aeL5ZCr5rC06aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC4yIiAgIE1heD0iMSIgICAgICBNaW49IjAiICAgICAgRGlnaXQ9IjYiICBEZXNjPSJpbml0aWFsIHdhdGVyIHNhdHVyYXRpb24iIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i5LqV5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19YIiAgICBBbGlhcz0iWOWdkOaghyIgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgICAgIE1heD0iMTAwMDAiICBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiICBEZXNjPSJ3ZWxsIFggY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19ZIiAgICBBbGlhcz0iWeWdkOaghyIgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgICAgIE1heD0iMTAwMDAiICBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiICBEZXNjPSJ3ZWxsIFkgY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19SdyIgICBBbGlhcz0i5LqV562S5Y2K5b6EIiAgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwLjEiICAgTWF4PSIxMCIgICAgIE1pbj0iMC4wMDEiICBEaWdpdD0iNiIgIERlc2M9IndlbGxib3JlIHJhZGl1cyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19Ta2luIiBBbGlhcz0i6KGo55qu57O75pWwIiAgICBVbml0PSIiICAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwIiAgIE1pbj0iLTEwMCIgICBEaWdpdD0iNiIgIERlc2M9InNraW4gZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0MiICAgIEFsaWFzPSLkupXnrZLlgqjpm4bns7vmlbAiIFVuaXQ9Im1eMy9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMTAwMCIgICBNaW49IjAiICAgICAgRGlnaXQ9IjYiICBEZXNjPSJ3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPgogICAgPFBhcmFJdGVtIE5hbWU9IldfV2VsbExlbmd0aCIgICAgICAgICBBbGlhcz0i5rC05bmz5q616ZW/5bqmIiAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjIwMCIgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0RyYWluQW5nbGUiICAgICAgICAgQWxpYXM9IuaOkuawtOinkuW6piIgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjAiICAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgZHJhaW4gYW5nbGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfTnVtYmVyT2ZGcmFjdHVyZXMiICBBbGlhcz0i6KOC57yd5pWw6YePIiAgICAgVW5pdD0iIiAgICAgRGVmYXVsdD0iMTUiICAgTWF4PSIxMDAwIiAgTWluPSIyIiAgICAgRGlnaXQ9IjAiIERlc2M9Im51bWJlciBvZiBmcmFjdHVyZXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfRnJhY3R1cmVIYWxmTGVuZ3RoIiBBbGlhcz0i6KOC57yd5Y2K6ZW/IiAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iNTAiICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGhhbGYtbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0ZyYWN0dXJlQW5nbGUiICAgICAgQWxpYXM9Iuijgue8neinkuW6piIgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjkwIiAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBhbmdsZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19EZmMiICAgICAgICAgICAgICAgIEFsaWFzPSLoo4LnvJ3lr7zmtYHog73lipsiIFVuaXQ9Im1kLm0iIERlZmF1bHQ9IjEwMDAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBjb25kdWN0aXZpdHkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i54us56uL6KOC57yd5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9YMCIgQWxpYXM9Iui1t+eCuVgwIiAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgc3RhcnQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX1kwIiBBbGlhcz0i6LW354K5WTAiICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBzdGFydCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWDEiIEFsaWFzPSLnu4jngrlYMSIgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGVuZCBYIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWTEiIEFsaWFzPSLnu4jngrlZMSIgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGVuZCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfRkMiIEFsaWFzPSLlr7zmtYHog73lipsiIFVuaXQ9Im1kLm0iIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgY29uZHVjdGl2aXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuaWreWxguWPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZUX1gwIiBBbGlhcz0i6LW354K5WDAiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZmF1bHQgc3RhcnQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMCIgQWxpYXM9Iui1t+eCuVkwIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZhdWx0IHN0YXJ0IFkgY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRlRfWDEiIEFsaWFzPSLnu4jngrlYMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMSIgQWxpYXM9Iue7iOeCuVkxIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZhdWx0IGVuZCBZIGNvb3JkaW5hdGUiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i55+p5b2i6L6555WM5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1pbiIgQWxpYXM9IuW3pui+ueeVjFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1pbiIgQWxpYXM9IuS4i+i+ueeVjFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1heCIgQWxpYXM9IuWPs+i+ueeVjFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1heCIgQWxpYXM9IuS4iui+ueeVjFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLlnIblvaLovrnnlYzlj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJYIiBBbGlhcz0i5ZyG5b+DWCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJjaXJjbGUgYm91bmRhcnkgY2VudGVyIFgiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkJDX0NlbnRlclkiIEFsaWFzPSLlnIblv4NZIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImNpcmNsZSBib3VuZGFyeSBjZW50ZXIgWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQkNfUmFkaXVzIiAgQWxpYXM9IuWNiuW+hCIgIFVuaXQ9Im0iIERlZmF1bHQ9IjEiIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiICBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IHJhZGl1cyIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLlpJrovrnlvaLovrnnlYzngrnlj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9YIiBBbGlhcz0iWOWdkOaghyIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9ZIiBBbGlhcz0iWeWdkOaghyIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBZIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuWMuuWfn+WPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJfTGVha2FnZSIgQWxpYXM9IuazhOmcsuezu+aVsCIgVW5pdD0iIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiIERpZ2l0PSI2IiBEZXNjPSJyZWdpb24gbGVha2FnZSBjb2VmZmljaWVudCIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLmoIforrDljLrln5/lj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21XIiAgICAgICBBbGlhcz0i5YKo6IO95q+UIiBVbml0PSIiIERlZmF1bHQ9IjEiIE1heD0iMSIgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiIERlc2M9InN0b3JhZ2UgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX0NvbUtyIiAgICAgIEFsaWFzPSLmtYHluqbmr5QiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxMDAwIiBNaW49IjAuMDAxIiBEaWdpdD0iNiIgRGVzYz0ibW9iaWxpdHkgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX05ldFRvR3Jvc3MiIEFsaWFzPSLlh4Dmr5vmr5QiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxIiAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0ibmV0LXRvLWdyb3NzIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8IS0tIOe7k+aenOmdouadv+WPguaVsOWPqueUsSByZWJ1aWxkUmVzdWx0UGFyYXMoKSDpgInmi6nvvIzkuI3kvJrlh7rnjrDlnKjlt6bkvqfpnaLmnb/kuK3jgIIgLS0+DQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i57uT5p6c5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfUnciICAgQWxpYXM9IuS6leW+hCIgICAgICAgICBVbml0PSJtIiAgICAgIERlZmF1bHQ9IjAuMSIgICAgTWF4PSIxMCIgICAgTWluPSIwLjAwMSIgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdlbGxib3JlIHJhZGl1cyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfRGZjIiAgQWxpYXM9Iuijgue8neWvvOa1geiDveWKmyIgVW5pdD0ibWQubSIgICBEZWZhdWx0PSIxMDAwIiAgIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBmcmFjdHVyZSBjb25kdWN0aXZpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX1NraW4iIEFsaWFzPSLooajnmq7ns7vmlbAiICAgICBVbml0PSIiICAgICAgIERlZmF1bHQ9IjAiICAgICAgTWF4PSIxMDAwIiAgTWluPSItMTAwIiAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHNraW4gZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfV19DIiAgICBBbGlhcz0i5LqV562S5YKo6ZuG57O75pWwIiBVbml0PSJtXjMvTVBhIiBEZWZhdWx0PSIwIiAgICAgIE1heD0iMTAwMCIgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPgoNCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1BpIiAgQWxpYXM9IuWIneWni+WcsOWxguWOi+WKmyIgICBVbml0PSJNUGEiICAgRGVmYXVsdD0iNDAiICAgICBNYXg9IjUwMCIgICBNaW49IjAuMSIgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGZvcm1hdGlvbiBwcmVzc3VyZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0siICAgQWxpYXM9Iua4l+mAj+eOhyIgICAgICAgICBVbml0PSJtRCIgICAgRGVmYXVsdD0iMSIgICAgICBNYXg9IjEwMDAwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBwZXJtZWFiaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9oIiAgIEFsaWFzPSLlgqjlsYLljprluqYiICAgICAgIFVuaXQ9Im0iICAgICBEZWZhdWx0PSIxMCIgICAgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHJlc2Vydm9pciB0aGlja25lc3MiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9waGkiIEFsaWFzPSLlrZTpmpnluqYiICAgICAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuMSIgICAgTWF4PSIxIiAgICAgTWluPSIxZS0wNSIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcG9yb3NpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9DdGkiIEFsaWFzPSLnu7zlkIjljovnvKnns7vmlbAiICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAxIiAgTWF4PSIxMCIgICAgTWluPSIxZS0zMCIgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgdG90YWwgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2YiICBBbGlhcz0i5bKp55+z5Y6L57yp57O75pWwIiAgIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwLjAwMDEiIE1heD0iMTAiICAgIE1pbj0iMWUtMzAiIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0icmVzdWx0IHJvY2sgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU29pIiBBbGlhcz0i5Yid5aeL5ZCr5rK56aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC44IiAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIG9pbCBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU2dpIiBBbGlhcz0i5Yid5aeL5ZCr5rCU6aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGdhcyBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU3dpIiBBbGlhcz0i5Yid5aeL5ZCr5rC06aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC4yIiAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIHdhdGVyIHNhdHVyYXRpb24iIC8+DQoNCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0JvIiAgIEFsaWFzPSLmsrnkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBvaWwgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVvIiBBbGlhcz0i5rK555u46buP5bqmIiAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBvaWwgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ28iICAgQWxpYXM9IuayueWOi+e8qeezu+aVsCIgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgb2lsIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0J3IiAgIEFsaWFzPSLmsLTkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3YXRlciBmb3JtYXRpb24gdm9sdW1lIGZhY3RvciIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X01pdXciIEFsaWFzPSLmsLTnm7jpu4/luqYiICAgVW5pdD0ibVBhLnMiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMWUtMzAiIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdhdGVyIHZpc2Nvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0N3IiAgIEFsaWFzPSLmsLTljovnvKnns7vmlbAiIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0icmVzdWx0IHdhdGVyIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0JnIiAgIEFsaWFzPSLmsJTkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVnIiBBbGlhcz0i5rCU55u46buP5bqmIiAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2ciICAgQWxpYXM9IuawlOWOi+e8qeezu+aVsCIgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgZ2FzIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCjwvUm9vdD4NCg== \ No newline at end of file +PD94bWwgdmVycup_mpac2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCg0KPCEtLSoqKioqKioqKioqKiwzyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCjwhLS0gICAg5pys5paH5qGj55qE55uu55qE5piv5bu656uLV1RBSeaVsOWAvOinoyhOTSnmqKHlnZfnmoTlj4LmlbDlrprkuYkgICAgICAgICAgICAgICAgLS0+DQo8IS0tICAgIOS7heWMheWQq1BFQknmsYLop6PlmajmiYDpnIDnmoTlgqjlsYLjgIHkupXlkozlh6DkvZXlr7nosaHlj4LmlbAgICAgICAgICAgICAgICAgICAtLT4NCjwhLS0gICAgRGF0ZTogMjAyNjA2MTUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0tPg0KPCEtLSoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCg0KPCEtLQ0KICAgIOWPguaVsOiMg+WbtO+8mlBFQknmsYLop6PlmajmiYDpnIDnmoTlgqjlsYLjgIHkupXlkozlh6DkvZXlr7nosaHlj4LmlbANCiAgICDlr7nlupQgSFhfTldUTV9NT0RFTF9JTlBVVCDnu5PmnoTkvZPlrZfmrrUNCiAgICDpgJrov4cgX3BhcmFIZWxwZXItPmxvYWRQYXJhRGVmaW5lcygpIOWKoOi9ve+8jHNLZXkg5qCH6K+G5Li6IHNfTm1fU2VyaWUNCi0tPg0KDQo8Um9vdD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuWCqOWxguWPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9ImgiICAgQWxpYXM9IuWCqOWxguWOmuW6piIgICAgICAgVW5pdD0ibSIgICAgIERlZmF1bHQ9IjEwIiAgICBNYXg9IjEwMDAwIiAgTWluPSIxZS0wNSIgIERpZ2l0PSI2IiAgRGVzYz0icmVzZXJ2b2lyIHRoaWNrbmVzcyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUGkiICBBbGlhcz0i5Yid5aeL5Y6L5YqbIiAgICAgICBVbml0PSJNUGEiICAgRGVmYXVsdD0iNDAiICAgIE1heD0iNTAwIiAgICBNaW49IjAuMSIgICAgRGlnaXQ9IjYiICBEZXNjPSJpbml0aWFsIGZvcm1hdGlvbiBwcmVzc3VyZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iSyIgICBBbGlhcz0i5riX6YCP546HIiAgICAgICAgIFVuaXQ9IkRhcmN5IiBEZWZhdWx0PSIwLjAwMSIgTWF4PSIxMCIgICAgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InBlcm1lYWJpbGl0eSIgLz4KICAgIDxQYXJhSXRlbSBOYW1lPSJwaGkiIEFsaWFzPSLlrZTpmpnluqYiICAgICAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuMSIgICBNYXg9IjEiICAgICAgTWluPSIxZS0wNSIgIERpZ2l0PSI2IiAgRGVzYz0icG9yb3NpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkN0aSIgQWxpYXM9Iue7vOWQiOWOi+e8qeezu+aVsCIgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMC4wMDEiIE1heD0iMTAiICAgICBNaW49IjFlLTMwIiAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJ0b3RhbCBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkNmIiAgQWxpYXM9IuWyqeefs+WOi+e8qeezu+aVsCIgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMC4wMDAxIiBNYXg9IjEwIiAgICBNaW49IjFlLTMwIiAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyb2NrIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iU29pIiBBbGlhcz0i5Yid5aeL5ZCr5rK56aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC44IiAgIE1heD0iMSIgICAgICBNaW49IjAiICAgICAgRGlnaXQ9IjYiICBEZXNjPSJpbml0aWFsIG9pbCBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJTd2kiIEFsaWFzPSLliJ3lp4vlkKvmsLTppbHlkozluqYiIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjIiICAgTWF4PSIxIiAgICAgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9ImluaXRpYWwgd2F0ZXIgc2F0dXJhdGlvbiIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLkupXlj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1giICAgIEFsaWFzPSJY5Z2Q5qCHIiAgICAgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwMCIgIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgIERlc2M9IndlbGwgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1kiICAgIEFsaWFzPSJZ5Z2Q5qCHIiAgICAgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwMCIgIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgIERlc2M9IndlbGwgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1J3IiAgIEFsaWFzPSLkupXnrZLljYrlvoQiICAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjAuMSIgICBNYXg9IjEwIiAgICAgTWluPSIwLjAwMSIgIERpZ2l0PSI2IiAgRGVzYz0id2VsbGJvcmUgcmFkaXVzIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1NraW4iIEFsaWFzPSLooajnmq7ns7vmlbAiICAgIFVuaXQ9IiIgICAgIERlZmF1bHQ9IjAiICAgICBNYXg9IjEwMDAiICAgTWluPSItMTAwIiAgIERpZ2l0PSI2IiAgRGVzYz0ic2tpbiBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfQyIgICAgQWxpYXM9IuS6leetkuWCqOmbhuezu+aVsCIgVW5pdD0ibV4zL01QYSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwIiAgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9IndlbGxib3JlIHN0b3JhZ2UgY29lZmZpY2llbnQiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfV2VsbExlbmd0aCIgICAgICAgICBBbGlhcz0i5rC05bmz5q616ZW/5bqmIiAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjIwMCIgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0RyYWluQW5nbGUiICAgICAgICAgQWxpYXM9IuaOkuawtOinkuW6piIgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjAiICAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgZHJhaW4gYW5nbGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfTnVtYmVyT2ZGcmFjdHVyZXMiICBBbGlhcz0i6KOC57yd5pWw6YePIiAgICAgVW5pdD0iIiAgICAgRGVmYXVsdD0iMTUiICAgTWF4PSIxMDAwIiAgTWluPSIyIiAgICAgRGlnaXQ9IjAiIERlc2M9Im51bWJlciBvZiBmcmFjdHVyZXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfRnJhY3R1cmVIYWxmTGVuZ3RoIiBBbGlhcz0i6KOC57yd5Y2K6ZW/IiAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iNTAiICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGhhbGYtbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0ZyYWN0dXJlQW5nbGUiICAgICAgQWxpYXM9Iuijgue8neinkuW6piIgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjkwIiAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBhbmdsZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19EZmMiICAgICAgICAgICAgICAgIEFsaWFzPSLoo4LnvJ3lr7zmtYHog73lipsiIFVuaXQ9Im1kLm0iIERlZmF1bHQ9IjEwMDAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBjb25kdWN0aXZpdHkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i54us56uL6KOC57yd5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9YMCIgQWxpYXM9Iui1t+eCuVgwIiAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgc3RhcnQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX1kwIiBBbGlhcz0i6LW354K5WTAiICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBzdGFydCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWDEiIEFsaWFzPSLnu4jngrlYMSIgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGVuZCBYIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWTEiIEFsaWFzPSLnu4jngrlZMSIgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGVuZCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfRkMiIEFsaWFzPSLlr7zmtYHog73lipsiIFVuaXQ9Im1kLm0iIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgY29uZHVjdGl2aXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuaWreWxguWPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZUX1gwIiBBbGlhcz0i6LW354K5WDAiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZmF1bHQgc3RhcnQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMCIgQWxpYXM9Iui1t+eCuVkwIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZhdWx0IHN0YXJ0IFkgY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRlRfWDEiIEFsaWFzPSLnu4jngrlYMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMSIgQWxpYXM9Iue7iOeCuVkxIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZhdWx0IGVuZCBZIGNvb3JkaW5hdGUiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i55+p5b2i6L6555WM5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1pbiIgQWxpYXM9IuW3pui+ueeVjFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1pbiIgQWxpYXM9IuS4i+i+ueeVjFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1heCIgQWxpYXM9IuWPs+i+ueeVjFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1heCIgQWxpYXM9IuS4iui+ueeVjFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLlnIblvaLovrnnlYzlj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJYIiBBbGlhcz0i5ZyG5b+DWCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJjaXJjbGUgYm91bmRhcnkgY2VudGVyIFgiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkJDX0NlbnRlclkiIEFsaWFzPSLlnIblv4NZIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImNpcmNsZSBib3VuZGFyeSBjZW50ZXIgWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQkNfUmFkaXVzIiAgQWxpYXM9IuWNiuW+hCIgIFVuaXQ9Im0iIERlZmF1bHQ9IjEiIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiICBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IHJhZGl1cyIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLlpJrovrnlvaLovrnnlYzngrnlj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9YIiBBbGlhcz0iWOWdkOaghyIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9ZIiBBbGlhcz0iWeWdkOaghyIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBZIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IuWMuuWfn+WPguaVsCI+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJfTGVha2FnZSIgQWxpYXM9IuazhOmcsuezu+aVsCIgVW5pdD0iIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiIERpZ2l0PSI2IiBEZXNjPSJyZWdpb24gbGVha2FnZSBjb2VmZmljaWVudCIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSLmoIforrDljLrln5/lj4LmlbAiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21XIiAgICAgICBBbGlhcz0i5YKo6IO95q+UIiBVbml0PSIiIERlZmF1bHQ9IjEiIE1heD0iMSIgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiIERlc2M9InN0b3JhZ2UgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX0NvbUtyIiAgICAgIEFsaWFzPSLmtYHluqbmr5QiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxMDAwIiBNaW49IjAuMDAxIiBEaWdpdD0iNiIgRGVzYz0ibW9iaWxpdHkgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX05ldFRvR3Jvc3MiIEFsaWFzPSLlh4Dmr5vmr5QiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxIiAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0ibmV0LXRvLWdyb3NzIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8IS0tIOe7k+aenOmdouadv+WPguaVsOWPqueUsSByZWJ1aWxkUmVzdWx0UGFyYXMoKSDpgInmi6nvvIzkuI3kvJrlh7rnjrDlnKjlt6bkvqfpnaLmnb/kuK3jgIIgLS0+DQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0i57uT5p6c5Y+C5pWwIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfUnciICAgQWxpYXM9IuS6leW+hCIgICAgICAgICBVbml0PSJtIiAgICAgIERlZmF1bHQ9IjAuMSIgICAgTWF4PSIxMCIgICAgTWluPSIwLjAwMSIgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdlbGxib3JlIHJhZGl1cyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfRGZjIiAgQWxpYXM9Iuijgue8neWvvOa1geiDveWKmyIgVW5pdD0ibWQubSIgICBEZWZhdWx0PSIxMDAwIiAgIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBmcmFjdHVyZSBjb25kdWN0aXZpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX1NraW4iIEFsaWFzPSLooajnmq7ns7vmlbAiICAgICBVbml0PSIiICAgICAgIERlZmF1bHQ9IjAiICAgICAgTWF4PSIxMDAwIiAgTWluPSItMTAwIiAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHNraW4gZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfV19DIiAgICBBbGlhcz0i5LqV562S5YKo6ZuG57O75pWwIiBVbml0PSJtXjMvTVBhIiBEZWZhdWx0PSIwIiAgICAgIE1heD0iMTAwMCIgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPg0KDQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9QaSIgIEFsaWFzPSLliJ3lp4vlnLDlsYLljovlipsiICAgVW5pdD0iTVBhIiAgIERlZmF1bHQ9IjQwIiAgICAgTWF4PSI1MDAiICAgTWluPSIwLjEiICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgaW5pdGlhbCBmb3JtYXRpb24gcHJlc3N1cmUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9LIiAgIEFsaWFzPSLmuJfpgI/njociICAgICAgICAgVW5pdD0iRGFyY3kiIERlZmF1bHQ9IjAuMDAxIiAgTWF4PSIxMCIgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcGVybWVhYmlsaXR5IiAvPgogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9oIiAgIEFsaWFzPSLlgqjlsYLljprluqYiICAgICAgIFVuaXQ9Im0iICAgICBEZWZhdWx0PSIxMCIgICAgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHJlc2Vydm9pciB0aGlja25lc3MiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9waGkiIEFsaWFzPSLlrZTpmpnluqYiICAgICAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuMSIgICAgTWF4PSIxIiAgICAgTWluPSIxZS0wNSIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcG9yb3NpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9DdGkiIEFsaWFzPSLnu7zlkIjljovnvKnns7vmlbAiICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAxIiAgTWF4PSIxMCIgICAgTWluPSIxZS0zMCIgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgdG90YWwgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2YiICBBbGlhcz0i5bKp55+z5Y6L57yp57O75pWwIiAgIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwLjAwMDEiIE1heD0iMTAiICAgIE1pbj0iMWUtMzAiIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0icmVzdWx0IHJvY2sgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU29pIiBBbGlhcz0i5Yid5aeL5ZCr5rK56aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC44IiAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIG9pbCBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU2dpIiBBbGlhcz0i5Yid5aeL5ZCr5rCU6aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGdhcyBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU3dpIiBBbGlhcz0i5Yid5aeL5ZCr5rC06aWx5ZKM5bqmIiBVbml0PSIiICAgICAgRGVmYXVsdD0iMC4yIiAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIHdhdGVyIHNhdHVyYXRpb24iIC8+DQoNCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0JvIiAgIEFsaWFzPSLmsrnkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBvaWwgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVvIiBBbGlhcz0i5rK555u46buP5bqmIiAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBvaWwgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ28iICAgQWxpYXM9IuayueWOi+e8qeezu+aVsCIgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgb2lsIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0J3IiAgIEFsaWFzPSLmsLTkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3YXRlciBmb3JtYXRpb24gdm9sdW1lIGZhY3RvciIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X01pdXciIEFsaWFzPSLmsLTnm7jpu4/luqYiICAgVW5pdD0ibVBhLnMiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMWUtMzAiIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdhdGVyIHZpc2Nvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0N3IiAgIEFsaWFzPSLmsLTljovnvKnns7vmlbAiIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0icmVzdWx0IHdhdGVyIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0JnIiAgIEFsaWFzPSLmsJTkvZPnp6/ns7vmlbAiIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVnIiBBbGlhcz0i5rCU55u46buP5bqmIiAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2ciICAgQWxpYXM9IuawlOWOi+e8qeezu+aVsCIgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgZ2FzIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCjwvUm9vdD4NCg== \ No newline at end of file diff --git a/Bin/Config/Common/ModelParaDefinesNM_en.dat b/Bin/Config/Common/ModelParaDefinesNM_en.dat index a1226c0..cac7ac4 100644 --- a/Bin/Config/Common/ModelParaDefinesNM_en.dat +++ b/Bin/Config/Common/ModelParaDefinesNM_en.dat @@ -1 +1 @@ -PD94bWwgdmVycup_mpac2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCg0KPCEtLSoqKioqKioqKioqKiwzyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCjwhLS0gICAgVGhpcyBmaWxlIGRlZmluZXMgcGFyYW1ldGVycyBmb3IgdGhlIFdUQUkgTnVtZXJpY2FsIChOTSkgICAgLS0+DQo8IS0tICAgIG1vZHVsZS4gT25seSByZXNlcnZvaXIsIHdlbGwsIGFuZCBnZW9tZXRyeSBwYXJhbWV0ZXJzICAgICAgICAtLT4NCjwhLS0gICAgcmVxdWlyZWQgYnkgdGhlIFBFQkkgc29sdmVyIGFyZSBpbmNsdWRlZC4gICAgICAgICAgICAgICAgICAgIC0tPg0KPCEtLSAgICBEYXRlOiAyMDI2MDYxNSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0+DQo8IS0tKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi0tPg0KDQo8IS0tDQogICAgUGFyYW1ldGVyIHNjb3BlOiByZXNlcnZvaXIsIHdlbGwsIGFuZCBnZW9tZXRyeSBwYXJhbWV0ZXJzIHJlcXVpcmVkIGJ5IHRoZSBQRUJJIHNvbHZlcg0KICAgIENvcnJlc3BvbmRzIHRvIEhYX05XVE1fTU9ERUxfSU5QVVQgc3RydWN0IGZpZWxkcw0KICAgIExvYWRlZCB2aWEgX3BhcmFIZWxwZXItPmxvYWRQYXJhRGVmaW5lcygpLCBzS2V5ID0gc19ObV9TZXJpZQ0KLS0+DQoNCjxSb290Pg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iUmVzZXJ2b2lyIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJoIiAgIEFsaWFzPSJSZXNlcnZvaXIgVGhpY2tuZXNzIiAgICAgICAgIFVuaXQ9Im0iICAgICBEZWZhdWx0PSIxMCIgICAgTWF4PSIxMDAwMCIgIE1pbj0iMWUtMDUiICBEaWdpdD0iMyIgIERlc2M9InJlc2Vydm9pciB0aGlja25lc3MiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlBpIiAgQWxpYXM9IkluaXRpYWwgUHJlc3N1cmUiICAgICAgICAgICAgVW5pdD0iTVBhIiAgIERlZmF1bHQ9IjQwIiAgICBNYXg9IjUwMCIgICAgTWluPSIwLjEiICAgIERpZ2l0PSI1IiAgRGVzYz0iaW5pdGlhbCBmb3JtYXRpb24gcHJlc3N1cmUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IksiICAgQWxpYXM9IlBlcm1lYWJpbGl0eSIgICAgICAgICAgICAgICAgVW5pdD0ibUQiICAgIERlZmF1bHQ9IjEiICAgICBNYXg9IjEwMDAwIiAgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0icGVybWVhYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJwaGkiIEFsaWFzPSJQb3Jvc2l0eSIgICAgICAgICAgICAgICAgICAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjEiICAgTWF4PSIxIiAgICAgIE1pbj0iMWUtMDUiICBEaWdpdD0iNCIgIERlc2M9InBvcm9zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJDdGkiIEFsaWFzPSJUb3RhbCBDb21wcmVzc2liaWxpdHkiICAgICAgIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwLjAwMSIgTWF4PSIxMCIgICAgIE1pbj0iMWUtMzAiICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InRvdGFsIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQ2YiICBBbGlhcz0iUm9jayBDb21wcmVzc2liaWxpdHkiICAgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMC4wMDAxIiBNYXg9IjEwIiAgICBNaW49IjFlLTMwIiAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyb2NrIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iU29pIiBBbGlhcz0iSW5pdGlhbCBPaWwgU2F0dXJhdGlvbiIgICAgICBVbml0PSIiICAgICAgRGVmYXVsdD0iMC44IiAgIE1heD0iMSIgICAgICBNaW49IjAiICAgICAgRGlnaXQ9IjYiICBEZXNjPSJpbml0aWFsIG9pbCBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJTd2kiIEFsaWFzPSJJbml0aWFsIFdhdGVyIFNhdHVyYXRpb24iICAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjIiICAgTWF4PSIxIiAgICAgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9ImluaXRpYWwgd2F0ZXIgc2F0dXJhdGlvbiIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJXZWxsIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1giICAgIEFsaWFzPSJYIENvb3JkaW5hdGUiICAgICAgICAgICAgVW5pdD0ibSIgICAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwMCIgIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgIERlc2M9IndlbGwgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1kiICAgIEFsaWFzPSJZIENvb3JkaW5hdGUiICAgICAgICAgICAgVW5pdD0ibSIgICAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwMCIgIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgIERlc2M9IndlbGwgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1J3IiAgIEFsaWFzPSJXZWxsYm9yZSBSYWRpdXMiICAgICAgICAgVW5pdD0ibSIgICAgICBEZWZhdWx0PSIwLjEiICAgTWF4PSIxMCIgICAgIE1pbj0iMC4wMDEiICBEaWdpdD0iNiIgIERlc2M9IndlbGxib3JlIHJhZGl1cyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19Ta2luIiBBbGlhcz0iU2tpbiBGYWN0b3IiICAgICAgICAgICAgIFVuaXQ9IiIgICAgICAgRGVmYXVsdD0iMCIgICAgIE1heD0iMTAwMCIgICBNaW49Ii0xMDAiICAgRGlnaXQ9IjYiICBEZXNjPSJza2luIGZhY3RvciIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19DIiAgICBBbGlhcz0iV2VsbGJvcmUgU3RvcmFnZSBDb2VmZiIgIFVuaXQ9Im1eMy9NUGEiIERlZmF1bHQ9IjAiICAgICBNYXg9IjEwMDAiICAgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0id2VsbGJvcmUgc3RvcmFnZSBjb2VmZmljaWVudCIgLz4KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1dlbGxMZW5ndGgiICAgICAgICAgQWxpYXM9Ikhvcml6b250YWwgV2VsbCBMZW5ndGgiIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjIwMCIgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0RyYWluQW5nbGUiICAgICAgICAgQWxpYXM9IkRyYWluIEFuZ2xlIiAgICAgICAgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjAiICAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgZHJhaW4gYW5nbGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfTnVtYmVyT2ZGcmFjdHVyZXMiICBBbGlhcz0iTnVtYmVyIG9mIEZyYWN0dXJlcyIgICAgVW5pdD0iIiAgICAgRGVmYXVsdD0iMTUiICAgTWF4PSIxMDAwIiAgTWluPSIyIiAgICAgRGlnaXQ9IjAiIERlc2M9Im51bWJlciBvZiBmcmFjdHVyZXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfRnJhY3R1cmVIYWxmTGVuZ3RoIiBBbGlhcz0iRnJhY3R1cmUgSGFsZi1MZW5ndGgiICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iNTAiICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGhhbGYtbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0ZyYWN0dXJlQW5nbGUiICAgICAgQWxpYXM9IkZyYWN0dXJlIEFuZ2xlIiAgICAgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjkwIiAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBhbmdsZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19EZmMiICAgICAgICAgICAgICAgIEFsaWFzPSJGcmFjdHVyZSBDb25kdWN0aXZpdHkiICBVbml0PSJtZC5tIiBEZWZhdWx0PSIxMDAwIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgY29uZHVjdGl2aXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IkluZGVwZW5kZW50IEZyYWN0dXJlIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX1gwIiBBbGlhcz0iU3RhcnQgWDAiICAgICAgICAgICAgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIHN0YXJ0IFggY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9ZMCIgQWxpYXM9IlN0YXJ0IFkwIiAgICAgICAgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBzdGFydCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWDEiIEFsaWFzPSJFbmQgWDEiICAgICAgICAgICAgICAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgZW5kIFggY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9ZMSIgQWxpYXM9IkVuZCBZMSIgICAgICAgICAgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBlbmQgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX0ZDIiBBbGlhcz0iRnJhY3R1cmUgQ29uZHVjdGl2aXR5IiBVbml0PSJtZC5tIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICAgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGNvbmR1Y3Rpdml0eSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJGYXVsdCBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRlRfWDAiIEFsaWFzPSJTdGFydCBYMCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBzdGFydCBYIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZUX1kwIiBBbGlhcz0iU3RhcnQgWTAiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZmF1bHQgc3RhcnQgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9YMSIgQWxpYXM9IkVuZCBYMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMSIgQWxpYXM9IkVuZCBZMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWSBjb29yZGluYXRlIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IlJlY3RhbmdsZSBCb3VuZGFyeSBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1pbiIgQWxpYXM9IkxlZnQgQm91bmRhcnkgWCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJyZWN0YW5nbGUgYm91bmRhcnkgbWluaW11bSBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUl9ZTWluIiBBbGlhcz0iQm90dG9tIEJvdW5kYXJ5IFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1heCIgQWxpYXM9IlJpZ2h0IEJvdW5kYXJ5IFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1heCIgQWxpYXM9IlRvcCBCb3VuZGFyeSBZIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9InJlY3RhbmdsZSBib3VuZGFyeSBtYXhpbXVtIFkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iQ2lyY2xlIEJvdW5kYXJ5IFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJYIiBBbGlhcz0iQ2VudGVyIFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IGNlbnRlciBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJZIiBBbGlhcz0iQ2VudGVyIFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IGNlbnRlciBZIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19SYWRpdXMiICBBbGlhcz0iUmFkaXVzIiAgIFVuaXQ9Im0iIERlZmF1bHQ9IjEiIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiICBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IHJhZGl1cyIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJQb2x5Z29uIEJvdW5kYXJ5IFZlcnRleCBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlBfWCIgQWxpYXM9IlggQ29vcmRpbmF0ZSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9ZIiBBbGlhcz0iWSBDb29yZGluYXRlIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9InBvbHlnb24gYm91bmRhcnkgdmVydGV4IFkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iUmVnaW9uIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSX0xlYWthZ2UiIEFsaWFzPSJMZWFrYWdlIENvZWZmaWNpZW50IiBVbml0PSIiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgRGlnaXQ9IjYiIERlc2M9InJlZ2lvbiBsZWFrYWdlIGNvZWZmaWNpZW50IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IlJlZ2lvbiBNYXJrIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21XIiAgICAgICBBbGlhcz0iU3RvcmFnZSBSYXRpbyIgVW5pdD0iIiBEZWZhdWx0PSIxIiBNYXg9IjEiICAgIE1pbj0iMCIgICAgIERpZ2l0PSI2IiBEZXNjPSJzdG9yYWdlIHJhdGlvIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21LciIgICAgICBBbGlhcz0iTW9iaWxpdHkgUmF0aW8iIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxMDAwIiBNaW49IjAuMDAxIiBEaWdpdD0iNiIgRGVzYz0ibW9iaWxpdHkgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX05ldFRvR3Jvc3MiIEFsaWFzPSJOZXQtdG8tR3Jvc3MiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxIiAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0ibmV0LXRvLWdyb3NzIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8IS0tIFJlc3VsdCBwYXJhbWV0ZXJzIGFyZSBzZWxlY3RlZCBvbmx5IGJ5IHJlYnVpbGRSZXN1bHRQYXJhcygpLiAtLT4NCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJSZXN1bHQgUGFyYW1ldGVycyI+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX1J3IiAgIEFsaWFzPSJXZWxsIFJhZGl1cyIgICAgICAgICAgICAgVW5pdD0ibSIgICAgICBEZWZhdWx0PSIwLjEiICAgIE1heD0iMTAiICAgIE1pbj0iMC4wMDEiICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSByYWRpdXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX0RmYyIgIEFsaWFzPSJGcmFjdHVyZSBDb25kdWN0aXZpdHkiICBVbml0PSJtZC5tIiAgIERlZmF1bHQ9IjEwMDAiICAgTWF4PSIxZSszMCIgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IGZyYWN0dXJlIGNvbmR1Y3Rpdml0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfU2tpbiIgQWxpYXM9IlNraW4gRmFjdG9yIiAgICAgICAgICAgIFVuaXQ9IiIgICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEwMDAiICBNaW49Ii0xMDAiICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgc2tpbiBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX0MiICAgIEFsaWFzPSJXZWxsYm9yZSBTdG9yYWdlIENvZWZmIiBVbml0PSJtXjMvTVBhIiBEZWZhdWx0PSIwIiAgICAgIE1heD0iMTAwMCIgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPgoNCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1BpIiAgQWxpYXM9IkluaXRpYWwgRm9ybWF0aW9uIFByZXNzdXJlIiBVbml0PSJNUGEiICAgRGVmYXVsdD0iNDAiICAgICBNYXg9IjUwMCIgICBNaW49IjAuMSIgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGZvcm1hdGlvbiBwcmVzc3VyZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0siICAgQWxpYXM9IlBlcm1lYWJpbGl0eSIgICAgICAgICAgICAgICBVbml0PSJtRCIgICAgRGVmYXVsdD0iMSIgICAgICBNYXg9IjEwMDAwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBwZXJtZWFiaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9oIiAgIEFsaWFzPSJSZXNlcnZvaXIgVGhpY2tuZXNzIiAgICAgICAgVW5pdD0ibSIgICAgIERlZmF1bHQ9IjEwIiAgICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcmVzZXJ2b2lyIHRoaWNrbmVzcyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X3BoaSIgQWxpYXM9IlBvcm9zaXR5IiAgICAgICAgICAgICAgICAgICBVbml0PSIiICAgICAgRGVmYXVsdD0iMC4xIiAgICBNYXg9IjEiICAgICBNaW49IjFlLTA1IiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBwb3Jvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0N0aSIgQWxpYXM9IlRvdGFsIENvbXByZXNzaWJpbGl0eSIgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMC4wMDEiICBNYXg9IjEwIiAgICBNaW49IjFlLTMwIiBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCB0b3RhbCBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9DZiIgIEFsaWFzPSJSb2NrIENvbXByZXNzaWJpbGl0eSIgICAgICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAwMSIgTWF4PSIxMCIgICAgTWluPSIxZS0zMCIgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgcm9jayBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9Tb2kiIEFsaWFzPSJJbml0aWFsIE9pbCBTYXR1cmF0aW9uIiAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuOCIgICAgTWF4PSIxIiAgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgaW5pdGlhbCBvaWwgc2F0dXJhdGlvbiIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1NnaSIgQWxpYXM9IkluaXRpYWwgR2FzIFNhdHVyYXRpb24iICAgICBVbml0PSIiICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGdhcyBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU3dpIiBBbGlhcz0iSW5pdGlhbCBXYXRlciBTYXR1cmF0aW9uIiAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjIiICAgIE1heD0iMSIgICAgIE1pbj0iMCIgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IGluaXRpYWwgd2F0ZXIgc2F0dXJhdGlvbiIgLz4NCg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQm8iICAgQWxpYXM9Ik9pbCBGb3JtYXRpb24gVm9sdW1lIEZhY3RvciIgICBVbml0PSJtMy9tMyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgb2lsIGZvcm1hdGlvbiB2b2x1bWUgZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfTWl1byIgQWxpYXM9Ik9pbCBWaXNjb3NpdHkiICAgICAgICAgICAgICAgICBVbml0PSJtUGEucyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIxZS0zMCIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgb2lsIHZpc2Nvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0NvIiAgIEFsaWFzPSJPaWwgQ29tcHJlc3NpYmlsaXR5IiAgICAgICAgICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgb2lsIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0J3IiAgIEFsaWFzPSJXYXRlciBGb3JtYXRpb24gVm9sdW1lIEZhY3RvciIgVW5pdD0ibTMvbTMiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdhdGVyIGZvcm1hdGlvbiB2b2x1bWUgZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfTWl1dyIgQWxpYXM9IldhdGVyIFZpc2Nvc2l0eSIgICAgICAgICAgICAgICBVbml0PSJtUGEucyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIxZS0zMCIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgd2F0ZXIgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ3ciICAgQWxpYXM9IldhdGVyIENvbXByZXNzaWJpbGl0eSIgICAgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMCIgTWF4PSIxZSszMCIgTWluPSIwIiAgICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCB3YXRlciBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9CZyIgICBBbGlhcz0iR2FzIEZvcm1hdGlvbiBWb2x1bWUgRmFjdG9yIiAgIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVnIiBBbGlhcz0iR2FzIFZpc2Nvc2l0eSIgICAgICAgICAgICAgICAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2ciICAgQWxpYXM9IkdhcyBDb21wcmVzc2liaWxpdHkiICAgICAgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMCIgTWF4PSIxZSszMCIgTWluPSIwIiAgICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCBnYXMgY29tcHJlc3NpYmlsaXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KPC9Sb290Pg0K \ No newline at end of file +PD94bWwgdmVycup_mpac2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCg0KPCEtLSoqKioqKioqKioqKiwzyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiotLT4NCjwhLS0gICAgVGhpcyBmaWxlIGRlZmluZXMgcGFyYW1ldGVycyBmb3IgdGhlIFdUQUkgTnVtZXJpY2FsIChOTSkgICAgLS0+DQo8IS0tICAgIG1vZHVsZS4gT25seSByZXNlcnZvaXIsIHdlbGwsIGFuZCBnZW9tZXRyeSBwYXJhbWV0ZXJzICAgICAgICAtLT4NCjwhLS0gICAgcmVxdWlyZWQgYnkgdGhlIFBFQkkgc29sdmVyIGFyZSBpbmNsdWRlZC4gICAgICAgICAgICAgICAgICAgIC0tPg0KPCEtLSAgICBEYXRlOiAyMDI2MDYxNSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0+DQo8IS0tKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi0tPg0KDQo8IS0tDQogICAgUGFyYW1ldGVyIHNjb3BlOiByZXNlcnZvaXIsIHdlbGwsIGFuZCBnZW9tZXRyeSBwYXJhbWV0ZXJzIHJlcXVpcmVkIGJ5IHRoZSBQRUJJIHNvbHZlcg0KICAgIENvcnJlc3BvbmRzIHRvIEhYX05XVE1fTU9ERUxfSU5QVVQgc3RydWN0IGZpZWxkcw0KICAgIExvYWRlZCB2aWEgX3BhcmFIZWxwZXItPmxvYWRQYXJhRGVmaW5lcygpLCBzS2V5ID0gc19ObV9TZXJpZQ0KLS0+DQoNCjxSb290Pg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iUmVzZXJ2b2lyIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJoIiAgIEFsaWFzPSJSZXNlcnZvaXIgVGhpY2tuZXNzIiAgICAgICAgIFVuaXQ9Im0iICAgICBEZWZhdWx0PSIxMCIgICAgTWF4PSIxMDAwMCIgIE1pbj0iMWUtMDUiICBEaWdpdD0iMyIgIERlc2M9InJlc2Vydm9pciB0aGlja25lc3MiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlBpIiAgQWxpYXM9IkluaXRpYWwgUHJlc3N1cmUiICAgICAgICAgICAgVW5pdD0iTVBhIiAgIERlZmF1bHQ9IjQwIiAgICBNYXg9IjUwMCIgICAgTWluPSIwLjEiICAgIERpZ2l0PSI1IiAgRGVzYz0iaW5pdGlhbCBmb3JtYXRpb24gcHJlc3N1cmUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IksiICAgQWxpYXM9IlBlcm1lYWJpbGl0eSIgICAgICAgICAgICAgICAgVW5pdD0iRGFyY3kiIERlZmF1bHQ9IjAuMDAxIiBNYXg9IjEwIiAgICAgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0icGVybWVhYmlsaXR5IiAvPgogICAgPFBhcmFJdGVtIE5hbWU9InBoaSIgQWxpYXM9IlBvcm9zaXR5IiAgICAgICAgICAgICAgICAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuMSIgICBNYXg9IjEiICAgICAgTWluPSIxZS0wNSIgIERpZ2l0PSI0IiAgRGVzYz0icG9yb3NpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkN0aSIgQWxpYXM9IlRvdGFsIENvbXByZXNzaWJpbGl0eSIgICAgICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAxIiBNYXg9IjEwIiAgICAgTWluPSIxZS0zMCIgIERpZ2l0PSIxMCIgU2NpZW50aWZpYz0iMSIgRGVzYz0idG90YWwgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJDZiIgIEFsaWFzPSJSb2NrIENvbXByZXNzaWJpbGl0eSIgICAgICAgIFVuaXQ9IjEvTVBhIiBEZWZhdWx0PSIwLjAwMDEiIE1heD0iMTAiICAgIE1pbj0iMWUtMzAiICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJvY2sgY29tcHJlc3NpYmlsaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJTb2kiIEFsaWFzPSJJbml0aWFsIE9pbCBTYXR1cmF0aW9uIiAgICAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjgiICAgTWF4PSIxIiAgICAgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9ImluaXRpYWwgb2lsIHNhdHVyYXRpb24iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlN3aSIgQWxpYXM9IkluaXRpYWwgV2F0ZXIgU2F0dXJhdGlvbiIgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuMiIgICBNYXg9IjEiICAgICAgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0iaW5pdGlhbCB3YXRlciBzYXR1cmF0aW9uIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IldlbGwgUGFyYW1ldGVycyI+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfWCIgICAgQWxpYXM9IlggQ29vcmRpbmF0ZSIgICAgICAgICAgICBVbml0PSJtIiAgICAgIERlZmF1bHQ9IjAiICAgICBNYXg9IjEwMDAwIiAgTWluPSItMTAwMDAiIERpZ2l0PSI2IiAgRGVzYz0id2VsbCBYIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfWSIgICAgQWxpYXM9IlkgQ29vcmRpbmF0ZSIgICAgICAgICAgICBVbml0PSJtIiAgICAgIERlZmF1bHQ9IjAiICAgICBNYXg9IjEwMDAwIiAgTWluPSItMTAwMDAiIERpZ2l0PSI2IiAgRGVzYz0id2VsbCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfUnciICAgQWxpYXM9IldlbGxib3JlIFJhZGl1cyIgICAgICAgICBVbml0PSJtIiAgICAgIERlZmF1bHQ9IjAuMSIgICBNYXg9IjEwIiAgICAgTWluPSIwLjAwMSIgIERpZ2l0PSI2IiAgRGVzYz0id2VsbGJvcmUgcmFkaXVzIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1NraW4iIEFsaWFzPSJTa2luIEZhY3RvciIgICAgICAgICAgICAgVW5pdD0iIiAgICAgICBEZWZhdWx0PSIwIiAgICAgTWF4PSIxMDAwIiAgIE1pbj0iLTEwMCIgICBEaWdpdD0iNiIgIERlc2M9InNraW4gZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0MiICAgIEFsaWFzPSJXZWxsYm9yZSBTdG9yYWdlIENvZWZmIiAgVW5pdD0ibV4zL01QYSIgRGVmYXVsdD0iMCIgICAgIE1heD0iMTAwMCIgICBNaW49IjAiICAgICAgRGlnaXQ9IjYiICBEZXNjPSJ3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX1dlbGxMZW5ndGgiICAgICAgICAgQWxpYXM9Ikhvcml6b250YWwgV2VsbCBMZW5ndGgiIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjIwMCIgIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0RyYWluQW5nbGUiICAgICAgICAgQWxpYXM9IkRyYWluIEFuZ2xlIiAgICAgICAgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjAiICAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJob3Jpem9udGFsIHdlbGwgZHJhaW4gYW5nbGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfTnVtYmVyT2ZGcmFjdHVyZXMiICBBbGlhcz0iTnVtYmVyIG9mIEZyYWN0dXJlcyIgICAgVW5pdD0iIiAgICAgRGVmYXVsdD0iMTUiICAgTWF4PSIxMDAwIiAgTWluPSIyIiAgICAgRGlnaXQ9IjAiIERlc2M9Im51bWJlciBvZiBmcmFjdHVyZXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IldfRnJhY3R1cmVIYWxmTGVuZ3RoIiBBbGlhcz0iRnJhY3R1cmUgSGFsZi1MZW5ndGgiICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iNTAiICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGhhbGYtbGVuZ3RoIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJXX0ZyYWN0dXJlQW5nbGUiICAgICAgQWxpYXM9IkZyYWN0dXJlIEFuZ2xlIiAgICAgICAgIFVuaXQ9Im8iICAgIERlZmF1bHQ9IjkwIiAgIE1heD0iMzYwIiAgIE1pbj0iLTM2MCIgIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBhbmdsZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iV19EZmMiICAgICAgICAgICAgICAgIEFsaWFzPSJGcmFjdHVyZSBDb25kdWN0aXZpdHkiICBVbml0PSJtZC5tIiBEZWZhdWx0PSIxMDAwIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgY29uZHVjdGl2aXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IkluZGVwZW5kZW50IEZyYWN0dXJlIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX1gwIiBBbGlhcz0iU3RhcnQgWDAiICAgICAgICAgICAgICBVbml0PSJtIiAgICBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIHN0YXJ0IFggY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9ZMCIgQWxpYXM9IlN0YXJ0IFkwIiAgICAgICAgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBzdGFydCBZIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZfWDEiIEFsaWFzPSJFbmQgWDEiICAgICAgICAgICAgICAgIFVuaXQ9Im0iICAgIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZnJhY3R1cmUgZW5kIFggY29vcmRpbmF0ZSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRl9ZMSIgQWxpYXM9IkVuZCBZMSIgICAgICAgICAgICAgICAgVW5pdD0ibSIgICAgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmcmFjdHVyZSBlbmQgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGX0ZDIiBBbGlhcz0iRnJhY3R1cmUgQ29uZHVjdGl2aXR5IiBVbml0PSJtZC5tIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICAgRGlnaXQ9IjYiIERlc2M9ImZyYWN0dXJlIGNvbmR1Y3Rpdml0eSIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJGYXVsdCBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iRlRfWDAiIEFsaWFzPSJTdGFydCBYMCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBzdGFydCBYIGNvb3JkaW5hdGUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IkZUX1kwIiBBbGlhcz0iU3RhcnQgWTAiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iZmF1bHQgc3RhcnQgWSBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9YMSIgQWxpYXM9IkVuZCBYMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWCBjb29yZGluYXRlIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJGVF9ZMSIgQWxpYXM9IkVuZCBZMSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJmYXVsdCBlbmQgWSBjb29yZGluYXRlIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IlJlY3RhbmdsZSBCb3VuZGFyeSBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1pbiIgQWxpYXM9IkxlZnQgQm91bmRhcnkgWCIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJyZWN0YW5nbGUgYm91bmRhcnkgbWluaW11bSBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUl9ZTWluIiBBbGlhcz0iQm90dG9tIEJvdW5kYXJ5IFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1pbmltdW0gWSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWE1heCIgQWxpYXM9IlJpZ2h0IEJvdW5kYXJ5IFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0icmVjdGFuZ2xlIGJvdW5kYXJ5IG1heGltdW0gWCIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlJfWU1heCIgQWxpYXM9IlRvcCBCb3VuZGFyeSBZIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9InJlY3RhbmdsZSBib3VuZGFyeSBtYXhpbXVtIFkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iQ2lyY2xlIEJvdW5kYXJ5IFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJYIiBBbGlhcz0iQ2VudGVyIFgiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IGNlbnRlciBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19DZW50ZXJZIiBBbGlhcz0iQ2VudGVyIFkiIFVuaXQ9Im0iIERlZmF1bHQ9IjAiIE1heD0iMTAwMDAiIE1pbj0iLTEwMDAwIiBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IGNlbnRlciBZIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCQ19SYWRpdXMiICBBbGlhcz0iUmFkaXVzIiAgIFVuaXQ9Im0iIERlZmF1bHQ9IjEiIE1heD0iMTAwMDAiIE1pbj0iMWUtMDUiICBEaWdpdD0iNiIgRGVzYz0iY2lyY2xlIGJvdW5kYXJ5IHJhZGl1cyIgLz4NCiAgPC9QYXJhR3JvdXA+DQoNCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJQb2x5Z29uIEJvdW5kYXJ5IFZlcnRleCBQYXJhbWV0ZXJzIj4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iQlBfWCIgQWxpYXM9IlggQ29vcmRpbmF0ZSIgVW5pdD0ibSIgRGVmYXVsdD0iMCIgTWF4PSIxMDAwMCIgTWluPSItMTAwMDAiIERpZ2l0PSI2IiBEZXNjPSJwb2x5Z29uIGJvdW5kYXJ5IHZlcnRleCBYIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJCUF9ZIiBBbGlhcz0iWSBDb29yZGluYXRlIiBVbml0PSJtIiBEZWZhdWx0PSIwIiBNYXg9IjEwMDAwIiBNaW49Ii0xMDAwMCIgRGlnaXQ9IjYiIERlc2M9InBvbHlnb24gYm91bmRhcnkgdmVydGV4IFkiIC8+DQogIDwvUGFyYUdyb3VwPg0KDQogIDxQYXJhR3JvdXAgTmFtZT0iIiBBbGlhcz0iUmVnaW9uIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSX0xlYWthZ2UiIEFsaWFzPSJMZWFrYWdlIENvZWZmaWNpZW50IiBVbml0PSIiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgRGlnaXQ9IjYiIERlc2M9InJlZ2lvbiBsZWFrYWdlIGNvZWZmaWNpZW50IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8UGFyYUdyb3VwIE5hbWU9IiIgQWxpYXM9IlJlZ2lvbiBNYXJrIFBhcmFtZXRlcnMiPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21XIiAgICAgICBBbGlhcz0iU3RvcmFnZSBSYXRpbyIgVW5pdD0iIiBEZWZhdWx0PSIxIiBNYXg9IjEiICAgIE1pbj0iMCIgICAgIERpZ2l0PSI2IiBEZXNjPSJzdG9yYWdlIHJhdGlvIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSTV9Db21LciIgICAgICBBbGlhcz0iTW9iaWxpdHkgUmF0aW8iIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxMDAwIiBNaW49IjAuMDAxIiBEaWdpdD0iNiIgRGVzYz0ibW9iaWxpdHkgcmF0aW8iIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJNX05ldFRvR3Jvc3MiIEFsaWFzPSJOZXQtdG8tR3Jvc3MiIFVuaXQ9IiIgRGVmYXVsdD0iMSIgTWF4PSIxIiAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgRGVzYz0ibmV0LXRvLWdyb3NzIiAvPg0KICA8L1BhcmFHcm91cD4NCg0KICA8IS0tIFJlc3VsdCBwYXJhbWV0ZXJzIGFyZSBzZWxlY3RlZCBvbmx5IGJ5IHJlYnVpbGRSZXN1bHRQYXJhcygpLiAtLT4NCiAgPFBhcmFHcm91cCBOYW1lPSIiIEFsaWFzPSJSZXN1bHQgUGFyYW1ldGVycyI+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX1J3IiAgIEFsaWFzPSJXZWxsIFJhZGl1cyIgICAgICAgICAgICAgVW5pdD0ibSIgICAgICBEZWZhdWx0PSIwLjEiICAgIE1heD0iMTAiICAgIE1pbj0iMC4wMDEiICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSByYWRpdXMiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX0RmYyIgIEFsaWFzPSJGcmFjdHVyZSBDb25kdWN0aXZpdHkiICBVbml0PSJtZC5tIiAgIERlZmF1bHQ9IjEwMDAiICAgTWF4PSIxZSszMCIgTWluPSIwIiAgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IGZyYWN0dXJlIGNvbmR1Y3Rpdml0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1dfU2tpbiIgQWxpYXM9IlNraW4gRmFjdG9yIiAgICAgICAgICAgIFVuaXQ9IiIgICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEwMDAiICBNaW49Ii0xMDAiICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgc2tpbiBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9XX0MiICAgIEFsaWFzPSJXZWxsYm9yZSBTdG9yYWdlIENvZWZmIiBVbml0PSJtXjMvTVBhIiBEZWZhdWx0PSIwIiAgICAgIE1heD0iMTAwMCIgIE1pbj0iMCIgICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCB3ZWxsYm9yZSBzdG9yYWdlIGNvZWZmaWNpZW50IiAvPg0KDQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9QaSIgIEFsaWFzPSJJbml0aWFsIEZvcm1hdGlvbiBQcmVzc3VyZSIgVW5pdD0iTVBhIiAgIERlZmF1bHQ9IjQwIiAgICAgTWF4PSI1MDAiICAgTWluPSIwLjEiICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgaW5pdGlhbCBmb3JtYXRpb24gcHJlc3N1cmUiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9LIiAgIEFsaWFzPSJQZXJtZWFiaWxpdHkiICAgICAgICAgICAgICAgVW5pdD0iRGFyY3kiIERlZmF1bHQ9IjAuMDAxIiAgTWF4PSIxMCIgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcGVybWVhYmlsaXR5IiAvPgogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9oIiAgIEFsaWFzPSJSZXNlcnZvaXIgVGhpY2tuZXNzIiAgICAgICAgVW5pdD0ibSIgICAgIERlZmF1bHQ9IjEwIiAgICAgTWF4PSIxMDAwMCIgTWluPSIxZS0wNSIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgcmVzZXJ2b2lyIHRoaWNrbmVzcyIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X3BoaSIgQWxpYXM9IlBvcm9zaXR5IiAgICAgICAgICAgICAgICAgICBVbml0PSIiICAgICAgRGVmYXVsdD0iMC4xIiAgICBNYXg9IjEiICAgICBNaW49IjFlLTA1IiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBwb3Jvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0N0aSIgQWxpYXM9IlRvdGFsIENvbXByZXNzaWJpbGl0eSIgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMC4wMDEiICBNYXg9IjEwIiAgICBNaW49IjFlLTMwIiBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCB0b3RhbCBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9DZiIgIEFsaWFzPSJSb2NrIENvbXByZXNzaWJpbGl0eSIgICAgICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAuMDAwMSIgTWF4PSIxMCIgICAgTWluPSIxZS0zMCIgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgcm9jayBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9Tb2kiIEFsaWFzPSJJbml0aWFsIE9pbCBTYXR1cmF0aW9uIiAgICAgVW5pdD0iIiAgICAgIERlZmF1bHQ9IjAuOCIgICAgTWF4PSIxIiAgICAgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgaW5pdGlhbCBvaWwgc2F0dXJhdGlvbiIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X1NnaSIgQWxpYXM9IkluaXRpYWwgR2FzIFNhdHVyYXRpb24iICAgICBVbml0PSIiICAgICAgRGVmYXVsdD0iMCIgICAgICBNYXg9IjEiICAgICBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBpbml0aWFsIGdhcyBzYXR1cmF0aW9uIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfU3dpIiBBbGlhcz0iSW5pdGlhbCBXYXRlciBTYXR1cmF0aW9uIiAgIFVuaXQ9IiIgICAgICBEZWZhdWx0PSIwLjIiICAgIE1heD0iMSIgICAgIE1pbj0iMCIgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IGluaXRpYWwgd2F0ZXIgc2F0dXJhdGlvbiIgLz4NCg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQm8iICAgQWxpYXM9Ik9pbCBGb3JtYXRpb24gVm9sdW1lIEZhY3RvciIgICBVbml0PSJtMy9tMyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIwIiAgICAgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgb2lsIGZvcm1hdGlvbiB2b2x1bWUgZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfTWl1byIgQWxpYXM9Ik9pbCBWaXNjb3NpdHkiICAgICAgICAgICAgICAgICBVbml0PSJtUGEucyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIxZS0zMCIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgb2lsIHZpc2Nvc2l0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0NvIiAgIEFsaWFzPSJPaWwgQ29tcHJlc3NpYmlsaXR5IiAgICAgICAgICAgVW5pdD0iMS9NUGEiIERlZmF1bHQ9IjAiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgRGlnaXQ9IjEwIiBTY2llbnRpZmljPSIxIiBEZXNjPSJyZXN1bHQgb2lsIGNvbXByZXNzaWJpbGl0eSIgLz4NCiAgICA8UGFyYUl0ZW0gTmFtZT0iUmVzdWx0X0J3IiAgIEFsaWFzPSJXYXRlciBGb3JtYXRpb24gVm9sdW1lIEZhY3RvciIgVW5pdD0ibTMvbTMiIERlZmF1bHQ9IjEiIE1heD0iMWUrMzAiIE1pbj0iMCIgICAgIERpZ2l0PSI2IiAgRGVzYz0icmVzdWx0IHdhdGVyIGZvcm1hdGlvbiB2b2x1bWUgZmFjdG9yIiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfTWl1dyIgQWxpYXM9IldhdGVyIFZpc2Nvc2l0eSIgICAgICAgICAgICAgICBVbml0PSJtUGEucyIgRGVmYXVsdD0iMSIgTWF4PSIxZSszMCIgTWluPSIxZS0zMCIgRGlnaXQ9IjYiICBEZXNjPSJyZXN1bHQgd2F0ZXIgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ3ciICAgQWxpYXM9IldhdGVyIENvbXByZXNzaWJpbGl0eSIgICAgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMCIgTWF4PSIxZSszMCIgTWluPSIwIiAgICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCB3YXRlciBjb21wcmVzc2liaWxpdHkiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9CZyIgICBBbGlhcz0iR2FzIEZvcm1hdGlvbiBWb2x1bWUgRmFjdG9yIiAgIFVuaXQ9Im0zL20zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjAiICAgICBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgZm9ybWF0aW9uIHZvbHVtZSBmYWN0b3IiIC8+DQogICAgPFBhcmFJdGVtIE5hbWU9IlJlc3VsdF9NaXVnIiBBbGlhcz0iR2FzIFZpc2Nvc2l0eSIgICAgICAgICAgICAgICAgIFVuaXQ9Im1QYS5zIiBEZWZhdWx0PSIxIiBNYXg9IjFlKzMwIiBNaW49IjFlLTMwIiBEaWdpdD0iNiIgIERlc2M9InJlc3VsdCBnYXMgdmlzY29zaXR5IiAvPg0KICAgIDxQYXJhSXRlbSBOYW1lPSJSZXN1bHRfQ2ciICAgQWxpYXM9IkdhcyBDb21wcmVzc2liaWxpdHkiICAgICAgICAgICBVbml0PSIxL01QYSIgRGVmYXVsdD0iMCIgTWF4PSIxZSszMCIgTWluPSIwIiAgICBEaWdpdD0iMTAiIFNjaWVudGlmaWM9IjEiIERlc2M9InJlc3VsdCBnYXMgY29tcHJlc3NpYmlsaXR5IiAvPg0KICA8L1BhcmFHcm91cD4NCg0KPC9Sb290Pg0K \ No newline at end of file diff --git a/Bin/Config/Common/UnitDefault_cn.dat b/Bin/Config/Common/UnitDefault_cn.dat index 7e3dd09..6271b6b 100644 --- a/Bin/Config/Common/UnitDefault_cn.dat +++ b/Bin/Config/Common/UnitDefault_cn.dat @@ -1 +1 @@ -77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMDHnu4TvvJrplb/luqYgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTAxIiBVbml0PSJtIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDEwMiIgVW5pdD0ia20iIGs9IjEwMDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDMiIFVuaXQ9ImNtIiBrPSIwLjAxIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTA0IiBVbml0PSJtbSIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDUiIFVuaXQ9ImluIiBrPSIwLjAyNTQiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDYiIFVuaXQ9ImZ0IiBrPSIwLjMwNDgiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDciIFVuaXQ9InlkIiBrPSIwLjkxNDQiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDgiIFVuaXQ9Im1pIiBrPSIxNjA5LjM0IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTA5IiBVbml0PSJubWlsZSIgaz0iMTg1MiIgRGlnaXQ9IjAiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDExMCIgVW5pdD0ibm0iIGs9IjFlLTA5IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDAy57uE77ya5pe26Ze0ID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRpbWUiIE5hbWU9InRpbWUiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMjAxIiBVbml0PSJzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDIwMiIgVW5pdD0ibXMiIGs9IjAuMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMjAzIiBVbml0PSJ1cyIgaz0iMWUtMDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDQiIFVuaXQ9Im1pbiIgaz0iNjAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDUiIFVuaXQ9ImgiIGs9IjM2MDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDYiIFVuaXQ9ImQiIGs9Ijg2NDAwIiBEaWdpdD0iMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDAz57uE77ya6LSo6YePID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9Im1hc3MiIE5hbWU9Im1hc3MiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzAxIiBVbml0PSJrZyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAzMDIiIFVuaXQ9ImciIGs9IjAuMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzAzIiBVbml0PSJ0b24iIGs9IjEwMDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAzMDQiIFVuaXQ9ImxiIiBrPSIwLjQ1MzU5MjM3IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA1IiBVbml0PSJveiIgaz0iMC4wMjgzNDk1IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA2IiBVbml0PSJzdG9uIiBrPSI5MDcuMTg1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA3IiBVbml0PSJsdG9uIiBrPSIxMDE2LjA1IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA057uE77ya6KeS5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9ImFuZ2xlIiBOYW1lPSJhbmdsZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA0MDEiIFVuaXQ9InJhZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA0MDIiIFVuaXQ9ImRlZyIgaz0iMC4wMTc0NTMyOTI1MTk5NDMzIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNDAzIiBVbml0PSJncmFkIiBrPSIwLjAxNTcwNzk2MzI2Nzk0OSIgRGlnaXQ9IjYiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwwNee7hO+8mumdouenryA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJhcmVhIiBOYW1lPSJhcmVhIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwMSIgVW5pdD0ibcKyIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwMiIgVW5pdD0ia23CsiIgaz0iMWU2IiBEaWdpdD0iMCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNTAzIiBVbml0PSJjbcKyIiBrPSIwLjAwMDEiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDQiIFVuaXQ9ImZ0wrIiIGs9IjAuMDkyOTAzMDQiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDUiIFVuaXQ9ImluwrIiIGs9IjAuMDAwNjQ1MTYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDYiIFVuaXQ9ImFjcmUiIGs9IjQwNDYuODU2NDIyNCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwNyIgVW5pdD0iaGEiIGs9IjEwMDAwIiBEaWdpdD0iMCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNTA4IiBVbml0PSJ5ZMKyIiBrPSIwLjgzNjEyNzM2IiBEaWdpdD0iNCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA257uE77ya5L2T56evID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZvbHVtZSIgTmFtZT0idm9sdW1lIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwMSIgVW5pdD0ibcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwMiIgVW5pdD0iTCIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDMiIFVuaXQ9Im1MIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNCIgVW5pdD0iY23CsyIgaz0iMWUtMDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDUiIFVuaXQ9ImNjIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNiIgVW5pdD0iYmJsIiBrPSIwLjE1ODk4NzI5NDkyOCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNyIgVW5pdD0iZ2FsKFVTKSIgaz0iMC4wMDM3ODU0MTE3ODQiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDgiIFVuaXQ9ImdhbChVSykiIGs9IjAuMDA0NTQ2MDkiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDkiIFVuaXQ9ImZ0wrMiIGs9IjAuMDI4MzE2ODQ2NTkyIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjEwIiBVbml0PSJpbsKzIiBrPSIxLjYzODcwNjRlLTA1IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjExIiBVbml0PSJxdCIgaz0iMC4wMDA5NDYzNTI5NDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MTIiIFVuaXQ9InB0IiBrPSIwLjAwMDQ3MzE3NjQ3MyIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYxMyIgVW5pdD0iZ2kiIGs9IjAuMDAwMTE4Mjk0MTE4IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjE0IiBVbml0PSJhY3JlLmZ0IiBrPSIxMjMzLjQ4MTgzNzU0NzUyIiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA357uE77ya6YCf5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZlbG9jaXR5IiBOYW1lPSJ2ZWxvY2l0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDEiIFVuaXQ9Im0vcyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDIiIFVuaXQ9ImttL2giIGs9IjAuMjc3Nzc3Nzc4IiBEaWdpdD0iNSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNzAzIiBVbml0PSJmdC9zIiBrPSIwLjMwNDgiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDQiIFVuaXQ9Im1pbGUvaCIgaz0iMC40NDcwNCIgRGlnaXQ9IjUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwwOOe7hO+8mua4qeW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ0ZW1wZXJhdHVyZSIgTmFtZT0idGVtcGVyYXR1cmUiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwODAxIiBVbml0PSJLIiBrPSIxIiBiPSIwIiBEaWdpdD0iMiIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDgwMiIgVW5pdD0i4oSDIiBrPSIxIiBiPSIyNzMuMTUiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDMiIFVuaXQ9ImRlZ0MiIGs9IjEiIGI9IjI3My4xNSIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDgwNCIgVW5pdD0iZGVnRiIgaz0iMC41NTU1NTU1NTU1NTU1NTYiIGI9IjI1NS4zNzIyMjIyMjIyMjIiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDUiIFVuaXQ9IsKwRiIgaz0iMC41NTU1NTU1NTU1NTU1NTYiIGI9IjI1NS4zNzIyMjIyMjIyMjIiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDYiIFVuaXQ9IlIiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBiPSIwIiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA557uE77ya5rip5beuID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRlbXBlcmF0dXJlX2RlbHRhIiBOYW1lPSJ0ZW1wZXJhdHVyZV9kZWx0YSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDEiIFVuaXQ9IksiIGs9IjEiIERpZ2l0PSIyIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTAyIiBVbml0PSLihIMiIGs9IjEiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDMiIFVuaXQ9ImRlZ0MiIGs9IjEiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDQiIFVuaXQ9ImRlZ0YiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTA1IiBVbml0PSLCsEYiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTA2IiBVbml0PSJSIiBrPSIwLjU1NTU1NTU1NTU1NTU1NiIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxMOe7hO+8muWcsOa4qeair+W6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDEiIFVuaXQ9IksvbSIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDIiIFVuaXQ9IuKEgy9rbSIgaz0iMC4wMDEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDMiIFVuaXQ9ImRlZy9rbSIgaz0iMC4wMDEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDQiIFVuaXQ9IsKwRi9mdCIgaz0iMS44MTgxODE4MTgxODE4MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTAwNSIgVW5pdD0iRi9mdCIgaz0iMS44MTgxODE4MTgxODE4MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTAwNiIgVW5pdD0iwrBGLzEwMGZ0IiBrPSIwLjAxODE4MTgxODE4MTgxODIiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDciIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI3NTg2MjA2ODk3IiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDEx57uE77ya5Y6L5YqbID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByZXNzdXJlIiBOYW1lPSJwcmVzc3VyZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDEiIFVuaXQ9IlBhIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwMiIgVW5pdD0ia1BhIiBrPSIxMDAwIiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTAzIiBVbml0PSJNUGEiIGs9IjFlNiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwNCIgVW5pdD0iYmFyIiBrPSIxMDAwMDAiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDUiIFVuaXQ9Im1iYXIiIGs9IjEwMCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwNiIgVW5pdD0icHNpIiBrPSI2ODk0Ljc1NzI5IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTA3IiBVbml0PSJhdG0iIGs9IjEwMTMyNSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwOCIgVW5pdD0iYXQiIGs9Ijk4MDY2LjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDkiIFVuaXQ9ImtnZi9jbcKyIiBrPSI5ODA2Ni41IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEwIiBVbml0PSJtbUhnIiBrPSIxMzMuMzIyMzY4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTExIiBVbml0PSJUb3JyIiBrPSIxMzMuMzIyMzY4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEyIiBVbml0PSJtSDJPIiBrPSI5ODA2LjY1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEzIiBVbml0PSJtbUgyTyIgaz0iOS44MDY2NSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTExNCIgVW5pdD0iZHluL2NtwrIiIGs9IjAuMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxMue7hO+8muWvhuW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJkZW5zaXR5IiBOYW1lPSJkZW5zaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwMSIgVW5pdD0ia2cvbcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwMiIgVW5pdD0iZy9jbcKzIiBrPSIxMDAwIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjAzIiBVbml0PSJnL2NjIiBrPSIxMDAwIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA0IiBVbml0PSJsYi9mdMKzIiBrPSIxNi4wMTg0NjM0IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA1IiBVbml0PSJsYi9nYWwoVVMpIiBrPSIxMTkuODI2NDI3IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA2IiBVbml0PSJsYi9nYWwoVUspIiBrPSI5OS43NzY0IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA3IiBVbml0PSJsYi9iYmwiIGs9IjIuODUzIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA4IiBVbml0PSJnL2Z0wrMiIGs9IjAuMDM1MzE0NyIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwOSIgVW5pdD0iZy9jZiIgaz0iMC4wMzUzMTQ3IiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDEz57uE77ya5Yqo5Yqb57KY5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZpc2Nvc2l0eSIgTmFtZT0idmlzY29zaXR5X2R5bmFtaWMiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzAxIiBVbml0PSJQYcK3cyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEzMDIiIFVuaXQ9Im1QYcK3cyIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEzMDMiIFVuaXQ9ImNQIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTMwNCIgVW5pdD0iUCIgaz0iMC4xIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzA1IiBVbml0PSJrZ2bCt3MvbcKyIiBrPSI5LjgwNjY1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzA2IiBVbml0PSJsYmbCt3MvZnTCsiIgaz0iNDcuODgwMyIgRGlnaXQ9IjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxNOe7hO+8mui/kOWKqOeymOW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHlfa2luZW1hdGljIiBOYW1lPSJ2aXNjb3NpdHlfa2luZW1hdGljIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTQwMSIgVW5pdD0ibcKyL3MiIGs9IjEiIERpZ2l0PSI2IiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDAyIiBVbml0PSJtbcKyL3MiIGs9IjFlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDAzIiBVbml0PSJjU3QiIGs9IjFlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDA0IiBVbml0PSJTdCIgaz0iMC4wMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDA1IiBVbml0PSJjbcKyL3MiIGs9IjAuMDAwMSIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTQwNiIgVW5pdD0iZnTCsi9zIiBrPSIwLjA5MjkwMzA0IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDE157uE77ya5riX6YCP546HID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNTAxIiBVbml0PSJEIiBrPSIxIiBEaWdpdD0iNCIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTUwMiIgVW5pdD0ibUQiIGs9IjAuMDAxIiBEaWdpdD0iNCIgLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTUwMyIgVW5pdD0iMS9jbcKyIiBrPSI5LjgxZSswNyIgRGlnaXQ9IjIiLz4NCg0KDQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTbnu4TvvJrog73ph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZW5lcmd5IiBOYW1lPSJlbmVyZ3kiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjAxIiBVbml0PSJKIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTYwMiIgVW5pdD0ia0oiIGs9IjEwMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDMiIFVuaXQ9Ik1KIiBrPSIxZTYiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDQiIFVuaXQ9ImNhbCIgaz0iNC4xODQiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDUiIFVuaXQ9ImtjYWwiIGs9IjQxODQiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDYiIFVuaXQ9IkJ0dSIgaz0iMTA1NS4wNTU4NTI2MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTYwNyIgVW5pdD0iQlRVIiBrPSIxMDU1LjA1NTg1MjYyIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjA4IiBVbml0PSJrV8K3aCIgaz0iMy42ZTYiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDkiIFVuaXQ9ImZ0wrdsYmYiIGs9IjEuMzU1ODE3OTQ4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjEwIiBVbml0PSJrZ2bCt20iIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MTEiIFVuaXQ9ImhwwrdoIiBrPSIyLjY4NDUyZTYiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTfnu4TvvJrlip/njocgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icG93ZXIiIE5hbWU9InBvd2VyIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwMSIgVW5pdD0iVyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE3MDIiIFVuaXQ9ImtXIiBrPSIxMDAwIiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzAzIiBVbml0PSJNVyIgaz0iMWU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzA0IiBVbml0PSJocCIgaz0iNzQ1LjY5OTg3MTU4MjI3IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzA1IiBVbml0PSJCdHUvaCIgaz0iMC4yOTMwNzEwNyIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwNiIgVW5pdD0iY2FsL3MiIGs9IjQuMTg2OCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwNyIgVW5pdD0ia2dmwrdtL3MiIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTjnu4TvvJrkvZPnp6/mtYHph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZmxvd19yYXRlX3ZvbHVtZSIgTmFtZT0iZmxvd19yYXRlX3ZvbHVtZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MDEiIFVuaXQ9Im3Csy9kIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwMiIgVW5pdD0ibcKzL3MiIGs9Ijg2NDAwIiBEaWdpdD0iNiIgUmVtYXJrPSIxIG3Csy9zID0gODY0MDAgbcKzL2QiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwMyIgVW5pdD0ibcKzL2EiIGs9IjAuMDAyNzM5NzI2MDI3Mzk3MjYiIERpZ2l0PSI2IiBSZW1hcms9IjEgbcKzL2EgPSAxLzM2NSBtwrMvZCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA0IiBVbml0PSJzdGIvZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iMSBzdGIvZCDiiYggMSBtwrMvZCAo5bel56iL6L+R5Ly8KSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA1IiBVbml0PSJiYmwvZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iMSBiYmwvZCDiiYggMSBtwrMvZCAo5bel56iL6L+R5Ly8KSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA2IiBVbml0PSJmdMKzL3MiIGs9IjI0NDYuNTc1IiBEaWdpdD0iNiIgUmVtYXJrPSIxIGZ0wrMvcyA9IDI0NDYuNTc1IG3Csy9kIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MDciIFVuaXQ9ImZ0wrMvZCIgaz0iMC4wMjgzMTY4NDY1OTIiIERpZ2l0PSI2IiBSZW1hcms9IjEgZnTCsy9kID0gMC4wMjgzMTY4NDY1OTIgbcKzL2QiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwOCIgVW5pdD0iTU1zY2YvZCIgaz0iMjgzMTYuODQ2NTkyIiBEaWdpdD0iNCIgUmVtYXJrPSIxIE1Nc2NmL2QgPSAyODMxNi44NDY1OTIgbcKzL2QgKOeQhuaDs+awlOS9kykiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwOSIgVW5pdD0iTXNjZi9kIiBrPSIyOC4zMTY4NDY1OTIiIERpZ2l0PSI2IiBSZW1hcms9IjEgTXNjZi9kID0gMjguMzE2ODQ2NTkyIG3Csy9kICjnkIbmg7PmsJTkvZMpIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MTAiIFVuaXQ9IjEw4oG0Km3Csy9kIiBrPSIxMDAwMCIgRGlnaXQ9IjQiIFJlbWFyaz0iMSDkuIfmlrkv5aSpID0gMTAwMDAgbcKzL2QiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxOee7hO+8muWOi+e8qeezu+aVsCA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJjb21wcmVzc2liaWxpdHkiIE5hbWU9ImNvbXByZXNzaWJpbGl0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE5MDEiIFVuaXQ9IjEvTVBhIiBrPSIxIiBEaWdpdD0iNiIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTkwMiIgVW5pdD0iMS9QYSIgaz0iMWU2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxOTAzIiBVbml0PSIxL3BzaSIgaz0iMTQ1LjAzNzczOCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTkwNCIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMyNjY3MTYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE5MDUiIFVuaXQ9IjEvYmFyIiBrPSIxMCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyMOe7hO+8mua1k+W6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJjb25jZW50cmF0aW9uIiBOYW1lPSJjb25jZW50cmF0aW9uIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwMSIgVW5pdD0ia2cvbcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwMiIgVW5pdD0iZy9MIiBrPSIxIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMDAzIiBVbml0PSJtZy9MIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwNCIgVW5pdD0izrxnL21MIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwNSIgVW5pdD0icHBtIiBrPSIwLjAwMSIgRGlnaXQ9IjQiIFJlbWFyaz0iYXNzdW1pbmcgMSBwcG0gPSAxIG1nL2tnIGluIHdhdGVyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIwMDYiIFVuaXQ9IktwcG0iIGs9IjEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIwMDciIFVuaXQ9Im1vbC9twrMiIGs9IjEiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjHnu4TvvJrnmb7liIbmr5QgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icGVyY2VudCIgTmFtZT0icGVyY2VudCI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIxMDEiIFVuaXQ9IiUiIGs9IjEiIERpZ2l0PSIyIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMTAyIiBVbml0PSJwdSIgaz0iMSIgRGlnaXQ9IjIiIFJlbWFyaz0icGVyIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjEwMyIgVW5pdD0iZnJhY3Rpb24iIGs9IjEwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjEwNCIgVW5pdD0idi92IiBrPSIxMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIxMDUiIFVuaXQ9Im3Csy9twrMiIGs9IjEwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyMue7hO+8muaFouW6pu+8iOWjsOazou+8iSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzbG93bmVzcyIgTmFtZT0ic2xvd25lc3MiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMjAxIiBVbml0PSJzL20iIGs9IjEiIERpZ2l0PSIzIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMjAyIiBVbml0PSJ1cy9tIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjIwMyIgVW5pdD0idXMvZnQiIGs9IjMuMjgwODM5ODk1MDEzMTJlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDIz57uE77ya5pS+5bCE5oCnID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InJhZGlvYWN0aXZpdHkiIE5hbWU9InJhZGlvYWN0aXZpdHkiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMzAxIiBVbml0PSJBUEkiIGs9IjEiIERpZ2l0PSIxIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMzAyIiBVbml0PSJnQVBJIiBrPSIxIiBEaWdpdD0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI057uE77ya55S15Y6LID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZvbHRhZ2UiIE5hbWU9InZvbHRhZ2UiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNDAxIiBVbml0PSJWIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjQwMiIgVW5pdD0ibVYiIGs9IjAuMDAxIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNDAzIiBVbml0PSJrViIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyNee7hO+8muWKmyA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJmb3JjZSIgTmFtZT0iZm9yY2UiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNTAxIiBVbml0PSJOIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjUwMiIgVW5pdD0ia04iIGs9IjEwMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI1MDMiIFVuaXQ9ImxiZiIgaz0iNC40NDgyMjE2MTUyNjA1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNTA0IiBVbml0PSJrZ2YiIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI1MDUiIFVuaXQ9ImR5biIgaz0iMWUtMDUiIERpZ2l0PSI2Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjbnu4TvvJrpopHnjocgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZnJlcXVlbmN5IiBOYW1lPSJmcmVxdWVuY3kiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNjAxIiBVbml0PSJIeiIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI2MDIiIFVuaXQ9ImtIeiIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjYwMyIgVW5pdD0iTUh6IiBrPSIxZTYiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI2MDQiIFVuaXQ9IkdIeiIgaz0iMWU5IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI357uE77ya54Ot5a+8546HID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRoZXJtYWxfY29uZHVjdGl2aXR5IiBOYW1lPSJ0aGVybWFsX2NvbmR1Y3Rpdml0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI3MDEiIFVuaXQ9IlcvKG3Ct0spIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjcwMiIgVW5pdD0iVy8obcK34oSDKSIgaz0iMSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjcwMyIgVW5pdD0iQnR1LyhowrdmdMK3wrBGKSIgaz0iMS43MzA3MzUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI3MDQiIFVuaXQ9ImNhbC8oY23Ct3PCt+KEgykiIGs9IjQxOC40IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI457uE77ya5q+U54Ot5a65ID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InNwZWNpZmljX2hlYXQiIE5hbWU9InNwZWNpZmljX2hlYXQiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODAxIiBVbml0PSJKLyhrZ8K3SykiIGs9IjEiIERpZ2l0PSIzIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODAyIiBVbml0PSJKLyhnwrdLKSIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjgwMyIgVW5pdD0iY2FsLyhnwrfihIMpIiBrPSI0MTg0IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODA0IiBVbml0PSJCdHUvKGxiwrfCsEYpIiBrPSI0MTg2LjgiIERpZ2l0PSIyIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjnnu4TvvJrmkanlsJTotKjph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibW9sYXJfbWFzcyIgTmFtZT0ibW9sYXJfbWFzcyI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI5MDEiIFVuaXQ9ImtnL21vbCIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI5MDIiIFVuaXQ9ImcvbW9sIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjkwMyIgVW5pdD0ibGIvbGJtb2wiIGs9IjAuNDUzNTkyMzciIERpZ2l0PSI0Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzDnu4TvvJpVbmtub3duMSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJtb2xhcl9tYXNzIiBOYW1lPSJtb2xhcl9tYXNzIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzAwMSIgVW5pdD0ia2cvbW9sIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzAwMiIgVW5pdD0iZy9tb2wiIGs9IjAuMDAxIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMDAzIiBVbml0PSJsYi9sYm1vbCIgaz0iMC40NTM1OTIzNyIgRGlnaXQ9IjQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzHnu4TvvJpVbmtub3duMSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ3cyIgTmFtZT0i55uQ5bqmIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwMSIgVW5pdD0ibWcvTCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMTAyIiBVbml0PSLOvGcvbUwiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwMyIgVW5pdD0iZy9MIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTMxMDQiIFVuaXQ9ImtnL21eMyIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwNSIgVW5pdD0icHBtIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTMxMDYiIFVuaXQ9IktwcG0iIGs9IjEwMDAiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwzMue7hO+8mlVua25vd24xID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZpc2Nvc2l0eSIgTmFtZT0idmlzY29zaXR5Ij4KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMjAxIiBVbml0PSJQYS5zIiBrPSIxIi8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwMiIgVW5pdD0ibVBhLnMiIGs9IjAuMDAxIi8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwMyIgVW5pdD0iUCIgaz0iMC4xIi8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwNCIgVW5pdD0iY1AiIGs9IjAuMDAxIi8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwNSIgVW5pdD0iYmYucy9mdF4yIiBrPSI0Ny44ODAzIi8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwNiIgVW5pdD0ia2dmLnMvbV4zIiBrPSI5LjgwNjY1Ii8+CiAgICAgICAgPC9Vbml0R3JvdXA+CgogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzPnu4TvvJrkupXnrZLlgqjpm4bns7vmlbAgPT09PT09PT09PT09PT09PT09PT0gLS0+CiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMyIgTmFtZT0iVG9EbzMiPgogICAgICAgICAgICA8VW5pdCBJRD0iVTMzMDEiIFVuaXQ9Im1eMy9NUGEiIGs9IjEiIERpZ2l0PSI2IiBSZW1hcms9ImJhc2UgdW5pdCIvPgogICAgICAgIDwvVW5pdEdyb3VwPgoKICAgIDwvVHlwZU9mPgo8L1Jvb3Q+Cg== \ No newline at end of file +77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMDHnu4TvvJrplb/luqYgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTAxIiBVbml0PSJtIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDEwMiIgVW5pdD0ia20iIGs9IjEwMDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDMiIFVuaXQ9ImNtIiBrPSIwLjAxIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTA0IiBVbml0PSJtbSIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDUiIFVuaXQ9ImluIiBrPSIwLjAyNTQiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDYiIFVuaXQ9ImZ0IiBrPSIwLjMwNDgiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDciIFVuaXQ9InlkIiBrPSIwLjkxNDQiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAxMDgiIFVuaXQ9Im1pIiBrPSIxNjA5LjM0IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMTA5IiBVbml0PSJubWlsZSIgaz0iMTg1MiIgRGlnaXQ9IjAiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDExMCIgVW5pdD0ibm0iIGs9IjFlLTA5IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDAy57uE77ya5pe26Ze0ID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRpbWUiIE5hbWU9InRpbWUiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMjAxIiBVbml0PSJzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDIwMiIgVW5pdD0ibXMiIGs9IjAuMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMjAzIiBVbml0PSJ1cyIgaz0iMWUtMDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDQiIFVuaXQ9Im1pbiIgaz0iNjAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDUiIFVuaXQ9ImgiIGs9IjM2MDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAyMDYiIFVuaXQ9ImQiIGs9Ijg2NDAwIiBEaWdpdD0iMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDAz57uE77ya6LSo6YePID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9Im1hc3MiIE5hbWU9Im1hc3MiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzAxIiBVbml0PSJrZyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAzMDIiIFVuaXQ9ImciIGs9IjAuMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzAzIiBVbml0PSJ0b24iIGs9IjEwMDAiIERpZ2l0PSIwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTAzMDQiIFVuaXQ9ImxiIiBrPSIwLjQ1MzU5MjM3IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA1IiBVbml0PSJveiIgaz0iMC4wMjgzNDk1IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA2IiBVbml0PSJzdG9uIiBrPSI5MDcuMTg1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwMzA3IiBVbml0PSJsdG9uIiBrPSIxMDE2LjA1IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA057uE77ya6KeS5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9ImFuZ2xlIiBOYW1lPSJhbmdsZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA0MDEiIFVuaXQ9InJhZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA0MDIiIFVuaXQ9ImRlZyIgaz0iMC4wMTc0NTMyOTI1MTk5NDMzIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNDAzIiBVbml0PSJncmFkIiBrPSIwLjAxNTcwNzk2MzI2Nzk0OSIgRGlnaXQ9IjYiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwwNee7hO+8mumdouenryA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJhcmVhIiBOYW1lPSJhcmVhIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwMSIgVW5pdD0ibcKyIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwMiIgVW5pdD0ia23CsiIgaz0iMWU2IiBEaWdpdD0iMCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNTAzIiBVbml0PSJjbcKyIiBrPSIwLjAwMDEiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDQiIFVuaXQ9ImZ0wrIiIGs9IjAuMDkyOTAzMDQiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDUiIFVuaXQ9ImluwrIiIGs9IjAuMDAwNjQ1MTYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA1MDYiIFVuaXQ9ImFjcmUiIGs9IjQwNDYuODU2NDIyNCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDUwNyIgVW5pdD0iaGEiIGs9IjEwMDAwIiBEaWdpdD0iMCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNTA4IiBVbml0PSJ5ZMKyIiBrPSIwLjgzNjEyNzM2IiBEaWdpdD0iNCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA257uE77ya5L2T56evID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZvbHVtZSIgTmFtZT0idm9sdW1lIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwMSIgVW5pdD0ibcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwMiIgVW5pdD0iTCIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDMiIFVuaXQ9Im1MIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNCIgVW5pdD0iY23CsyIgaz0iMWUtMDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDUiIFVuaXQ9ImNjIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNiIgVW5pdD0iYmJsIiBrPSIwLjE1ODk4NzI5NDkyOCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYwNyIgVW5pdD0iZ2FsKFVTKSIgaz0iMC4wMDM3ODU0MTE3ODQiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDgiIFVuaXQ9ImdhbChVSykiIGs9IjAuMDA0NTQ2MDkiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MDkiIFVuaXQ9ImZ0wrMiIGs9IjAuMDI4MzE2ODQ2NTkyIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjEwIiBVbml0PSJpbsKzIiBrPSIxLjYzODcwNjRlLTA1IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjExIiBVbml0PSJxdCIgaz0iMC4wMDA5NDYzNTI5NDYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA2MTIiIFVuaXQ9InB0IiBrPSIwLjAwMDQ3MzE3NjQ3MyIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDYxMyIgVW5pdD0iZ2kiIGs9IjAuMDAwMTE4Mjk0MTE4IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNjE0IiBVbml0PSJhY3JlLmZ0IiBrPSIxMjMzLjQ4MTgzNzU0NzUyIiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA357uE77ya6YCf5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZlbG9jaXR5IiBOYW1lPSJ2ZWxvY2l0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDEiIFVuaXQ9Im0vcyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDIiIFVuaXQ9ImttL2giIGs9IjAuMjc3Nzc3Nzc4IiBEaWdpdD0iNSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwNzAzIiBVbml0PSJmdC9zIiBrPSIwLjMwNDgiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA3MDQiIFVuaXQ9Im1pbGUvaCIgaz0iMC40NDcwNCIgRGlnaXQ9IjUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwwOOe7hO+8mua4qeW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ0ZW1wZXJhdHVyZSIgTmFtZT0idGVtcGVyYXR1cmUiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwODAxIiBVbml0PSJLIiBrPSIxIiBiPSIwIiBEaWdpdD0iMiIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDgwMiIgVW5pdD0i4oSDIiBrPSIxIiBiPSIyNzMuMTUiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDMiIFVuaXQ9ImRlZ0MiIGs9IjEiIGI9IjI3My4xNSIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMDgwNCIgVW5pdD0iZGVnRiIgaz0iMC41NTU1NTU1NTU1NTU1NTYiIGI9IjI1NS4zNzIyMjIyMjIyMjIiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDUiIFVuaXQ9IsKwRiIgaz0iMC41NTU1NTU1NTU1NTU1NTYiIGI9IjI1NS4zNzIyMjIyMjIyMjIiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA4MDYiIFVuaXQ9IlIiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBiPSIwIiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDA557uE77ya5rip5beuID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRlbXBlcmF0dXJlX2RlbHRhIiBOYW1lPSJ0ZW1wZXJhdHVyZV9kZWx0YSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDEiIFVuaXQ9IksiIGs9IjEiIERpZ2l0PSIyIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTAyIiBVbml0PSLihIMiIGs9IjEiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDMiIFVuaXQ9ImRlZ0MiIGs9IjEiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTA5MDQiIFVuaXQ9ImRlZ0YiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTA1IiBVbml0PSLCsEYiIGs9IjAuNTU1NTU1NTU1NTU1NTU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUwOTA2IiBVbml0PSJSIiBrPSIwLjU1NTU1NTU1NTU1NTU1NiIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxMOe7hO+8muWcsOa4qeair+W6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDEiIFVuaXQ9IksvbSIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDIiIFVuaXQ9IuKEgy9rbSIgaz0iMC4wMDEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDMiIFVuaXQ9ImRlZy9rbSIgaz0iMC4wMDEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDQiIFVuaXQ9IsKwRi9mdCIgaz0iMS44MTgxODE4MTgxODE4MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTAwNSIgVW5pdD0iRi9mdCIgaz0iMS44MTgxODE4MTgxODE4MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTAwNiIgVW5pdD0iwrBGLzEwMGZ0IiBrPSIwLjAxODE4MTgxODE4MTgxODIiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEwMDciIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI3NTg2MjA2ODk3IiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDEx57uE77ya5Y6L5YqbID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByZXNzdXJlIiBOYW1lPSJwcmVzc3VyZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDEiIFVuaXQ9IlBhIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwMiIgVW5pdD0ia1BhIiBrPSIxMDAwIiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTAzIiBVbml0PSJNUGEiIGs9IjFlNiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwNCIgVW5pdD0iYmFyIiBrPSIxMDAwMDAiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDUiIFVuaXQ9Im1iYXIiIGs9IjEwMCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwNiIgVW5pdD0icHNpIiBrPSI2ODk0Ljc1NzI5IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTA3IiBVbml0PSJhdG0iIGs9IjEwMTMyNSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTEwOCIgVW5pdD0iYXQiIGs9Ijk4MDY2LjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTExMDkiIFVuaXQ9ImtnZi9jbcKyIiBrPSI5ODA2Ni41IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEwIiBVbml0PSJtbUhnIiBrPSIxMzMuMzIyMzY4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTExIiBVbml0PSJUb3JyIiBrPSIxMzMuMzIyMzY4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEyIiBVbml0PSJtSDJPIiBrPSI5ODA2LjY1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMTEzIiBVbml0PSJtbUgyTyIgaz0iOS44MDY2NSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTExNCIgVW5pdD0iZHluL2NtwrIiIGs9IjAuMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxMue7hO+8muWvhuW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJkZW5zaXR5IiBOYW1lPSJkZW5zaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwMSIgVW5pdD0ia2cvbcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwMiIgVW5pdD0iZy9jbcKzIiBrPSIxMDAwIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjAzIiBVbml0PSJnL2NjIiBrPSIxMDAwIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA0IiBVbml0PSJsYi9mdMKzIiBrPSIxNi4wMTg0NjM0IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA1IiBVbml0PSJsYi9nYWwoVVMpIiBrPSIxMTkuODI2NDI3IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA2IiBVbml0PSJsYi9nYWwoVUspIiBrPSI5OS43NzY0IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA3IiBVbml0PSJsYi9iYmwiIGs9IjIuODUzIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMjA4IiBVbml0PSJnL2Z0wrMiIGs9IjAuMDM1MzE0NyIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTIwOSIgVW5pdD0iZy9jZiIgaz0iMC4wMzUzMTQ3IiBEaWdpdD0iMyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDEz57uE77ya5Yqo5Yqb57KY5bqmID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZpc2Nvc2l0eSIgTmFtZT0idmlzY29zaXR5X2R5bmFtaWMiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzAxIiBVbml0PSJQYcK3cyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEzMDIiIFVuaXQ9Im1QYcK3cyIgaz0iMC4wMDEiIERpZ2l0PSI0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTEzMDMiIFVuaXQ9ImNQIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTMwNCIgVW5pdD0iUCIgaz0iMC4xIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzA1IiBVbml0PSJrZ2bCt3MvbcKyIiBrPSI5LjgwNjY1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxMzA2IiBVbml0PSJsYmbCt3MvZnTCsiIgaz0iNDcuODgwMyIgRGlnaXQ9IjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxNOe7hO+8mui/kOWKqOeymOW6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHlfa2luZW1hdGljIiBOYW1lPSJ2aXNjb3NpdHlfa2luZW1hdGljIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTQwMSIgVW5pdD0ibcKyL3MiIGs9IjEiIERpZ2l0PSI2IiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDAyIiBVbml0PSJtbcKyL3MiIGs9IjFlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDAzIiBVbml0PSJjU3QiIGs9IjFlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDA0IiBVbml0PSJTdCIgaz0iMC4wMDAxIiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNDA1IiBVbml0PSJjbcKyL3MiIGs9IjAuMDAwMSIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTQwNiIgVW5pdD0iZnTCsi9zIiBrPSIwLjA5MjkwMzA0IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDE157uE77ya5riX6YCP546HID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNTAxIiBVbml0PSJEYXJjeSIgaz0iMSIgRGlnaXQ9IjQiIFJlbWFyaz0iYmFzZSB1bml0Ii8+CiAgICAgICAgICAgIDxVbml0IElEPSJVMTUwMiIgVW5pdD0ibUQiIGs9IjAuMDAxIiBEaWdpdD0iNCIgLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTUwMyIgVW5pdD0iMS9jbcKyIiBrPSI5LjgxZSswNyIgRGlnaXQ9IjIiLz4NCg0KDQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTbnu4TvvJrog73ph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZW5lcmd5IiBOYW1lPSJlbmVyZ3kiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjAxIiBVbml0PSJKIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTYwMiIgVW5pdD0ia0oiIGs9IjEwMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDMiIFVuaXQ9Ik1KIiBrPSIxZTYiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDQiIFVuaXQ9ImNhbCIgaz0iNC4xODQiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDUiIFVuaXQ9ImtjYWwiIGs9IjQxODQiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDYiIFVuaXQ9IkJ0dSIgaz0iMTA1NS4wNTU4NTI2MiIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTYwNyIgVW5pdD0iQlRVIiBrPSIxMDU1LjA1NTg1MjYyIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjA4IiBVbml0PSJrV8K3aCIgaz0iMy42ZTYiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MDkiIFVuaXQ9ImZ0wrdsYmYiIGs9IjEuMzU1ODE3OTQ4IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNjEwIiBVbml0PSJrZ2bCt20iIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE2MTEiIFVuaXQ9ImhwwrdoIiBrPSIyLjY4NDUyZTYiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTfnu4TvvJrlip/njocgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icG93ZXIiIE5hbWU9InBvd2VyIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwMSIgVW5pdD0iVyIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE3MDIiIFVuaXQ9ImtXIiBrPSIxMDAwIiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzAzIiBVbml0PSJNVyIgaz0iMWU2IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzA0IiBVbml0PSJocCIgaz0iNzQ1LjY5OTg3MTU4MjI3IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxNzA1IiBVbml0PSJCdHUvaCIgaz0iMC4yOTMwNzEwNyIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwNiIgVW5pdD0iY2FsL3MiIGs9IjQuMTg2OCIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTcwNyIgVW5pdD0ia2dmwrdtL3MiIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMTjnu4TvvJrkvZPnp6/mtYHph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZmxvd19yYXRlX3ZvbHVtZSIgTmFtZT0iZmxvd19yYXRlX3ZvbHVtZSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MDEiIFVuaXQ9Im3Csy9kIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwMiIgVW5pdD0ibcKzL3MiIGs9Ijg2NDAwIiBEaWdpdD0iNiIgUmVtYXJrPSIxIG3Csy9zID0gODY0MDAgbcKzL2QiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwMyIgVW5pdD0ibcKzL2EiIGs9IjAuMDAyNzM5NzI2MDI3Mzk3MjYiIERpZ2l0PSI2IiBSZW1hcms9IjEgbcKzL2EgPSAxLzM2NSBtwrMvZCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA0IiBVbml0PSJzdGIvZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iMSBzdGIvZCDiiYggMSBtwrMvZCAo5bel56iL6L+R5Ly8KSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA1IiBVbml0PSJiYmwvZCIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iMSBiYmwvZCDiiYggMSBtwrMvZCAo5bel56iL6L+R5Ly8KSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxODA2IiBVbml0PSJmdMKzL3MiIGs9IjI0NDYuNTc1IiBEaWdpdD0iNiIgUmVtYXJrPSIxIGZ0wrMvcyA9IDI0NDYuNTc1IG3Csy9kIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MDciIFVuaXQ9ImZ0wrMvZCIgaz0iMC4wMjgzMTY4NDY1OTIiIERpZ2l0PSI2IiBSZW1hcms9IjEgZnTCsy9kID0gMC4wMjgzMTY4NDY1OTIgbcKzL2QiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwOCIgVW5pdD0iTU1zY2YvZCIgaz0iMjgzMTYuODQ2NTkyIiBEaWdpdD0iNCIgUmVtYXJrPSIxIE1Nc2NmL2QgPSAyODMxNi44NDY1OTIgbcKzL2QgKOeQhuaDs+awlOS9kykiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTgwOSIgVW5pdD0iTXNjZi9kIiBrPSIyOC4zMTY4NDY1OTIiIERpZ2l0PSI2IiBSZW1hcms9IjEgTXNjZi9kID0gMjguMzE2ODQ2NTkyIG3Csy9kICjnkIbmg7PmsJTkvZMpIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE4MTAiIFVuaXQ9IjEw4oG0Km3Csy9kIiBrPSIxMDAwMCIgRGlnaXQ9IjQiIFJlbWFyaz0iMSDkuIfmlrkv5aSpID0gMTAwMDAgbcKzL2QiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwxOee7hO+8muWOi+e8qeezu+aVsCA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJjb21wcmVzc2liaWxpdHkiIE5hbWU9ImNvbXByZXNzaWJpbGl0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE5MDEiIFVuaXQ9IjEvTVBhIiBrPSIxIiBEaWdpdD0iNiIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTkwMiIgVW5pdD0iMS9QYSIgaz0iMWU2IiBEaWdpdD0iNiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUxOTAzIiBVbml0PSIxL3BzaSIgaz0iMTQ1LjAzNzczOCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMTkwNCIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMyNjY3MTYiIERpZ2l0PSI2Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTE5MDUiIFVuaXQ9IjEvYmFyIiBrPSIxMCIgRGlnaXQ9IjYiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyMOe7hO+8mua1k+W6piA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJjb25jZW50cmF0aW9uIiBOYW1lPSJjb25jZW50cmF0aW9uIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwMSIgVW5pdD0ia2cvbcKzIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwMiIgVW5pdD0iZy9MIiBrPSIxIiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMDAzIiBVbml0PSJtZy9MIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwNCIgVW5pdD0izrxnL21MIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjAwNSIgVW5pdD0icHBtIiBrPSIwLjAwMSIgRGlnaXQ9IjQiIFJlbWFyaz0iYXNzdW1pbmcgMSBwcG0gPSAxIG1nL2tnIGluIHdhdGVyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIwMDYiIFVuaXQ9IktwcG0iIGs9IjEiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIwMDciIFVuaXQ9Im1vbC9twrMiIGs9IjEiIERpZ2l0PSIzIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjHnu4TvvJrnmb7liIbmr5QgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icGVyY2VudCIgTmFtZT0icGVyY2VudCI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIxMDEiIFVuaXQ9IiUiIGs9IjEiIERpZ2l0PSIyIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMTAyIiBVbml0PSJwdSIgaz0iMSIgRGlnaXQ9IjIiIFJlbWFyaz0icGVyIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjEwMyIgVW5pdD0iZnJhY3Rpb24iIGs9IjEwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjEwNCIgVW5pdD0idi92IiBrPSIxMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTIxMDUiIFVuaXQ9Im3Csy9twrMiIGs9IjEwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyMue7hO+8muaFouW6pu+8iOWjsOazou+8iSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzbG93bmVzcyIgTmFtZT0ic2xvd25lc3MiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMjAxIiBVbml0PSJzL20iIGs9IjEiIERpZ2l0PSIzIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMjAyIiBVbml0PSJ1cy9tIiBrPSIxZS0wNiIgRGlnaXQ9IjYiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjIwMyIgVW5pdD0idXMvZnQiIGs9IjMuMjgwODM5ODk1MDEzMTJlLTA2IiBEaWdpdD0iNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDIz57uE77ya5pS+5bCE5oCnID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InJhZGlvYWN0aXZpdHkiIE5hbWU9InJhZGlvYWN0aXZpdHkiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMzAxIiBVbml0PSJBUEkiIGs9IjEiIERpZ2l0PSIxIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyMzAyIiBVbml0PSJnQVBJIiBrPSIxIiBEaWdpdD0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI057uE77ya55S15Y6LID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZvbHRhZ2UiIE5hbWU9InZvbHRhZ2UiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNDAxIiBVbml0PSJWIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjQwMiIgVW5pdD0ibVYiIGs9IjAuMDAxIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNDAzIiBVbml0PSJrViIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwyNee7hO+8muWKmyA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJmb3JjZSIgTmFtZT0iZm9yY2UiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNTAxIiBVbml0PSJOIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjUwMiIgVW5pdD0ia04iIGs9IjEwMDAiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI1MDMiIFVuaXQ9ImxiZiIgaz0iNC40NDgyMjE2MTUyNjA1IiBEaWdpdD0iMyIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNTA0IiBVbml0PSJrZ2YiIGs9IjkuODA2NjUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI1MDUiIFVuaXQ9ImR5biIgaz0iMWUtMDUiIERpZ2l0PSI2Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjbnu4TvvJrpopHnjocgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZnJlcXVlbmN5IiBOYW1lPSJmcmVxdWVuY3kiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyNjAxIiBVbml0PSJIeiIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI2MDIiIFVuaXQ9ImtIeiIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjYwMyIgVW5pdD0iTUh6IiBrPSIxZTYiIERpZ2l0PSIyIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI2MDQiIFVuaXQ9IkdIeiIgaz0iMWU5IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI357uE77ya54Ot5a+8546HID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRoZXJtYWxfY29uZHVjdGl2aXR5IiBOYW1lPSJ0aGVybWFsX2NvbmR1Y3Rpdml0eSI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI3MDEiIFVuaXQ9IlcvKG3Ct0spIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjcwMiIgVW5pdD0iVy8obcK34oSDKSIgaz0iMSIgRGlnaXQ9IjMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjcwMyIgVW5pdD0iQnR1LyhowrdmdMK3wrBGKSIgaz0iMS43MzA3MzUiIERpZ2l0PSIzIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI3MDQiIFVuaXQ9ImNhbC8oY23Ct3PCt+KEgykiIGs9IjQxOC40IiBEaWdpdD0iMiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8IS0tID09PT09PT09PT09PT09PT09PT09IOesrDI457uE77ya5q+U54Ot5a65ID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InNwZWNpZmljX2hlYXQiIE5hbWU9InNwZWNpZmljX2hlYXQiPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODAxIiBVbml0PSJKLyhrZ8K3SykiIGs9IjEiIERpZ2l0PSIzIiBSZW1hcms9ImJhc2UgdW5pdCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODAyIiBVbml0PSJKLyhnwrdLKSIgaz0iMTAwMCIgRGlnaXQ9IjIiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjgwMyIgVW5pdD0iY2FsLyhnwrfihIMpIiBrPSI0MTg0IiBEaWdpdD0iMiIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUyODA0IiBVbml0PSJCdHUvKGxiwrfCsEYpIiBrPSI0MTg2LjgiIERpZ2l0PSIyIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMjnnu4TvvJrmkanlsJTotKjph48gPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibW9sYXJfbWFzcyIgTmFtZT0ibW9sYXJfbWFzcyI+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI5MDEiIFVuaXQ9ImtnL21vbCIgaz0iMSIgRGlnaXQ9IjMiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTI5MDIiIFVuaXQ9ImcvbW9sIiBrPSIwLjAwMSIgRGlnaXQ9IjQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMjkwMyIgVW5pdD0ibGIvbGJtb2wiIGs9IjAuNDUzNTkyMzciIERpZ2l0PSI0Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzDnu4TvvJpVbmtub3duMSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJtb2xhcl9tYXNzIiBOYW1lPSJtb2xhcl9tYXNzIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzAwMSIgVW5pdD0ia2cvbW9sIiBrPSIxIiBEaWdpdD0iMyIgUmVtYXJrPSJiYXNlIHVuaXQiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzAwMiIgVW5pdD0iZy9tb2wiIGs9IjAuMDAxIiBEaWdpdD0iNCIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMDAzIiBVbml0PSJsYi9sYm1vbCIgaz0iMC40NTM1OTIzNyIgRGlnaXQ9IjQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzHnu4TvvJpVbmtub3duMSA9PT09PT09PT09PT09PT09PT09PSAtLT4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ3cyIgTmFtZT0i55uQ5bqmIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwMSIgVW5pdD0ibWcvTCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMTAyIiBVbml0PSLOvGcvbUwiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwMyIgVW5pdD0iZy9MIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTMxMDQiIFVuaXQ9ImtnL21eMyIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzEwNSIgVW5pdD0icHBtIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTMxMDYiIFVuaXQ9IktwcG0iIGs9IjEwMDAiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCiAgICAgICAgPCEtLSA9PT09PT09PT09PT09PT09PT09PSDnrKwzMue7hO+8mlVua25vd24xID09PT09PT09PT09PT09PT09PT09IC0tPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InZpc2Nvc2l0eSIgTmFtZT0idmlzY29zaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwMSIgVW5pdD0iUGEucyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMjAyIiBVbml0PSJtUGEucyIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwMyIgVW5pdD0iUCIgaz0iMC4xIi8+DQogICAgICAgICAgICA8VW5pdCBJRD0iVTMyMDQiIFVuaXQ9ImNQIiBrPSIwLjAwMSIvPg0KICAgICAgICAgICAgPFVuaXQgSUQ9IlUzMjA1IiBVbml0PSJiZi5zL2Z0XjIiIGs9IjQ3Ljg4MDMiLz4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzIwNiIgVW5pdD0ia2dmLnMvbV4zIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDwhLS0gPT09PT09PT09PT09PT09PT09PT0g56ysMzPnu4TvvJrkupXnrZLlgqjpm4bns7vmlbAgPT09PT09PT09PT09PT09PT09PT0gLS0+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzMiIE5hbWU9IlRvRG8zIj4NCiAgICAgICAgICAgIDxVbml0IElEPSJVMzMwMSIgVW5pdD0ibV4zL01QYSIgaz0iMSIgRGlnaXQ9IjYiIFJlbWFyaz0iYmFzZSB1bml0Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgPC9UeXBlT2Y+DQo8L1Jvb3Q+DQo= \ No newline at end of file diff --git a/Bin/Config/Common/UnitDefault_en.dat b/Bin/Config/Common/UnitDefault_en.dat index 1d2140c..a988b43 100644 --- a/Bin/Config/Common/UnitDefault_en.dat +++ b/Bin/Config/Common/UnitDefault_en.dat @@ -1 +1 @@ -77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iY29tcHJlc3NpYmlsaXR5IiBOYW1lPSJjb21wcmVzc2liaWxpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL01QYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL1BhIiBrPSIxZTYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9wc2kiIGs9IjE0NS4wMzgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZGVuc2l0eSIgTmFtZT0iZGVuc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY21eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiL2Z0XjMiIGs9IjAuMDE2MDIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVTKSIgaz0iMC4xMTk4MjYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVLKSIgaz0iMC4wOTk3NiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYi9iYmwiIGs9IjAuMDAyODUzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY2MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9jZiIgaz0iMy41MzE0N2UtMDUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9mdF4zIiBrPSIzLjUzMTQ3ZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImRlZy9tIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvaW4iIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWcva20iIGs9IjAuMDk5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvZnQiIGs9IjE4MS44MTgiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtIiBrPSIzOS4zNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtIiBrPSIwLjM5MzcwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbSIgaz0iMC4wMzkzNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImttIiBrPSIzOTM3MC4xIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0IiBrPSIxMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtaSIgaz0iNjMzNDYuNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ5ZCIgaz0iMzUuOTg3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubWlsZSIgaz0iNzI5MTMuNCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubSIgaz0iMy45MzcwMWUtMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0icmFkIiBrPSI3LjgyODMxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcmNlbnQiIE5hbWU9InBlcmNlbnQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIlIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB1IiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9tXjMiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ2L3YiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmcmFjdGlvbiIgaz0iMTAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtRCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eLTIiIGs9IjkuODFlKzA3Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBvd2VyIiBOYW1lPSJwb3dlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iQnR1L2giIGs9IjAuMjkzMDcxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tL3MiIGs9IjkuODA2NjUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2FsL3MiIGs9IjQuMTg2OCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocCIgaz0iNzM1LjQ5OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcmVzc3VyZSIgTmFtZT0icHJlc3N1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJQYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJNUGEiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IktQYSIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYXIiIGs9IjEwMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJUb3JyIiBrPSIxMzMuMzIyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImR5bi9jbV4yIiBrPSIwLjEiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSGciIGs9IjEzMy4zMjIiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSDJPIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImF0IiBrPSI5ODA2Ni41Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBzaSIgaz0iNjg5Ni41NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhdG0iIGs9IjEwMTMyNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YvY21eMiIgaz0iOTgwNzkuNyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kdWN0aW9uIiBOYW1lPSJwcm9kdWN0aW9uIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2QiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idGNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2EiIGs9IjAuMDAyNzQwMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZi9kIiBrPSIyODMyMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtY2YvZCIgaz0iMjguMzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RiL2QiIGs9IjAuMTU5Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByb2RnYXMiIE5hbWU9InByb2RnYXMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvZCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxMF40Km1eMy9kIiBrPSIxMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvYSIgaz0iMC4wMDI3NDAwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbWNmL2QiIGs9IjI4MzIwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZi9kIiBrPSIyOC4zMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzdGIvZCIgaz0iMC4xNTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJvZG9pbCIgTmFtZT0icHJvZG9pbCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kd2F0ZXIiIE5hbWU9InByb2R3YXRlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJyYWRpb2FjdGl2aXR5IiBOYW1lPSJyYWRpb2FjdGl2aXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZ0FQSSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJBUEkiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ic2xvd25lc3MiIE5hbWU9InNsb3duZXNzIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idXMvbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ1cy9mdCIgaz0iMy4yODA4NCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzcXVhcmUiIE5hbWU9InNxdWFyZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrbV4yIiBrPSIxZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbl4yIiBrPSIwLjAwMDY0NTIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMiIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUiIGs9IjQwNDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZV4yIiBrPSIyLjU4ODg4ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmdF4yIiBrPSIwLjA5MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJoYSIgaz0iMTAwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ieWReMiIgaz0iMC44MzYxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InJhZDIiIGs9IjAuMDM5NTM2OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzdHJlbmd0aCIgTmFtZT0ic3RyZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJOIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiZiIgaz0iNC40NDQ0NCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YiIGs9IjkuODEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZHluIiBrPSIxZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ0ZW1wZXJhdHVyZSIgTmFtZT0idGVtcGVyYXR1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSyIgaz0iMSIgYj0iMjczLjE1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYiIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLihIMiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGhlbWFsIHBvd2VyIiBOYW1lPSJ0aGVtYWwgcG93ZXIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJKIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNhbCIgaz0iNC4xODY4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkJ0dSIgaz0iMTA1NS4wNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrVy5oIiBrPSIzLjZlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0LmxiZiIgaz0iMS4zNTU4MiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocC5oKFVTKSIgaz0iMi42NDc3OWUrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSHAuaChVSykiIGs9IjIuNjg0NTJlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnLm0iIGs9IjkuODAwMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2NhbCIgaz0iNDE4NS44NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJCVFUiIGs9IjEwNTQuODUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGltZSIgTmFtZT0idGltZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InVzIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1zIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InMiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1pbiIgaz0iNmUrMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaHIiIGs9IjMuNmUrMDkiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRGF5IiBrPSI4LjY0ZSsxMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2ZWxvY2l0eSIgTmFtZT0idmVsb2NpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZS9oIiBrPSIwLjQ0NzA0Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0L3MiIGs9IjAuMzA0OCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkiIE5hbWU9InZpc2Nvc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlBhLnMiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibVBhLnMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlAiIGs9IjAuMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjUCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmYucy9mdF4yIiBrPSI0Ny44ODAzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5zL21eMyIgaz0iOS44MDY2NSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkgYWN0IiBOYW1lPSJ2aXNjb3NpdHkgYWN0Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4yL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMi9zIiBrPSIwLjAwMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMi9zIiBrPSIwLjA5MjkwMyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJTdCIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNTdCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1eMi9zIiBrPSIxZS0wNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x0YWdlIiBOYW1lPSJ2b2x0YWdlIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibXYiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idiIgaz0iMTAwMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x1bWUiIE5hbWU9InZvbHVtZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaW5eMyIgaz0iMS42MzllLTA1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXjMiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB0IiBrPSIwLjAwMDQ3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnYWwoVVMpIiBrPSIwLjAwMzc4NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYmwiIGs9IjAuMTU5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZiIgaz0iMi44MzE3ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YiIGs9IjIuODMxN2UrMTAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZiIgaz0iMjgzMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmIiBrPSIyOC4zMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMyIgaz0iMC4wMjgzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdhbChVSykiIGs9IjAuMDA0NTQ2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdpIiBrPSIwLjAwMDExOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJxdCIgaz0iMC4wMDA5NDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2MiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUuZnQiIGs9IjEyMzQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id2VpZ2h0IiBOYW1lPSJ3ZWlnaHQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0b24iIGs9IjEwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibHRvbiIgaz0iMTAxNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYiIgaz0iMC40NTQiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ib3oiIGs9IjAuMDI4MzUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RvbiIgaz0iOTA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImciIGs9IjAuMDAxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id3MiIE5hbWU9IuebkOW6piI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1nL0wiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0izrxnL21MIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvTCIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBwbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJLcHBtIiBrPSIxMDAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzEiIE5hbWU9IlRvRG8xIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9tXjMvZCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMiIgTmFtZT0iVG9EbzIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLCsCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMyIgTmFtZT0iVG9EbzMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvTVBhIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG80IiBOYW1lPSJUb0RvNCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1ELyhtUGEucykiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzUiIE5hbWU9IlRvRG81Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTVBhL20iIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzYiIE5hbWU9IlRvRG82Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibUQubSIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvNyIgTmFtZT0iVG9EbzciPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnL21vbCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KDQogICAgPC9UeXBlT2Y+DQo8L1Jvb3Q+DQo= \ No newline at end of file +77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iY29tcHJlc3NpYmlsaXR5IiBOYW1lPSJjb21wcmVzc2liaWxpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL01QYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL1BhIiBrPSIxZTYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9wc2kiIGs9IjE0NS4wMzgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZGVuc2l0eSIgTmFtZT0iZGVuc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY21eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiL2Z0XjMiIGs9IjAuMDE2MDIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVTKSIgaz0iMC4xMTk4MjYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVLKSIgaz0iMC4wOTk3NiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYi9iYmwiIGs9IjAuMDAyODUzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY2MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9jZiIgaz0iMy41MzE0N2UtMDUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9mdF4zIiBrPSIzLjUzMTQ3ZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImRlZy9tIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvaW4iIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWcva20iIGs9IjAuMDk5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvZnQiIGs9IjE4MS44MTgiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtIiBrPSIzOS4zNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtIiBrPSIwLjM5MzcwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbSIgaz0iMC4wMzkzNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImttIiBrPSIzOTM3MC4xIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0IiBrPSIxMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtaSIgaz0iNjMzNDYuNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ5ZCIgaz0iMzUuOTg3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubWlsZSIgaz0iNzI5MTMuNCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubSIgaz0iMy45MzcwMWUtMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0icmFkIiBrPSI3LjgyODMxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcmNlbnQiIE5hbWU9InBlcmNlbnQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIlIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB1IiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9tXjMiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ2L3YiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmcmFjdGlvbiIgaz0iMTAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRGFyY3kiIGs9IjEiLz4KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtRCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eLTIiIGs9IjkuODFlKzA3Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBvd2VyIiBOYW1lPSJwb3dlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iQnR1L2giIGs9IjAuMjkzMDcxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tL3MiIGs9IjkuODA2NjUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2FsL3MiIGs9IjQuMTg2OCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocCIgaz0iNzM1LjQ5OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcmVzc3VyZSIgTmFtZT0icHJlc3N1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJQYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJNUGEiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IktQYSIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYXIiIGs9IjEwMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJUb3JyIiBrPSIxMzMuMzIyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImR5bi9jbV4yIiBrPSIwLjEiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSGciIGs9IjEzMy4zMjIiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSDJPIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImF0IiBrPSI5ODA2Ni41Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBzaSIgaz0iNjg5Ni41NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhdG0iIGs9IjEwMTMyNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YvY21eMiIgaz0iOTgwNzkuNyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kdWN0aW9uIiBOYW1lPSJwcm9kdWN0aW9uIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2QiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idGNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2EiIGs9IjAuMDAyNzQwMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZi9kIiBrPSIyODMyMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtY2YvZCIgaz0iMjguMzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RiL2QiIGs9IjAuMTU5Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByb2RnYXMiIE5hbWU9InByb2RnYXMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvZCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxMF40Km1eMy9kIiBrPSIxMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvYSIgaz0iMC4wMDI3NDAwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbWNmL2QiIGs9IjI4MzIwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZi9kIiBrPSIyOC4zMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzdGIvZCIgaz0iMC4xNTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJvZG9pbCIgTmFtZT0icHJvZG9pbCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kd2F0ZXIiIE5hbWU9InByb2R3YXRlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJyYWRpb2FjdGl2aXR5IiBOYW1lPSJyYWRpb2FjdGl2aXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZ0FQSSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJBUEkiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ic2xvd25lc3MiIE5hbWU9InNsb3duZXNzIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idXMvbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ1cy9mdCIgaz0iMy4yODA4NCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzcXVhcmUiIE5hbWU9InNxdWFyZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrbV4yIiBrPSIxZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbl4yIiBrPSIwLjAwMDY0NTIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMiIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUiIGs9IjQwNDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZV4yIiBrPSIyLjU4ODg4ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmdF4yIiBrPSIwLjA5MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJoYSIgaz0iMTAwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ieWReMiIgaz0iMC44MzYxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InJhZDIiIGs9IjAuMDM5NTM2OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzdHJlbmd0aCIgTmFtZT0ic3RyZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJOIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiZiIgaz0iNC40NDQ0NCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YiIGs9IjkuODEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZHluIiBrPSIxZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ0ZW1wZXJhdHVyZSIgTmFtZT0idGVtcGVyYXR1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSyIgaz0iMSIgYj0iMjczLjE1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYiIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLihIMiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGhlbWFsIHBvd2VyIiBOYW1lPSJ0aGVtYWwgcG93ZXIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJKIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNhbCIgaz0iNC4xODY4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkJ0dSIgaz0iMTA1NS4wNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrVy5oIiBrPSIzLjZlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0LmxiZiIgaz0iMS4zNTU4MiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocC5oKFVTKSIgaz0iMi42NDc3OWUrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSHAuaChVSykiIGs9IjIuNjg0NTJlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnLm0iIGs9IjkuODAwMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2NhbCIgaz0iNDE4NS44NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJCVFUiIGs9IjEwNTQuODUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGltZSIgTmFtZT0idGltZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InVzIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1zIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InMiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1pbiIgaz0iNmUrMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaHIiIGs9IjMuNmUrMDkiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRGF5IiBrPSI4LjY0ZSsxMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2ZWxvY2l0eSIgTmFtZT0idmVsb2NpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZS9oIiBrPSIwLjQ0NzA0Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0L3MiIGs9IjAuMzA0OCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkiIE5hbWU9InZpc2Nvc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlBhLnMiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibVBhLnMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlAiIGs9IjAuMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjUCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmYucy9mdF4yIiBrPSI0Ny44ODAzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5zL21eMyIgaz0iOS44MDY2NSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkgYWN0IiBOYW1lPSJ2aXNjb3NpdHkgYWN0Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4yL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMi9zIiBrPSIwLjAwMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMi9zIiBrPSIwLjA5MjkwMyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJTdCIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNTdCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1eMi9zIiBrPSIxZS0wNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x0YWdlIiBOYW1lPSJ2b2x0YWdlIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibXYiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idiIgaz0iMTAwMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x1bWUiIE5hbWU9InZvbHVtZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaW5eMyIgaz0iMS42MzllLTA1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXjMiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB0IiBrPSIwLjAwMDQ3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnYWwoVVMpIiBrPSIwLjAwMzc4NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYmwiIGs9IjAuMTU5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZiIgaz0iMi44MzE3ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YiIGs9IjIuODMxN2UrMTAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZiIgaz0iMjgzMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmIiBrPSIyOC4zMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMyIgaz0iMC4wMjgzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdhbChVSykiIGs9IjAuMDA0NTQ2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdpIiBrPSIwLjAwMDExOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJxdCIgaz0iMC4wMDA5NDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2MiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUuZnQiIGs9IjEyMzQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id2VpZ2h0IiBOYW1lPSJ3ZWlnaHQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0b24iIGs9IjEwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibHRvbiIgaz0iMTAxNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYiIgaz0iMC40NTQiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ib3oiIGs9IjAuMDI4MzUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RvbiIgaz0iOTA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImciIGs9IjAuMDAxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id3MiIE5hbWU9IuebkOW6piI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1nL0wiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0izrxnL21MIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvTCIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBwbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJLcHBtIiBrPSIxMDAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzEiIE5hbWU9IlRvRG8xIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9tXjMvZCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMiIgTmFtZT0iVG9EbzIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLCsCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMyIgTmFtZT0iVG9EbzMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvTVBhIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG80IiBOYW1lPSJUb0RvNCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1ELyhtUGEucykiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzUiIE5hbWU9IlRvRG81Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTVBhL20iIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzYiIE5hbWU9IlRvRG82Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibUQubSIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvNyIgTmFtZT0iVG9EbzciPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnL21vbCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KDQogICAgPC9UeXBlT2Y+DQo8L1Jvb3Q+DQo= \ No newline at end of file diff --git a/Bin/Config/Unit/UnitDefault.dat b/Bin/Config/Unit/UnitDefault.dat index 1d2140c..7637503 100644 --- a/Bin/Config/Unit/UnitDefault.dat +++ b/Bin/Config/Unit/UnitDefault.dat @@ -1 +1 @@ -77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iY29tcHJlc3NpYmlsaXR5IiBOYW1lPSJjb21wcmVzc2liaWxpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL01QYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL1BhIiBrPSIxZTYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9wc2kiIGs9IjE0NS4wMzgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZGVuc2l0eSIgTmFtZT0iZGVuc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY21eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiL2Z0XjMiIGs9IjAuMDE2MDIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVTKSIgaz0iMC4xMTk4MjYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVLKSIgaz0iMC4wOTk3NiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYi9iYmwiIGs9IjAuMDAyODUzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY2MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9jZiIgaz0iMy41MzE0N2UtMDUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9mdF4zIiBrPSIzLjUzMTQ3ZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImRlZy9tIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvaW4iIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWcva20iIGs9IjAuMDk5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvZnQiIGs9IjE4MS44MTgiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtIiBrPSIzOS4zNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtIiBrPSIwLjM5MzcwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbSIgaz0iMC4wMzkzNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImttIiBrPSIzOTM3MC4xIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0IiBrPSIxMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtaSIgaz0iNjMzNDYuNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ5ZCIgaz0iMzUuOTg3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubWlsZSIgaz0iNzI5MTMuNCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubSIgaz0iMy45MzcwMWUtMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0icmFkIiBrPSI3LjgyODMxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcmNlbnQiIE5hbWU9InBlcmNlbnQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIlIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB1IiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9tXjMiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ2L3YiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmcmFjdGlvbiIgaz0iMTAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtRCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eLTIiIGs9IjkuODFlKzA3Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBvd2VyIiBOYW1lPSJwb3dlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iQnR1L2giIGs9IjAuMjkzMDcxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tL3MiIGs9IjkuODA2NjUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2FsL3MiIGs9IjQuMTg2OCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocCIgaz0iNzM1LjQ5OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcmVzc3VyZSIgTmFtZT0icHJlc3N1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJQYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJNUGEiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IktQYSIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYXIiIGs9IjEwMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJUb3JyIiBrPSIxMzMuMzIyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImR5bi9jbV4yIiBrPSIwLjEiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSGciIGs9IjEzMy4zMjIiLz4NCiAgICAgICAgICAgIDxVbml0IFVuaXQ9Im1tSDJPIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImF0IiBrPSI5ODA2Ni41Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBzaSIgaz0iNjg5Ni41NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhdG0iIGs9IjEwMTMyNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YvY21eMiIgaz0iOTgwNzkuNyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kdWN0aW9uIiBOYW1lPSJwcm9kdWN0aW9uIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2QiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idGNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2EiIGs9IjAuMDAyNzQwMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZi9kIiBrPSIyODMyMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtY2YvZCIgaz0iMjguMzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RiL2QiIGs9IjAuMTU5Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByb2RnYXMiIE5hbWU9InByb2RnYXMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvZCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxMF40Km1eMy9kIiBrPSIxMDAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvYSIgaz0iMC4wMDI3NDAwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbWNmL2QiIGs9IjI4MzIwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZi9kIiBrPSIyOC4zMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzdGIvZCIgaz0iMC4xNTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJvZG9pbCIgTmFtZT0icHJvZG9pbCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kd2F0ZXIiIE5hbWU9InByb2R3YXRlciI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJyYWRpb2FjdGl2aXR5IiBOYW1lPSJyYWRpb2FjdGl2aXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZ0FQSSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJBUEkiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ic2xvd25lc3MiIE5hbWU9InNsb3duZXNzIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idXMvbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ1cy9mdCIgaz0iMy4yODA4NCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzcXVhcmUiIE5hbWU9InNxdWFyZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrbV4yIiBrPSIxZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbl4yIiBrPSIwLjAwMDY0NTIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMiIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUiIGs9IjQwNDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZV4yIiBrPSIyLjU4ODg4ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmdF4yIiBrPSIwLjA5MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJoYSIgaz0iMTAwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ieWReMiIgaz0iMC44MzYxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InJhZDIiIGs9IjAuMDM5NTM2OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJzdHJlbmd0aCIgTmFtZT0ic3RyZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJOIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiZiIgaz0iNC40NDQ0NCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YiIGs9IjkuODEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZHluIiBrPSIxZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ0ZW1wZXJhdHVyZSIgTmFtZT0idGVtcGVyYXR1cmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSyIgaz0iMSIgYj0iMjczLjE1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYiIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLihIMiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGhlbWFsIHBvd2VyIiBOYW1lPSJ0aGVtYWwgcG93ZXIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJKIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNhbCIgaz0iNC4xODY4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5tIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkJ0dSIgaz0iMTA1NS4wNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrVy5oIiBrPSIzLjZlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0LmxiZiIgaz0iMS4zNTU4MiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJocC5oKFVTKSIgaz0iMi42NDc3OWUrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSHAuaChVSykiIGs9IjIuNjg0NTJlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnLm0iIGs9IjkuODAwMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2NhbCIgaz0iNDE4NS44NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJCVFUiIGs9IjEwNTQuODUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGltZSIgTmFtZT0idGltZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InVzIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1zIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InMiIGs9IjFlKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1pbiIgaz0iNmUrMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaHIiIGs9IjMuNmUrMDkiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRGF5IiBrPSI4LjY0ZSsxMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2ZWxvY2l0eSIgTmFtZT0idmVsb2NpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWlsZS9oIiBrPSIwLjQ0NzA0Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0L3MiIGs9IjAuMzA0OCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkiIE5hbWU9InZpc2Nvc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlBhLnMiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibVBhLnMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IlAiIGs9IjAuMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjUCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmYucy9mdF4yIiBrPSI0Ny44ODAzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtnZi5zL21eMyIgaz0iOS44MDY2NSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2aXNjb3NpdHkgYWN0IiBOYW1lPSJ2aXNjb3NpdHkgYWN0Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4yL3MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY21eMi9zIiBrPSIwLjAwMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMi9zIiBrPSIwLjA5MjkwMyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJTdCIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNTdCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1eMi9zIiBrPSIxZS0wNiIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x0YWdlIiBOYW1lPSJ2b2x0YWdlIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibXYiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idiIgaz0iMTAwMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJ2b2x1bWUiIE5hbWU9InZvbHVtZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbCIgaz0iMWUtMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTCIgaz0iMC4wMDEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaW5eMyIgaz0iMS42MzllLTA1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXjMiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB0IiBrPSIwLjAwMDQ3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnYWwoVVMpIiBrPSIwLjAwMzc4NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiYmwiIGs9IjAuMTU5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZiIgaz0iMi44MzE3ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YiIGs9IjIuODMxN2UrMTAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZiIgaz0iMjgzMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmIiBrPSIyOC4zMTciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMyIgaz0iMC4wMjgzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdhbChVSykiIGs9IjAuMDA0NTQ2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdpIiBrPSIwLjAwMDExOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJxdCIgaz0iMC4wMDA5NDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY2MiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImFjcmUuZnQiIGs9IjEyMzQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id2VpZ2h0IiBOYW1lPSJ3ZWlnaHQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0b24iIGs9IjEwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibHRvbiIgaz0iMTAxNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYiIgaz0iMC40NTQiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ib3oiIGs9IjAuMDI4MzUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RvbiIgaz0iOTA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImciIGs9IjAuMDAxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0id3MiIE5hbWU9IuebkOW6piI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1nL0wiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0izrxnL21MIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvTCIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InBwbSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJLcHBtIiBrPSIxMDAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KDQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzEiIE5hbWU9IlRvRG8xIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9tXjMvZCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMiIgTmFtZT0iVG9EbzIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSLCsCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvMyIgTmFtZT0iVG9EbzMiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvTVBhIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG80IiBOYW1lPSJUb0RvNCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1ELyhtUGEucykiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzUiIE5hbWU9IlRvRG81Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTVBhL20iIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzYiIE5hbWU9IlRvRG82Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibUQubSIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvNyIgTmFtZT0iVG9EbzciPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnL21vbCIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KDQogICAgPC9UeXBlT2Y+DQo8L1Jvb3Q+DQo= \ No newline at end of file +77u/PD94bWwgcup_mpadmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxSb290Pg0KICAgIDxUeXwzyBlT2Y+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iY29tcHJlc3NpYmlsaXR5IiBOYW1lPSJjb21wcmVzc2liaWxpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL01QYSIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIxL1BhIiBrPSIxZTYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9wc2kiIGs9IjE0NS4wMzgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMS9hdG0iIGs9IjkuODY5MjMiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iZGVuc2l0eSIgTmFtZT0iZGVuc2l0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY21eMyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy9tXjMiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImxiL2Z0XjMiIGs9IjAuMDE2MDIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVTKSIgaz0iMC4xMTk4MjYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIvZ2FsKFVLKSIgaz0iMC4wOTk3NiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYi9iYmwiIGs9IjAuMDAyODUzIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImcvY2MiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9jZiIgaz0iMy41MzE0N2UtMDUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9mdF4zIiBrPSIzLjUzMTQ3ZS0wNSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJnZW90aGVybWFsZ3JhZGllbnQiIE5hbWU9Imdlb3RoZXJtYWxncmFkaWVudCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImRlZy9tIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvaW4iIGs9IjEuOCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkZWcva20iIGs9IjAuMDk5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvbWlsZSIgaz0iMC4wMzQ0ODI4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkYvZnQiIGs9IjE4MS44MTgiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ibGVuZ3RoIiBOYW1lPSJsZW5ndGgiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJpbiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtIiBrPSIzOS4zNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtIiBrPSIwLjM5MzcwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbSIgaz0iMC4wMzkzNzAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImttIiBrPSIzOTM3MC4xIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0IiBrPSIxMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtaSIgaz0iNjMzNDYuNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ5ZCIgaz0iMzUuOTg3MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubWlsZSIgaz0iNzI5MTMuNCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJubSIgaz0iMy45MzcwMWUtMDgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0icmFkIiBrPSI3LjgyODMxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcmNlbnQiIE5hbWU9InBlcmNlbnQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSIlIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InB1IiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9tXjMiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ2L3YiIGs9IjEwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmcmFjdGlvbiIgaz0iMTAwIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InBlcm1lYWJpbGl0eSIgTmFtZT0icGVybWVhYmlsaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iRGFyY3kiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibUQiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXi0yIiBrPSI5LjgxZSswNyIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwb3dlciIgTmFtZT0icG93ZXIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJXIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkJ0dS9oIiBrPSIwLjI5MzA3MSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YubS9zIiBrPSI5LjgwNjY1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNhbC9zIiBrPSI0LjE4NjgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaHAiIGs9IjczNS40OTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJlc3N1cmUiIE5hbWU9InByZXNzdXJlIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iUGEiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTVBhIiBrPSIxZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJLUGEiIGs9IjEwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmFyIiBrPSIxMDAwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iVG9yciIgaz0iMTMzLjMyMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJkeW4vY21eMiIgaz0iMC4xIi8+DQogICAgICAgICAgICA8VW5pdCBVbml0PSJtbUhnIiBrPSIxMzMuMzIyIi8+DQogICAgICAgICAgICA8VW5pdCBVbml0PSJtbUgyTyIgaz0iOS44MDY2NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhdCIgaz0iOTgwNjYuNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJwc2kiIGs9IjY4OTYuNTUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYXRtIiBrPSIxMDEzMjUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2dmL2NtXjIiIGs9Ijk4MDc5LjciLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJvZHVjdGlvbiIgTmFtZT0icHJvZHVjdGlvbiI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9kIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InRjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMy9hIiBrPSIwLjAwMjc0MDA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJjZi9kIiBrPSIwLjAyODMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YvZCIgaz0iMjgzMjAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWNmL2QiIGs9IjI4LjMyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0Yi9kIiBrPSIwLjE1OSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJwcm9kZ2FzIiBOYW1lPSJwcm9kZ2FzIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2QiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iMTBeNCptXjMvZCIgaz0iMTAwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idGNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL2EiIGs9IjAuMDAyNzQwMDciLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmNmL2QiIGs9IjAuMDI4MzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibW1jZi9kIiBrPSIyODMyMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtY2YvZCIgaz0iMjguMzIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ic3RiL2QiIGs9IjAuMTU5Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InByb2RvaWwiIE5hbWU9InByb2RvaWwiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvZCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvYSIgaz0iMC4wMDI3NDAwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbWNmL2QiIGs9IjI4MzIwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZi9kIiBrPSIyOC4zMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzdGIvZCIgaz0iMC4xNTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icHJvZHdhdGVyIiBOYW1lPSJwcm9kd2F0ZXIiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvZCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ0Y2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMvYSIgaz0iMC4wMDI3NDAwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YvZCIgaz0iMC4wMjgzMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtbWNmL2QiIGs9IjI4MzIwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZi9kIiBrPSIyOC4zMiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzdGIvZCIgaz0iMC4xNTkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0icmFkaW9hY3Rpdml0eSIgTmFtZT0icmFkaW9hY3Rpdml0eSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImdBUEkiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iQVBJIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InNsb3duZXNzIiBOYW1lPSJzbG93bmVzcyI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InVzL20iIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idXMvZnQiIGs9IjMuMjgwODQiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ic3F1YXJlIiBOYW1lPSJzcXVhcmUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjIiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia21eMiIgaz0iMWUrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaW5eMiIgaz0iMC4wMDA2NDUyIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXjIiIGs9IjAuMDAwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhY3JlIiBrPSI0MDQ3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1pbGVeMiIgaz0iMi41ODg4OGUrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZnReMiIgaz0iMC4wOTMiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaGEiIGs9IjEwMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InlkXjIiIGs9IjAuODM2MSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJyYWQyIiBrPSIwLjAzOTUzNjkiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0ic3RyZW5ndGgiIE5hbWU9InN0cmVuZ3RoIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iTiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJsYmYiIGs9IjQuNDQ0NDQiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2dmIiBrPSI5LjgxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImR5biIgaz0iMWUtMDUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idGVtcGVyYXR1cmUiIE5hbWU9InRlbXBlcmF0dXJlIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZGVnIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IksiIGs9IjEiIGI9IjI3My4xNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJGIiBrPSIxLjgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0i4oSDIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRoZW1hbCBwb3dlciIgTmFtZT0idGhlbWFsIHBvd2VyIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iSiIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjYWwiIGs9IjQuMTg2OCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2YubSIgaz0iOS44MDY2NSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJCdHUiIGs9IjEwNTUuMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia1cuaCIgaz0iMy42ZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmdC5sYmYiIGs9IjEuMzU1ODIiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iaHAuaChVUykiIGs9IjIuNjQ3NzllKzA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkhwLmgoVUspIiBrPSIyLjY4NDUyZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZy5tIiBrPSI5LjgwMDA4Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImtjYWwiIGs9IjQxODUuODUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iQlRVIiBrPSIxMDU0Ljg1Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9InRpbWUiIE5hbWU9InRpbWUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJ1cyIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtcyIgaz0iMTAwMCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJzIiBrPSIxZSswNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtaW4iIGs9IjZlKzA3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImhyIiBrPSIzLjZlKzA5Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkRheSIgaz0iOC42NGUrMTAiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idmVsb2NpdHkiIE5hbWU9InZlbG9jaXR5Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibS9zIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1pbGUvaCIgaz0iMC40NDcwNCIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJmdC9zIiBrPSIwLjMwNDgiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idmlzY29zaXR5IiBOYW1lPSJ2aXNjb3NpdHkiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJQYS5zIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1QYS5zIiBrPSIwLjAwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJQIiBrPSIwLjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iY1AiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImJmLnMvZnReMiIgaz0iNDcuODgwMyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJrZ2Yucy9tXjMiIGs9IjkuODA2NjUiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idmlzY29zaXR5IGFjdCIgTmFtZT0idmlzY29zaXR5IGFjdCI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1eMi9zIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNtXjIvcyIgaz0iMC4wMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0XjIvcyIgaz0iMC4wOTI5MDMiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iU3QiIGs9IjAuMDAwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjU3QiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tXjIvcyIgaz0iMWUtMDYiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idm9sdGFnZSIgTmFtZT0idm9sdGFnZSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im12IiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InYiIGs9IjEwMDAiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0idm9sdW1lIiBOYW1lPSJ2b2x1bWUiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtXjMiIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibWwiIGs9IjFlLTA2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IkwiIGs9IjAuMDAxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImluXjMiIGs9IjEuNjM5ZS0wNSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJjbV4zIiBrPSIxZS0wNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJwdCIgaz0iMC4wMDA0NzMiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZ2FsKFVTKSIgaz0iMC4wMDM3ODUiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iYmJsIiBrPSIwLjE1OSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJiY2YiIGs9IjIuODMxN2UrMDYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idGNmIiBrPSIyLjgzMTdlKzEwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1tY2YiIGs9IjI4MzE3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1jZiIgaz0iMjguMzE3Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImZ0XjMiIGs9IjAuMDI4MyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnYWwoVUspIiBrPSIwLjAwNDU0NiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnaSIgaz0iMC4wMDAxMTgiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0icXQiIGs9IjAuMDAwOTQ2Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9ImNjIiBrPSIxZS0wNiIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJhY3JlLmZ0IiBrPSIxMjM0Ii8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IndlaWdodCIgTmFtZT0id2VpZ2h0Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2ciIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0idG9uIiBrPSIxMDAwIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Imx0b24iIGs9IjEwMTYiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibGIiIGs9IjAuNDU0Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im96IiBrPSIwLjAyODM1Ii8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9InN0b24iIGs9IjkwNyIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnIiBrPSIwLjAwMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IndzIiBOYW1lPSLnm5DluqYiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtZy9MIiBrPSIxIi8+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Is68Zy9tTCIgaz0iMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJnL0wiIGs9IjEwMDAiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ia2cvbV4zIiBrPSIwLjAwMSIvPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJwcG0iIGs9IjEiLz4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iS3BwbSIgaz0iMTAwMCIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG8xIiBOYW1lPSJUb0RvMSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9IjEvbV4zL2QiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzIiIE5hbWU9IlRvRG8yIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iwrAiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzMiIE5hbWU9IlRvRG8zIj4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0ibV4zL01QYSIgaz0iMSIvPg0KICAgICAgICA8L1VuaXRHcm91cD4NCiAgICAgICAgPFVuaXRHcm91cCBUeXBlPSJUb0RvNCIgTmFtZT0iVG9EbzQiPg0KICAgICAgICAgICAgPFVuaXQgUmVtYXJrPSIgIiBVbml0PSJtRC8obVBhLnMpIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG81IiBOYW1lPSJUb0RvNSI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Ik1QYS9tIiBrPSIxIi8+DQogICAgICAgIDwvVW5pdEdyb3VwPg0KICAgICAgICA8VW5pdEdyb3VwIFR5cGU9IlRvRG82IiBOYW1lPSJUb0RvNiI+DQogICAgICAgICAgICA8VW5pdCBSZW1hcms9IiAiIFVuaXQ9Im1ELm0iIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQogICAgICAgIDxVbml0R3JvdXAgVHlwZT0iVG9EbzciIE5hbWU9IlRvRG83Ij4NCiAgICAgICAgICAgIDxVbml0IFJlbWFyaz0iICIgVW5pdD0iZy9tb2wiIGs9IjEiLz4NCiAgICAgICAgPC9Vbml0R3JvdXA+DQoNCg0KICAgIDwvVHlwZU9mPg0KPC9Sb290Pg0K \ No newline at end of file diff --git a/Bin/XmlFiles/ModelParaDefinesNM_cn.xml b/Bin/XmlFiles/ModelParaDefinesNM_cn.xml index 367fb1c..f26f2ef 100644 --- a/Bin/XmlFiles/ModelParaDefinesNM_cn.xml +++ b/Bin/XmlFiles/ModelParaDefinesNM_cn.xml @@ -17,7 +17,7 @@ - + @@ -90,7 +90,7 @@ - + diff --git a/Bin/XmlFiles/ModelParaDefinesNM_en.xml b/Bin/XmlFiles/ModelParaDefinesNM_en.xml index fb7e17f..ad27365 100644 --- a/Bin/XmlFiles/ModelParaDefinesNM_en.xml +++ b/Bin/XmlFiles/ModelParaDefinesNM_en.xml @@ -18,7 +18,7 @@ - + @@ -91,7 +91,7 @@ - + diff --git a/Bin/XmlFiles/UnitDefault_cn.xml b/Bin/XmlFiles/UnitDefault_cn.xml index e96401b..28882e7 100644 --- a/Bin/XmlFiles/UnitDefault_cn.xml +++ b/Bin/XmlFiles/UnitDefault_cn.xml @@ -166,7 +166,7 @@ - + diff --git a/Bin/XmlFiles/UnitDefault_en.xml b/Bin/XmlFiles/UnitDefault_en.xml index b6fa119..3c4111b 100644 --- a/Bin/XmlFiles/UnitDefault_en.xml +++ b/Bin/XmlFiles/UnitDefault_en.xml @@ -46,7 +46,7 @@ - + From 5539150b5b137b614ed7010a247f1c322fb3c4c3 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 7 Aug 2026 14:47:47 +0800 Subject: [PATCH 04/12] =?UTF-8?q?1.=E8=87=AA=E5=8A=A8=E6=8B=9F=E5=90=88?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E8=8C=83=E5=9B=B4=E8=AE=BE=E7=BD=AE=E4=B8=8E?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=20=202.=E7=A7=BB=E9=99=A4GA=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/MPA_cn.qm | Bin 48283 -> 47628 bytes Bin/Config/Lang/cn/MPA_cn.ts | 6 - Bin/Config/Lang/cn/WTAI_cn.ts | 4 - Bin/Config/Lang/cn/nmNum_cn.qm | Bin 103081 -> 93646 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 389 +-- Include/mAlg/mAlgDefines/mAlgDefines.h | 3 +- .../nmCalculation/nmCalculationAutoFitGA.h | 267 -- Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 19 +- .../nmSubWxs/nmWxAutomaticFittingStart.h | 12 - .../nmCalculation/nmCalculationAutoFitGA.cpp | 2911 ----------------- Src/nmNum/nmData/nmDataAutomaticFitting.cpp | 37 +- Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 709 ++-- .../nmSubWxs/nmWxAutomaticFittingStart.cpp | 73 +- 13 files changed, 499 insertions(+), 3931 deletions(-) delete mode 100644 Include/nmNum/nmCalculation/nmCalculationAutoFitGA.h delete mode 100644 Src/nmNum/nmCalculation/nmCalculationAutoFitGA.cpp diff --git a/Bin/Config/Lang/cn/MPA_cn.qm b/Bin/Config/Lang/cn/MPA_cn.qm index 496f6ae54e1b7376588bb4f1040258643aebadeb..3d5457b619164042b996a9dffda998805e1ac842 100644 GIT binary patch delta 6394 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 Kw3a)tO#VMd90xc6 delta 6953 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 Gauss Newton 规则化高斯-牛顿 - - - - Genetic Algorithm - 遗传算法 - Parameter diff --git a/Bin/Config/Lang/cn/WTAI_cn.ts b/Bin/Config/Lang/cn/WTAI_cn.ts index 2fd73d5..46e180b 100644 --- a/Bin/Config/Lang/cn/WTAI_cn.ts +++ b/Bin/Config/Lang/cn/WTAI_cn.ts @@ -4715,10 +4715,6 @@ MethodID:%1 Normalized Gauss Newton 规则化高斯-牛顿 - - Genetic Algorithm - 遗传算法 - Parameter diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index bdc77257ab79843b77a2408f204d5cb53efec515..19bf9a383803b135f0206e61491f581566c84ea4 100644 GIT binary patch delta 10313 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 delta 13259 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 Witg8ATGhAT+#rzN5RE^^R diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 5879529..465cceb 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -47,345 +47,6 @@ Reason: %1 压力(MPa) - - nmCalculationAutoFitGA - - === GA Automatic Fitting Started === - === GA自动拟合开始 === - - - Algorithm: Genetic Algorithm - 算法:遗传算法 - - - ERROR: Failed to load configuration from data manager - 错误:从数据管理器加载配置失败 - - - Enabled parameters count: %1 - 启用参数数量:%1 - - - ERROR: No parameters enabled for optimization - 错误:没有启用优化参数 - - - ERROR: Target LogLog data is empty or insufficient - 错误:目标双对数数据为空或不足 - - - ERROR: Target LogLog data arrays have inconsistent sizes - 错误:目标双对数数据数组大小不一致 - - - Target data validation passed (%1 data points) - 目标数据验证通过(%1 个数据点) - - - === Evaluating Initial Solution (Elite Protection) === - === 评估初始解 === - - - Initial parameters: - 初始参数: - - - Starting initial solution evaluation... - 开始初始解评估... - - - ERROR: m_userInitialSolution is empty! - 错误:初始解为空! - - - Initial param[%1] = %2 - 初始参数[%1] = %2 - - - evaluateGenes returned: %1 - 评估基因 返回:%1 - - - Taking SUCCESS branch (fitness < 1e9) - 进入成功分支 - - - Initial solution evaluation successful - 初始解评估成功 - - - Initial fitness (error): %1 - 初始误差:%1 - - - Elite protection activated - initial solution will be preserved if no significant improvement found - 如果未找到显著改进,将保留初始解. - - - Taking FAILURE branch (fitness >= 1e9) - 进入失败分支 - - - Initial solution evaluation failed - starting with random initialization - 初始解评估失败 - 使用随机初始化开始 - - - Exception during initial solution evaluation - 初始解评估期间出现异常 - - - Population initialized: %1 individuals, %2 dimensions - 种群初始化:%1 个个体,%2 个维度 - - - === Starting GA Main Loop === - === 开始GA主循环 === - - - --- Generation %1/%2 --- - --- 第 %1/%2 代 --- - - - Current best error: %1 - 当前最佳误差:%1 - - - Total evaluations: %1 (successful: %2, failures: %3) - 总评估次数:%1(成功:%2,失败:%3) - - - Optimization stopped by user request - 优化因用户请求而停止 - - - Generation %1 completed: best = %2, avg = %3, worst = %4 - 第 %1 代完成:最佳 = %2,平均 = %3,最差 = %4 - - - === TARGET ACHIEVED === - === 达到目标 === - - - Target error achieved! Current error: %1 < Target: %2 - 达到目标误差!当前误差:%1 < 目标:%2 - - - Optimization completed successfully after %1 generations - 达到目标误差!当前误差:%1 < 目标:%2 - - - === TRUE CONVERGENCE DETECTED === - === 检测到真正收敛 === - - - Algorithm has converged to a stable solution - 算法已收敛到稳定解 - - - Final error: %1 after %2 generations - 最终误差:%1,经过 %2 代 - - - Solution quality: %1 (1.0 = target achieved) - 解质量:%1 - - - === LOCAL OPTIMUM DETECTED === - === 检测到局部最优 === - - - Algorithm appears to be trapped in local optimum - 算法似乎陷入局部最优 - - - Current error: %1 after %2 generations - 当前误差:%1,经过 %2 代 - - - Suggestion: Try restarting with different parameters or larger search space - 建议:尝试使用不同参数或更大搜索空间重新开始 - - - === CONSECUTIVE FAILURES === - === 连续失败 === - - - Too many consecutive failed generations (%1/%2) - 连续失败代数过多(%1/%2) - - - Optimization status: diversity=%1 - 优化状态:多样性=%1 - - - Generation %1 completed - Current best: %2 - 第 %1 代完成 - 当前最佳:%2 - - - CRITICAL ERROR: %1 - 严重错误: %1 - - - CRITICAL ERROR: Unknown exception in GA main loop - 严重错误:GA主循环中的未知异常 - - - Applying optimized parameters to model... - 正在将优化参数应用到模型... - - - === Optimization Results === - === 优化结果 === - - - Final error: %1 - 最终误差: %1 - - - Total generations: %1 - 总代数:%1 - - - Total evaluations: %1 (successful: %2) - 总评估次数: %1 (成功: %2) - - - Optimized parameters: - 优化参数: - - - Parameters applied successfully to data manager - 参数已成功应用到数据管理器 - - - ERROR: Failed to apply final parameters: %1 - 错误: 应用最终参数失败: %1 - - - ERROR: Unknown error applying final parameters - 错误: 应用最终参数时出现未知错误 - - - === GA OPTIMIZATION SUCCESSFUL === - === GA优化成功 === - - - === GA OPTIMIZATION CONVERGED === - === GA优化收敛 === - - - === GA OPTIMIZATION - LOCAL OPTIMUM === - === GA优化 - 局部最优 === - - - === GA OPTIMIZATION - MAX GENERATIONS === - === GA优化 - 达到最大代数 === - - - === GA OPTIMIZATION STOPPED BY USER === - === GA优化 - 用户停止 === - - - === GA OPTIMIZATION FAILED === - === GA优化失败 === - - - === GA OPTIMIZATION - UNKNOWN END === - === GA优化 - 未知结束 === - - - Result: %1 - 结果: %1 - - - === User Stop Request Received === - === 用户停止请求已接收 === - - - Gracefully stopping GA optimization... - 在停止GA优化... - - - Force stopping current evaluation... - 强制停止当前评估... - - - GA optimization stop request processed - GA优化停止请求已处理 - - - Stop request received but optimization is not running - 收到停止请求但优化未运行 - - - Individual %1 improved: %2 -> %3 - 个体 %1 改进:%2 -> %3 - - - Individual %1: evaluation failed - 个体 %1:评估失败 - - - Individual %1: Exception: %2 - 个体 %1:异常:%2 - - - Individual %1: Unknown exception - 个体 %1:未知异常 - - - WARNING: No successful evaluations in generation %1 (consecutive failures: %2) - 警告:第 %1 代中没有成功评估(连续失败:%2) - - - Current generation stats: %1 successful, %2 failed out of %3 individuals (success rate: %4%) - 当前代统计:%3 个个体中 %1 个成功,%2 个失败(成功率:%4%) - - - ERROR: Too many consecutive failed generations (%1/%2) - stopping optimization - 错误:连续失败代数过多(%1/%2)- 停止优化 - - - WARNING: Low success rate (%1%) in generation %2, but continuing optimization - 警告:第 %2 代中成功率低(%1%),但继续优化 - - - No initial solution for elite protection - 没有初始解用于迭代 - - - === Final Result Validation (Elite Protection) === - === 最终结果验证 === - - - Comparing results: Initial=%1, Final=%2 - 比较结果: 初始=%1, 最终=%2 - - - Improvement: %1 (%2%) - 改进: %1 (%2%) - - - Elite protection triggered: insufficient improvement - 改进不足 - - - Threshold: %1%, Actual: %2% - 阈值: %1%, 实际: %2% - - - Restoring initial solution as final result - 恢复初始解作为最终结果 - - - Initial solution restored successfully - 初始解恢复成功 - - - Final result validated - significant improvement achieved - 最终结果已验证 - 实现显著改进 - - nmCalculationAutoFitPSO @@ -3698,6 +3359,38 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We Warning 警告 + + Invalid parameter range + 参数范围无效 + + + The parameter table is unavailable. + 参数表不可用。 + + + The parameter row is invalid. + 参数行无效。 + + + The range values for %1 are incomplete. + %1 的范围值不完整。 + + + The minimum value, initial value, and maximum value of %1 must be finite numbers. + %1 的最小值、初始值和最大值必须是有限数值。 + + + The physical range of %1 is invalid. + %1 的物理范围无效。 + + + The values of %1 exceed the physical range [%2, %3]. + %1 的参数值超出物理范围 [%2, %3]。 + + + The values of %1 must satisfy: minimum <= initial value <= maximum. + %1 的参数值必须满足:最小值 <= 初始值 <= 最大值。 + Please select a target well! 请选择一口目标井! @@ -3740,11 +3433,6 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We Optimized parameters have been applied to the model. 拟合参数已应用到模型。 - - GA Optimization completed: - - GA求解完成: - Optimization Completed 拟合完成 @@ -3774,27 +3462,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We Optimization Stopped 拟合停止 - - GA Optimization stopped by user: - - GA拟合被用户强行终止: - PSO algorithm selected. 用户选择PSO。 - - GA algorithm selected. - 用户选择GA - PSO (Particle Swarm) PSO - - GA (Genetic Algorithm) - GA - Darcy diff --git a/Include/mAlg/mAlgDefines/mAlgDefines.h b/Include/mAlg/mAlgDefines/mAlgDefines.h index 7c96b83..4eeaa12 100644 --- a/Include/mAlg/mAlgDefines/mAlgDefines.h +++ b/Include/mAlg/mAlgDefines/mAlgDefines.h @@ -292,8 +292,7 @@ enum Fit_Method { FM_GaussNewton = 0, //高斯牛顿 FM_GaussNewtonEx, //归一化高斯牛顿 - FM_Genetic, //遗传算法 - FM_ParticleSwarm, //粒子群算法(Particle Swarm Optimization) + FM_ParticleSwarm = 3, //粒子群算法(Particle Swarm Optimization),保留原有枚举值 FM_Unknown }; diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitGA.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitGA.h deleted file mode 100644 index 64f9e4d..0000000 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitGA.h +++ /dev/null @@ -1,267 +0,0 @@ -#ifndef NMCALCULATIONAUTOFITGA_H -#define NMCALCULATIONAUTOFITGA_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nmCalculation_global.h" - -class nmDataWellBase; - -enum StopReasonGA { - GA_CONTINUE_OPTIMIZATION = 0, - GA_TARGET_ACHIEVED, - GA_TRUE_CONVERGENCE, - GA_LOCAL_OPTIMUM, - GA_MAX_ITERATIONS, - GA_USER_STOPPED, - GA_CONSECUTIVE_FAILURES, - GA_OPTIMIZATION_FAILED -}; - -struct GAIndividual -{ - QVector genes; // 基因(参数值) - double fitness; // 适应度值 - bool isEvaluated; // 是否已评估 - - GAIndividual() : fitness(1e10), isEvaluated(false) {} -}; - -class NMCALCULATION_EXPORT nmCalculationAutoFitGA : public QObject -{ - Q_OBJECT - -public: - explicit nmCalculationAutoFitGA(QObject* parent = nullptr); - virtual ~nmCalculationAutoFitGA(); - - // ==================== 公共接口方法 ==================== - void setTargetLogLogData(const QVector>& targetData); - bool startAutoFitting(); - void stopFitting(); - bool isRunning() const; - int getCurrentGeneration() const; - QVector getBestSolution() const; - double getBestFitness() const; - QString getLastError() const; - void resetOptimizer(); - - void setGATargetWellName(const QString& wellName); - -signals: - void progressUpdated(int generation, double bestFitness); - void fittingFinished(bool success, const QString& message); - void logMessageGenerated(const QString& message); - - private slots: - void updateProgress(); - -private: - - // 临时目录管理 - void initializeTemporaryDirectory(); - void cleanupTemporaryDirectory(); - bool removeDirectoryRecursively(const QString& path); - - // ==================== 数据加载方法 ==================== - // 从数据管理器加载所有配置 - bool loadAllConfigFromDataManager(); - // 加载优化配置 - void loadOptimizationConfig(); - // 加载参数边界 - void loadParameterBounds(); - // 提取用户初始值 - void extractUserInitialValues(); - // ==================== 遗传算法核心方法 ==================== - // 初始化种群 - void initializePopulation(); - // 评估基因 - double evaluateGenes(const QVector& genes); - // 评估个体 - double evaluateIndividual(GAIndividual& individual); - // 评估种群 - void evaluatePopulation(); - // 选择操作 - int tournamentSelection(); - int rouletteWheelSelection(); - - // 交叉操作 - void crossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2); - void singlePointCrossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2); - void uniformCrossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2); - - // 变异操作 - void mutate(GAIndividual& individual); - void gaussianMutation(GAIndividual& individual); - void polynomialMutation(GAIndividual& individual); - - // 精英保留 - void applyElitism(QVector& newPopulation); - // 更新种群统计 - void updatePopulationStatistics(); - // 收敛检查 - bool checkConvergence(); - // 自适应参数更新 - void adaptiveParameterUpdate(int generation); - - // ==================== 智能收敛判断方法 ==================== - // 分析优化状态 - StopReasonGA analyzeOptimizationStatus(); - // 检查真收敛 - bool checkTrueConvergence() const; - // 检查局部最优陷阱 - bool checkLocalOptimumTrap() const; - // 计算种群多样性 - double calculatePopulationDiversity() const; - // 计算适应度方差 - double calculateFitnessVariance(int windowSize) const; - // 计算长期改进 - double calculateLongTermImprovement(int windowSize) const; - // 更新收敛指标 - void updateConvergenceMetrics(); - // 最终结果验证和保护 - void validateAndProtectFinalResult(); - - // ==================== 参数处理方法 ==================== - // 参数验证 - bool validateParameters(const QVector& parameters) const; - // 双对数数据验证 - bool validateLogLogData(const QVector>& logLogData) const; - // 初始值验证 - bool validateInitialValues() const; - // 应用参数到数据管理器 - void applyParametersToDataManager(const QVector& parameters); - // 更新储层参数 - void updateReservoirParameters(const QVector& parameters); - // 更新井参数 - void updateWellParameters(const QVector& parameters); - // 更新井到数据管理器 - void updateWellToDataManager(nmDataWellBase* pWell); - // 参数边界约束 - void clampToLimits(QVector& parameters) const; - - // ==================== 求解器相关方法 ==================== - // 运行求解器 - QVector> runSolver(); - // 运行EXE求解器 - QVector> runSolverExe(); - // 运行Dll求解器 - QVector> runSolverDll(); - // 验证求解器结果 - bool validateSolverResult(const QVector>& result) const; - - // ==================== 数据处理方法 ==================== - // 插值数据 - QVector interpolateData(const QVector& source, - const QVector& targetX) const; - // 计算双对数曲线误差 - double calculateLogLogCurveError(const QVector>& target, - const QVector>& result) const; - // 计算曲线误差 - double calculateCurveError(const QVector& curve1, - const QVector& curve2) const; - - // ==================== 工具方法 ==================== - // 生成0-1随机数 - double random01() const; - // 高斯随机数 - double gaussianRandom(double mean, double stddev) const; - // 获取启用参数数量 - int getEnabledParameterCount() const; - // 保存优化结果 - void saveOptimizationResult(); - - -private: - // ==================== 常量定义 ==================== - static const double MIN_FITNESS_IMPROVEMENT; - static const double MUTATION_STRENGTH; - static const int CONVERGENCE_CHECK_INTERVAL; - static const int MAX_STAGNATION_GENERATIONS; - - // ==================== 核心状态变量 ==================== - bool m_isRunning; // 是否正在运行 - bool m_shouldStop; // 是否应该停止 - bool m_isPaused; // 是否暂停 - int m_currentGeneration; // 当前代数 - - // 适应度统计 - double m_bestFitness; // 最优适应度 - double m_worstFitness; // 最差适应度 - double m_averageFitness; // 平均适应度 - double m_previousBestFitness; // 上一代最优适应度 - - // ==================== GA算法参数 ==================== - int m_populationSize; // 种群大小 - int m_maxGenerations; // 最大代数 - double m_targetError; // 目标误差 - double m_crossoverRate; // 交叉概率 - double m_mutationRate; // 变异概率 - double m_elitismRate; // 精英保留比例 - int m_tournamentSize; // 锦标赛选择大小 - bool m_useUniformCrossover; // 是否使用均匀交叉 - - // ==================== 种群和个体 ==================== - QVector m_population; // 当前种群 - QVector m_eliteIndividuals; // 精英个体 - GAIndividual m_bestIndividual; // 全局最优个体 - - // ==================== 评估统计 ==================== - int m_totalEvaluations; // 总评估次数 - int m_successfulEvaluations; // 成功评估次数 - int m_evaluationInProgress; // 正在进行的评估计数 - int m_consecutiveFailures; // 连续失败次数 - - // ==================== 精英保护相关 ==================== - QVector m_initialValues; // 用户初始参数值 - QVector m_userInitialSolution; // 用户初始解 - double m_userInitialFitness; // 用户初始适应度 - int m_consecutiveFailedGenerations; // 连续失败代数 - int m_maxConsecutiveFailures; // 最大允许连续失败数 - bool m_hasValidUserSolution; // 是否有有效的用户解 - double m_improvementThreshold; // 改进阈值 - - // ==================== 收敛判断相关 ==================== - double m_diversityThreshold; // 多样性阈值 - double m_convergenceVarianceThreshold; // 收敛方差阈值 - int m_trueConvergenceWindow; // 真收敛判断窗口 - int m_localOptimumWindow; // 局部最优判断窗口 - double m_nearTargetFactor; // 接近目标的因子 - double m_farTargetFactor; // 远离目标的因子 - - // ==================== 历史记录 ==================== - QVector m_convergenceHistory; // 收敛历史 - QVector m_diversityHistory; // 多样性历史 - - // ==================== 参数配置 ==================== - QVector m_parameterSelected; // 参数选择状态 - QVector m_parameterLower; // 参数下界 - QVector m_parameterUpper; // 参数上界 - QVector m_enabledParamIndices; // 启用参数索引 - - // ==================== 目标数据 ==================== - QVector> m_targetLogLogData; // 目标双对数数据 - - // ==================== 其他 ==================== - QString m_lastError; // 最后错误信息 - QTimer* m_progressTimer; // 进度更新定时器 - // DLL求解器需要的临时目录 - QString m_tempDirectory; - - QString m_targetWellName;// 目标井名称 -}; - -#endif // NMCALCULATIONAUTOFITGA_H \ No newline at end of file diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index a245aac..30a9119 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -21,17 +21,10 @@ #include "nmDataWellBase.h" #include "nmDataAutomaticFitting.h" #include "nmCalculationAutoFitPSO.h" -#include "nmCalculationAutoFitGA.h" #include "nmWxAutomaticfittingStart.h" #include "nmSubWxs_global.h" -// 算法类型枚举 -enum OptimizationAlgorithm { - ALGORITHM_PSO = 0, - ALGORITHM_GA = 1 -}; - class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase { Q_OBJECT @@ -46,7 +39,7 @@ public: void onAccept(); void onReject(); void onWellSelected(int index); - void onAlgorithmChanged(int index); + void onParameterTableItemChanged(QTableWidgetItem* item); // 自动拟合相关槽函数 void runAutoFitting(); @@ -63,6 +56,12 @@ private: void setParameterRowVisible(QTableWidget* table, int row, bool visible); void renumberVisibleParameterRows(QTableWidget* table); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); + void initializeSuggestedParameterRanges(); + void updateRangeForParameter(int parameterIndex, double centerValue, bool afterFit); + void setParameterRange(int parameterIndex, double minValue, double maxValue); + bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue); + void normalizeSavedParameterRanges(); + bool validateParameterTable(QString& errorMessage, int parameterIndex = -1); void startAutoFitting(const QVector>& targetData, const QStringList& selectedParams, const QString& targetWellName); void cleanupFitting(); @@ -108,10 +107,10 @@ private: // 自动拟合相关成员 nmCalculationAutoFitPSO* m_autoFitterPSO; - nmCalculationAutoFitGA* m_autoFitterGA; QProgressDialog* m_progressDialog; QTimer* m_progressTimer; - OptimizationAlgorithm m_selectedAlgorithm; // 选中的算法类型 + bool m_autoParameterRanges; + bool m_updatingParameterRanges; // 拟合开始界面 nmWxAutomaticfittingStart* m_progressMonitor; diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h index eb37eeb..3e9067e 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h @@ -22,12 +22,10 @@ #include #include -#include "nmCalculationAutoFitGA.h" #include "nmCalculationAutoFitPSO.h" // 前向声明 class nmCalculationAutoFitPSO; -class nmCalculationAutoFitGA; class QPainter; class QColor; class QPaintEvent; @@ -68,12 +66,6 @@ private: bool m_pseudoPressureMode; }; -// 算法类型枚举 -enum FittingAlgorithmType { - FITTING_ALGORITHM_PSO = 0, - FITTING_ALGORITHM_GA = 1 -}; - class nmWxAutomaticfittingStart : public iDlgBase { Q_OBJECT @@ -85,8 +77,6 @@ public: // PSO算法接口 void setAutoFitter(nmCalculationAutoFitPSO* autoFitter); - // GA算法接口 - void setAutoFitterGA(nmCalculationAutoFitGA* autoFitter); // 通用设置接口 void setFittingParameters(int maxIterations, double targetError, const QString& wellName); @@ -161,8 +151,6 @@ private: // 算法实例 nmCalculationAutoFitPSO* m_autoFitterPSO; - nmCalculationAutoFitGA* m_autoFitterGA; - FittingAlgorithmType m_algorithmType; // 拟合参数 int m_maxIterations; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitGA.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitGA.cpp deleted file mode 100644 index 45eda30..0000000 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitGA.cpp +++ /dev/null @@ -1,2911 +0,0 @@ -#include "nmCalculationAutoFitGA.h" -//#include "nmCalculationExeSolverTask.h" -#include "nmCalculationDllPebiSolverTask.h" -#include "nmDataAnalyzeManager.h" -#include "nmDataWellBase.h" -#include "nmDataVerticalWell.h" -#include "nmDataVerticalFracturedWell.h" -#include "nmDataHorizontalFracturedWell.h" -#include "nmDataReservoir.h" -#include "nmDataAutomaticFitting.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_WIN -#include -#include -#define DEBUG_OUT(msg) OutputDebugStringA(QString("[AutoFit] %1\n").arg(msg).toLocal8Bit().data()) -#endif - -// 常量定义 -const double nmCalculationAutoFitGA::MIN_FITNESS_IMPROVEMENT = 1e-8; -const double nmCalculationAutoFitGA::MUTATION_STRENGTH = 0.1; -const int nmCalculationAutoFitGA::CONVERGENCE_CHECK_INTERVAL = 10; -const int nmCalculationAutoFitGA::MAX_STAGNATION_GENERATIONS = 20; - -// 无穷大和NaN检查 -static inline bool isFiniteNumber(double value) -{ -#ifdef Q_OS_WIN - return _finite(value) != 0 && !_isnan(value); -#else - return std::isfinite(value); -#endif -} - -// sleep函数 -static inline void msleep(int ms) -{ -#ifdef Q_OS_WIN - Sleep(ms); -#endif -} - -// 构造函数 -nmCalculationAutoFitGA::nmCalculationAutoFitGA(QObject* parent) - : QObject(parent) - , m_isRunning(false) - , m_shouldStop(false) - , m_isPaused(false) - , m_currentGeneration(0) - , m_bestFitness(1e10) - , m_worstFitness(-1e10) - , m_averageFitness(1e10) - , m_previousBestFitness(1e10) - , m_populationSize(40) - , m_maxGenerations(100) - , m_targetError(0.001) - , m_crossoverRate(0.8) - , m_mutationRate(0.1) - , m_elitismRate(0.1) - , m_tournamentSize(3) - , m_useUniformCrossover(true) - , m_totalEvaluations(0) - , m_successfulEvaluations(0) - , m_evaluationInProgress(0) - , m_consecutiveFailures(0) - , m_progressTimer(0) - , m_userInitialFitness(1e10) - , m_consecutiveFailedGenerations(0) - , m_maxConsecutiveFailures(3) - , m_hasValidUserSolution(false) - , m_improvementThreshold(0.05) - , m_diversityThreshold(0.05) - , m_convergenceVarianceThreshold(1e-8) - , m_trueConvergenceWindow(15) - , m_localOptimumWindow(8) - , m_nearTargetFactor(2.0) - , m_farTargetFactor(10.0) - , m_targetWellName("") -{ - DEBUG_OUT(QString("GA Constructor: this=0x%1").arg((quintptr)this, 0, 16)); - - // 初始化随机数种子 - qsrand(QTime::currentTime().msec()); - - // 初始化临时目录用于DLL求解器 - initializeTemporaryDirectory(); - - // 初始化最优个体 - m_bestIndividual.fitness = 1e10; - m_bestIndividual.isEvaluated = false; - - // 创建进度更新定时器 - m_progressTimer = new QTimer(this); - connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(updateProgress())); - - DEBUG_OUT("AutoFit GA calculator initialized (data-driven mode)"); - DEBUG_OUT("GA Constructor completed"); -} - -// 析构函数 -nmCalculationAutoFitGA::~nmCalculationAutoFitGA() -{ - DEBUG_OUT(QString("GA Destructor: this=0x%1").arg((quintptr)this, 0, 16)); - - // 首先停止算法 - if(m_isRunning) { - m_shouldStop = true; // 立即设置停止标志 - - // 等待当前操作完成,增加超时时间 - int waitCount = 0; - while(m_isRunning && waitCount < 100) { - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 50); - msleep(50); - waitCount++; - } - - // 如果仍在运行则强制停止 - if(m_isRunning) { - DEBUG_OUT("Force stopping GA - timeout reached"); - m_isRunning = false; - } - } - - // 清理临时目录 - cleanupTemporaryDirectory(); - - // 断开所有信号连接,防止回调已销毁的对象 - disconnect(this, nullptr, nullptr, nullptr); - - DEBUG_OUT("GA Destructor completed"); -} - -// ==================== 目录处理方法 ==================== - -void nmCalculationAutoFitGA::initializeTemporaryDirectory() -{ - QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz"); - QString processId = QString::number(QCoreApplication::applicationPid()); - - m_tempDirectory = QApplication::applicationDirPath() + - "/autofit_temp_" + processId + "_" + timestamp; - - // 确保目录不存在 - int counter = 0; - QString originalPath = m_tempDirectory; - - while(QDir(m_tempDirectory).exists() && counter < 100) { - m_tempDirectory = originalPath + "_" + QString::number(counter); - counter++; - } - - if(QDir().mkpath(m_tempDirectory)) { - DEBUG_OUT(QString("Initialized temp directory: %1").arg(m_tempDirectory)); - } else { - DEBUG_OUT(QString("Warning: Failed to create temp directory: %1").arg(m_tempDirectory)); - m_tempDirectory = QApplication::applicationDirPath(); - } -} - -void nmCalculationAutoFitGA::cleanupTemporaryDirectory() -{ - if(QDir(m_tempDirectory).exists()) { - if(removeDirectoryRecursively(m_tempDirectory)) { - DEBUG_OUT("Temp directory cleaned up successfully"); - } else { - DEBUG_OUT("Warning: Failed to clean up temp directory completely"); - } - } -} - -bool nmCalculationAutoFitGA::removeDirectoryRecursively(const QString& path) -{ - QDir dir(path); - - if(!dir.exists()) { - return true; - } - - // 递归删除子目录和文件 - QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden); - bool allRemoved = true; - - for(int i = 0; i < entries.size(); ++i) { - const QFileInfo& entry = entries[i]; - - if(entry.isDir()) { - if(!removeDirectoryRecursively(entry.absoluteFilePath())) { - allRemoved = false; - } - } else { - QFile file(entry.absoluteFilePath()); - - // 处理只读文件 - if(!file.permissions().testFlag(QFile::WriteUser)) { - file.setPermissions(file.permissions() | QFile::WriteUser); - } - - if(!file.remove()) { - DEBUG_OUT(QString("Failed to remove file: %1").arg(entry.absoluteFilePath())); - allRemoved = false; - } - } - } - - // 删除目录本身 - if(allRemoved) { - return dir.rmdir(path); - } - - return false; -} - -// ==================== 公共接口方法 ==================== - -void nmCalculationAutoFitGA::setTargetLogLogData(const QVector>& targetData) -{ - m_targetLogLogData = targetData; - DEBUG_OUT(QString("Target LogLog data set: %1 arrays").arg(targetData.size())); - - if(targetData.size() >= 3) { - DEBUG_OUT(QString("LogLog data points: X=%1, Y1=%2, Y2=%3") - .arg(targetData[0].size()) - .arg(targetData[1].size()) - .arg(targetData[2].size())); - } -} - -bool nmCalculationAutoFitGA::startAutoFitting() -{ - if(m_isRunning) { - m_lastError = "GA fitting is already running"; - return false; - } - - DEBUG_OUT("=== GA AUTO FITTING START ==="); - - // 发送初始化日志 - emit logMessageGenerated(tr("=== GA Automatic Fitting Started ===")); - emit logMessageGenerated(tr("Algorithm: Genetic Algorithm")); - - try { - // 从数据管理器加载所有配置 - if(!loadAllConfigFromDataManager()) { - emit logMessageGenerated(tr("ERROR: Failed to load configuration from data manager")); - return false; - } - - int enabledParams = getEnabledParameterCount(); - emit logMessageGenerated(tr("Enabled parameters count: %1").arg(enabledParams)); - - if(enabledParams == 0) { - m_lastError = "No parameters enabled for optimization"; - emit logMessageGenerated(tr("ERROR: No parameters enabled for optimization")); - return false; - } - - if(m_targetLogLogData.isEmpty() || m_targetLogLogData.size() < 3) { - m_lastError = "Target LogLog data is empty or insufficient"; - emit logMessageGenerated(tr("ERROR: Target LogLog data is empty or insufficient")); - return false; - } - - // 检查数据一致性 - if(m_targetLogLogData[0].size() != m_targetLogLogData[1].size() || - m_targetLogLogData[0].size() != m_targetLogLogData[2].size()) { - m_lastError = "Target LogLog data arrays have inconsistent sizes"; - emit logMessageGenerated(tr("ERROR: Target LogLog data arrays have inconsistent sizes")); - return false; - } - - emit logMessageGenerated(tr("Target data validation passed (%1 data points)").arg(m_targetLogLogData[0].size())); - - // 使用保存的初始值进行精英保护 - QVector savedInitialValues = m_initialValues; - - // 重置状态 - resetOptimizer(); - m_isRunning = true; - m_shouldStop = false; - m_isPaused = false; - m_currentGeneration = 0; - m_consecutiveFailures = 0; - m_consecutiveFailedGenerations = 0; - - // 精英保护:评估用户初始解 - if(!savedInitialValues.isEmpty()) { - m_userInitialSolution = savedInitialValues; - emit logMessageGenerated(tr("=== Evaluating Initial Solution (Elite Protection) ===")); - - // 输出初始参数值 - QString paramStr = tr("Initial parameters: "); - for(int i = 0; i < m_userInitialSolution.size(); ++i) { - paramStr += QString("[%1]=%2 ").arg(i).arg(m_userInitialSolution[i], 0, 'f', 6); - } - emit logMessageGenerated(paramStr); - - try { - emit logMessageGenerated(tr("Starting initial solution evaluation...")); - DEBUG_OUT(QString("Before evaluateFitness: m_userInitialSolution size = %1").arg(m_userInitialSolution.size())); - - if(m_userInitialSolution.isEmpty()) { - emit logMessageGenerated(tr("ERROR: m_userInitialSolution is empty!")); - return false; - } - - for(int i = 0; i < m_userInitialSolution.size(); ++i) { - emit logMessageGenerated(tr("Initial param[%1] = %2").arg(i).arg(m_userInitialSolution[i], 0, 'f', 6)); - } - - m_userInitialFitness = evaluateGenes(m_userInitialSolution); - - emit logMessageGenerated(tr("evaluateGenes returned: %1").arg(m_userInitialFitness, 0, 'e', 10)); - - if(m_userInitialFitness < 1e9) { - emit logMessageGenerated(tr("Taking SUCCESS branch (fitness < 1e9)")); - m_hasValidUserSolution = true; - m_bestFitness = m_userInitialFitness; - m_bestIndividual.genes = m_userInitialSolution; - m_bestIndividual.fitness = m_userInitialFitness; - m_bestIndividual.isEvaluated = true; - - emit logMessageGenerated(tr("Initial solution evaluation successful")); - emit logMessageGenerated(tr("Initial fitness (error): %1").arg(m_userInitialFitness, 0, 'e', 4)); - emit logMessageGenerated(tr("Elite protection activated - initial solution will be preserved if no significant improvement found")); - } else { - emit logMessageGenerated(tr("Taking FAILURE branch (fitness >= 1e9)")); - m_hasValidUserSolution = false; - emit logMessageGenerated(tr("Initial solution evaluation failed - starting with random initialization")); - } - } catch(...) { - m_hasValidUserSolution = false; - emit logMessageGenerated(tr("Exception during initial solution evaluation")); - } - - // 恢复初始值供种群初始化使用 - m_initialValues = savedInitialValues; - } - - // 初始化种群 - initializePopulation(); - emit logMessageGenerated(tr("Population initialized: %1 individuals, %2 dimensions").arg(m_populationSize).arg(getEnabledParameterCount())); - - // GA主循环 - emit logMessageGenerated(tr("=== Starting GA Main Loop ===")); - - for(m_currentGeneration = 0; m_currentGeneration < m_maxGenerations && !m_shouldStop; ++m_currentGeneration) { - - // 每次迭代都输出标题,或者只在重要迭代输出详细信息 - bool shouldOutputDetail = (m_currentGeneration % qMax(1, m_maxGenerations / 10) == 0) || - (m_currentGeneration < 5) || - (m_currentGeneration >= m_maxGenerations - 2); - - // 每次迭代都输出标题 - emit logMessageGenerated(tr("--- Generation %1/%2 ---").arg(m_currentGeneration + 1).arg(m_maxGenerations)); - - // 只在特定迭代输出详细统计信息 - if(shouldOutputDetail) { - emit logMessageGenerated(tr("Current best error: %1").arg(m_bestFitness, 0, 'e', 4)); - emit logMessageGenerated(tr("Total evaluations: %1 (successful: %2, failures: %3)") - .arg(m_totalEvaluations).arg(m_successfulEvaluations).arg(m_totalEvaluations - m_successfulEvaluations)); - } - - // 检查暂停状态 - while(m_isPaused && !m_shouldStop) { - QApplication::processEvents(); - //msleep(100); - } - - if(m_shouldStop) { - emit logMessageGenerated(tr("Optimization stopped by user request")); - break; - } - - // 1. 评估种群 - evaluatePopulation(); - - if(m_shouldStop) break; - - // 2. 更新统计信息 - updatePopulationStatistics(); - - // 3. 发射进度信号 - emit progressUpdated(m_currentGeneration, m_bestFitness); - - if(shouldOutputDetail) { - emit logMessageGenerated(tr("Generation %1 completed: best = %2, avg = %3, worst = %4") - .arg(m_currentGeneration + 1).arg(m_bestFitness, 0, 'e', 4) - .arg(m_averageFitness, 0, 'e', 4).arg(m_worstFitness, 0, 'e', 4)); - } - - // 记录收敛历史 - m_convergenceHistory.append(m_bestFitness); - - // 更新收敛指标 - updateConvergenceMetrics(); - - // 智能收敛判断 - StopReasonGA stopReason = analyzeOptimizationStatus(); - - if(stopReason == GA_TARGET_ACHIEVED) { - emit logMessageGenerated(tr("=== TARGET ACHIEVED ===")); - emit logMessageGenerated(tr("Target error achieved! Current error: %1 < Target: %2") - .arg(m_bestFitness, 0, 'e', 4).arg(m_targetError, 0, 'e', 4)); - emit logMessageGenerated(tr("Optimization completed successfully after %1 generations") - .arg(m_currentGeneration + 1)); - break; - } - else if(stopReason == GA_TRUE_CONVERGENCE) { - emit logMessageGenerated(tr("=== TRUE CONVERGENCE DETECTED ===")); - emit logMessageGenerated(tr("Algorithm has converged to a stable solution")); - emit logMessageGenerated(tr("Final error: %1 after %2 generations") - .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1)); - emit logMessageGenerated(tr("Solution quality: %1 (1.0 = target achieved)") - .arg(m_targetError / qMax(1e-10, m_bestFitness), 0, 'f', 3)); - break; - } - else if(stopReason == GA_LOCAL_OPTIMUM) { - emit logMessageGenerated(tr("=== LOCAL OPTIMUM DETECTED ===")); - emit logMessageGenerated(tr("Algorithm appears to be trapped in local optimum")); - emit logMessageGenerated(tr("Current error: %1 after %2 generations") - .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1)); - emit logMessageGenerated(tr("Suggestion: Try restarting with different parameters or larger search space")); - break; - } - else if(stopReason == GA_CONSECUTIVE_FAILURES) { - emit logMessageGenerated(tr("=== CONSECUTIVE FAILURES ===")); - emit logMessageGenerated(tr("Too many consecutive failed generations (%1/%2)") - .arg(m_consecutiveFailedGenerations).arg(m_maxConsecutiveFailures)); - break; - } - else if(stopReason == GA_CONTINUE_OPTIMIZATION) { - // 继续优化,每10次迭代输出一次状态 - if(m_currentGeneration > 0 && m_currentGeneration % 10 == 0) { - double diversity = calculatePopulationDiversity(); - - emit logMessageGenerated(tr(" Optimization status: diversity=%1") - .arg(diversity, 0, 'f', 4)); - } - } - - // 4. 创建新一代种群 - if(m_currentGeneration < m_maxGenerations - 1) - { - QVector newPopulation; - newPopulation.reserve(m_populationSize); - - // 精英保留 - applyElitism(newPopulation); - - // 生成新个体直到填满种群 - while(newPopulation.size() < m_populationSize && !m_shouldStop) { - // 选择父代 - int parent1Index = tournamentSelection(); - int parent2Index = tournamentSelection(); - - // 确保父代不同 - while(parent1Index == parent2Index && m_population.size() > 1) { - parent2Index = tournamentSelection(); - } - - GAIndividual offspring1, offspring2; - - // 交叉 - if(random01() < m_crossoverRate) { - crossover(m_population[parent1Index], m_population[parent2Index], - offspring1, offspring2); - } else { - offspring1 = m_population[parent1Index]; - offspring2 = m_population[parent2Index]; - } - - // 变异 - if(random01() < m_mutationRate) { - mutate(offspring1); - } - if(random01() < m_mutationRate) { - mutate(offspring2); - } - - // 边界约束 - clampToLimits(offspring1.genes); - clampToLimits(offspring2.genes); - - // 添加到新种群 - if(newPopulation.size() < m_populationSize) { - newPopulation.append(offspring1); - } - if(newPopulation.size() < m_populationSize) { - newPopulation.append(offspring2); - } - } - - // 替换种群 - m_population = newPopulation; - - // 自适应参数调整 - adaptiveParameterUpdate(m_currentGeneration); - } - - // 输出迭代结束标记 - if(shouldOutputDetail) { - emit logMessageGenerated(tr("Generation %1 completed - Current best: %2") - .arg(m_currentGeneration + 1).arg(m_bestFitness, 0, 'e', 4)); - } - - // 强制处理事件,保持界面响应 - QApplication::processEvents(); - - // 在世代间稍作停顿,减少系统负载 - if(m_currentGeneration % 3 == 2) { - //msleep(200); - } - } - - // 最终结果验证和保护 - validateAndProtectFinalResult(); - - } catch(const std::exception& e) { - m_lastError = QString("Critical exception in GA main loop: %1").arg(e.what()); - emit logMessageGenerated(tr("CRITICAL ERROR: %1").arg(e.what())); - cleanupTemporaryDirectory(); - m_isRunning = false; - if(m_progressTimer) m_progressTimer->stop(); - emit fittingFinished(false, m_lastError); - return false; - } catch(...) { - m_lastError = "Unknown critical exception in GA main loop"; - emit logMessageGenerated(tr("CRITICAL ERROR: Unknown exception in GA main loop")); - cleanupTemporaryDirectory(); - m_isRunning = false; - if(m_progressTimer) m_progressTimer->stop(); - emit fittingFinished(false, m_lastError); - return false; - } - - m_isRunning = false; - if(m_progressTimer) m_progressTimer->stop(); - - // 应用最终参数 - if(!m_bestIndividual.genes.isEmpty()) { - try { - emit logMessageGenerated(tr("Applying optimized parameters to model...")); - applyParametersToDataManager(m_bestIndividual.genes); - saveOptimizationResult(); - - // 输出最终优化结果 - emit logMessageGenerated(tr("=== Optimization Results ===")); - emit logMessageGenerated(tr("Final error: %1").arg(m_bestFitness, 0, 'e', 4)); - emit logMessageGenerated(tr("Total generations: %1").arg(m_currentGeneration + 1)); - emit logMessageGenerated(tr("Total evaluations: %1 (successful: %2)") - .arg(m_totalEvaluations).arg(m_successfulEvaluations)); - - // 输出最优参数值 - QString finalParams = tr("Optimized parameters: "); - for(int i = 0; i < m_bestIndividual.genes.size(); ++i) { - finalParams += QString("[%1]=%2 ").arg(i).arg(m_bestIndividual.genes[i], 0, 'f', 6); - } - emit logMessageGenerated(finalParams); - - emit logMessageGenerated(tr("Parameters applied successfully to data manager")); - } catch(const std::exception& e) { - emit logMessageGenerated(tr("ERROR: Failed to apply final parameters: %1").arg(e.what())); - m_lastError = QString("Failed to apply final parameters: %1").arg(e.what()); - } catch(...) { - emit logMessageGenerated(tr("ERROR: Unknown error applying final parameters")); - m_lastError = "Failed to apply final parameters due to unknown error"; - } - } - - // 判断系统确定最终结果 - bool success; - QString message; - StopReasonGA finalReason = analyzeOptimizationStatus(); - - if(finalReason == GA_TARGET_ACHIEVED) { - success = true; - //message = QString("GA optimization completed successfully. Target achieved. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION SUCCESSFUL ===")); - } - else if(finalReason == GA_TRUE_CONVERGENCE) { - success = true; - //message = QString("GA optimization converged to stable solution. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION CONVERGED ===")); - } - else if(finalReason == GA_LOCAL_OPTIMUM) { - success = true; - //message = QString("GA optimization trapped in local optimum. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION - LOCAL OPTIMUM ===")); - } - else if(finalReason == GA_MAX_ITERATIONS) { - success = true; - //message = QString("GA optimization completed. Max generations reached. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION - MAX GENERATIONS ===")); - } - else if(finalReason == GA_USER_STOPPED) { - success = true; - //message = QString("GA optimization stopped by user. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION STOPPED BY USER ===")); - } - else if(finalReason == GA_CONSECUTIVE_FAILURES) { - success = false; - //message = QString("GA optimization failed due to consecutive failures. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION FAILED ===")); - } - else { - success = false; - //message = QString("GA optimization ended unexpectedly. Best error: %1, Generations: %2") - // .arg(m_bestFitness, 0, 'e', 4).arg(m_currentGeneration + 1); - emit logMessageGenerated(tr("=== GA OPTIMIZATION - UNKNOWN END ===")); - } - - emit logMessageGenerated(tr("Result: %1").arg(success ? "SUCCESS" : "FAILED")); - - emit fittingFinished(success, message); - cleanupTemporaryDirectory(); - return success; -} - -void nmCalculationAutoFitGA::stopFitting() -{ - if(m_isRunning) { - DEBUG_OUT("Stop request received, setting stop flag..."); - - // 添加停止日志 - emit logMessageGenerated(tr("=== User Stop Request Received ===")); - emit logMessageGenerated(tr("Gracefully stopping GA optimization...")); - - m_shouldStop = true; - - if(m_progressTimer) { - m_progressTimer->stop(); - } - - // 等待当前评估完成,缩短超时时间 - int waitCount = 0; - while(m_evaluationInProgress > 0 && waitCount < 30) { // 减少等待时间 - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 50); - msleep(50); - waitCount++; - } - - // 超时时强制重置 - if(m_evaluationInProgress > 0) { - DEBUG_OUT("Force resetting evaluation counter"); - emit logMessageGenerated(tr("Force stopping current evaluation...")); - m_evaluationInProgress = 0; - } - - // 确保运行标志被清除 - m_isRunning = false; - - emit logMessageGenerated(tr("GA optimization stop request processed")); - DEBUG_OUT("Stop request processed"); - } else { - DEBUG_OUT("Stop request received but GA is not running"); - emit logMessageGenerated(tr("Stop request received but optimization is not running")); - } - cleanupTemporaryDirectory(); -} - -bool nmCalculationAutoFitGA::isRunning() const -{ - return m_isRunning; -} - -int nmCalculationAutoFitGA::getCurrentGeneration() const -{ - return m_currentGeneration; -} - -QVector nmCalculationAutoFitGA::getBestSolution() const -{ - return m_bestIndividual.genes; -} - -double nmCalculationAutoFitGA::getBestFitness() const -{ - return m_bestFitness; -} - -QString nmCalculationAutoFitGA::getLastError() const -{ - return m_lastError; -} - -void nmCalculationAutoFitGA::resetOptimizer() -{ - m_population.clear(); - m_eliteIndividuals.clear(); - m_bestIndividual = GAIndividual(); - m_bestFitness = 1e10; - m_worstFitness = -1e10; - m_averageFitness = 1e10; - m_previousBestFitness = 1e10; - m_currentGeneration = 0; - m_totalEvaluations = 0; - m_successfulEvaluations = 0; - m_convergenceHistory.clear(); - m_lastError.clear(); - m_initialValues.clear(); - m_userInitialSolution.clear(); - m_userInitialFitness = 1e10; - m_hasValidUserSolution = false; - m_diversityHistory.clear(); - - DEBUG_OUT("GA optimizer reset"); -} - -void nmCalculationAutoFitGA::setGATargetWellName(const QString& wellName) -{ - m_targetWellName = wellName; -} - -void nmCalculationAutoFitGA::updateProgress() -{ - // 这个槽函数在定时器触发时被调用,可以用来更新界面或执行周期性任务 - if(m_isRunning) { - QApplication::processEvents(); - } -} - -// ==================== 数据加载方法 ==================== -bool nmCalculationAutoFitGA::loadAllConfigFromDataManager() -{ - try { - loadOptimizationConfig(); - loadParameterBounds(); - extractUserInitialValues(); - return true; - } catch(...) { - m_lastError = "Failed to load configuration from data manager"; - return false; - } -} - -void nmCalculationAutoFitGA::loadOptimizationConfig() -{ - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); - - // 加载基本配置 - m_maxGenerations = fittingData.getIterationCount().getValue().toInt(); - m_targetError = fittingData.getErrorTolerance().getValue().toDouble(); - - // 设置GA默认参数 - m_populationSize = 40; - m_crossoverRate = 0.8; - m_mutationRate = 0.1; - m_elitismRate = 0.15; - m_tournamentSize = 3; - m_useUniformCrossover = true; - - DEBUG_OUT(QString("Loaded GA optimization config: generations=%1, error=%2, population=%3") - .arg(m_maxGenerations).arg(m_targetError).arg(m_populationSize)); -} - -void nmCalculationAutoFitGA::loadParameterBounds() -{ - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); - - // 获取参数选择状态 - m_parameterSelected.resize(8); - m_parameterSelected[0] = fittingData.getPermeabilitySelected(); - m_parameterSelected[1] = fittingData.getSkinSelected(); - m_parameterSelected[2] = fittingData.getWellboreStorageSelected(); - m_parameterSelected[3] = fittingData.getPorositySelected(); - m_parameterSelected[4] = fittingData.getThicknessSelected(); - m_parameterSelected[5] = fittingData.getCtSelected(); - m_parameterSelected[6] = fittingData.getCfSelected(); - m_parameterSelected[7] = fittingData.getSwiSelected(); - - // 获取参数边界 - m_parameterLower.resize(8); - m_parameterUpper.resize(8); - - m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble(); - m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble(); - - m_parameterLower[1] = fittingData.getSkinMin().getValue().toDouble(); - m_parameterUpper[1] = fittingData.getSkinMax().getValue().toDouble(); - - m_parameterLower[2] = fittingData.getWellboreStorageMin().getValue().toDouble(); - m_parameterUpper[2] = fittingData.getWellboreStorageMax().getValue().toDouble(); - - m_parameterLower[3] = fittingData.getPorosityMin().getValue().toDouble(); - m_parameterUpper[3] = fittingData.getPorosityMax().getValue().toDouble(); - - m_parameterLower[4] = fittingData.getThicknessMin().getValue().toDouble(); - m_parameterUpper[4] = fittingData.getThicknessMax().getValue().toDouble(); - - m_parameterLower[5] = fittingData.getCtMin().getValue().toDouble(); - m_parameterUpper[5] = fittingData.getCtMax().getValue().toDouble(); - - m_parameterLower[6] = fittingData.getCfMin().getValue().toDouble(); - m_parameterUpper[6] = fittingData.getCfMax().getValue().toDouble(); - - m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble(); - m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble(); - - // 更新启用参数索引 - m_enabledParamIndices.clear(); - for(int i = 0; i < m_parameterSelected.size(); ++i) { - if(m_parameterSelected[i]) { - m_enabledParamIndices.append(i); - } - } - DEBUG_OUT(QString("Loaded parameter bounds: %1 enabled parameters") - .arg(m_enabledParamIndices.size())); -} - -void nmCalculationAutoFitGA::extractUserInitialValues() -{ - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - //QVector wells = dataManager->getWellDataList(); - nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); - - m_initialValues.clear(); - - // 按照启用参数的顺序提取初始值 - for(int i = 0; i < m_enabledParamIndices.size(); ++i) { - int paramIndex = m_enabledParamIndices[i]; - double initialValue = 0.0; - - switch(paramIndex) { - case 0: // 渗透率 - initialValue = reservoirData.getPermeability().getValue().toDouble(); - break; - - case 1: // 表皮系数 - if(pTargetWell) { - initialValue = pTargetWell->getPerforation(0)->getSkin().getValue().toDouble(); - } - - break; - - case 2: // 井筒储集系数 - if(pTargetWell) { - initialValue = pTargetWell->getWellboreStorage().getValue().toDouble(); - } - - break; - - case 3: // 孔隙度 - initialValue = reservoirData.getPorosity().getValue().toDouble(); - break; - - case 4: // 储层厚度 - initialValue = reservoirData.getThickness().getValue().toDouble(); - break; - - case 5: // 综合压缩系数 - initialValue = reservoirData.getCt().getValue().toDouble(); - break; - - case 6: // 岩石压缩系数 - initialValue = reservoirData.getCf().getValue().toDouble(); - break; - - case 7: // 初始含水饱和度 - initialValue = reservoirData.getSwi().getValue().toDouble(); - break; - } - - m_initialValues.append(initialValue); - } - - DEBUG_OUT(QString("Extracted %1 user initial values").arg(m_initialValues.size())); - - for(int i = 0; i < m_initialValues.size(); ++i) { - 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"); - } -} - -// ==================== 遗传算法核心方法 ==================== - -void nmCalculationAutoFitGA::initializePopulation() -{ - int dimensions = getEnabledParameterCount(); - if(dimensions == 0) return; - - m_population.clear(); - m_population.resize(m_populationSize); - - bool hasValidInitials = !m_initialValues.isEmpty() && m_initialValues.size() >= dimensions; - int guidedCount = hasValidInitials ? qMax(2, m_populationSize / 2) : qMax(1, m_populationSize / 3); - - DEBUG_OUT(QString("Enhanced population initialization: %1 individuals, %2 guided, %3 random") - .arg(m_populationSize).arg(guidedCount).arg(m_populationSize - guidedCount)); - - for(int i = 0; i < m_populationSize; ++i) { - GAIndividual& individual = m_population[i]; - individual.genes.resize(dimensions); - individual.fitness = 1e10; - individual.isEvaluated = false; - - // 基因初始化 - for(int j = 0; j < dimensions; ++j) { - int paramIndex = m_enabledParamIndices[j]; - double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex]; - - if(i == 0 && hasValidInitials) { - // 第一个个体:使用用户初始值 - individual.genes[j] = m_initialValues[j]; - } else if(i < guidedCount && hasValidInitials) { - // 引导搜索策略 - double searchRadius; - if(i <= guidedCount / 3) { - searchRadius = range * 0.03; - } else if(i <= guidedCount * 2 / 3) { - searchRadius = range * 0.08; - } else { - searchRadius = range * 0.15; - } - double offset = (random01() - 0.5) * searchRadius; - individual.genes[j] = m_initialValues[j] + offset; - } else { - // 随机初始化 - individual.genes[j] = m_parameterLower[paramIndex] + random01() * range; - } - } - - // 边界约束 - clampToLimits(individual.genes); - } -} - -double nmCalculationAutoFitGA::evaluateGenes(const QVector& genes) -{ - const QString funcName = QString("evaluateGenes[Gen%1]").arg(m_currentGeneration); - static int callCount = 0; - callCount++; - - try { - DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 genes") - .arg(funcName).arg(callCount).arg(genes.size())); - - // 打印参数值用于对比 - QString paramStr = "Parameters: "; - - for(int i = 0; i < genes.size(); ++i) { - paramStr += QString("[%1]=%2 ").arg(i).arg(genes[i], 0, 'f', 6); - } - - DEBUG_OUT(QString("%1: %2").arg(funcName).arg(paramStr)); - - // 1. 参数有效性检查 - if(!validateParameters(genes)) { - DEBUG_OUT(QString("%1: Call #%2 - Invalid parameters").arg(funcName).arg(callCount)); - return 1e10; - } - - // 2. 检查数据管理器状态 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - - if(!dataManager) { - DEBUG_OUT(QString("%1: Call #%2 - DataManager is null").arg(funcName).arg(callCount)); - return 1e10; - } - - // 3. 应用参数到数据管理器 - try { - DEBUG_OUT(QString("%1: Call #%2 - Applying parameters to DataManager").arg(funcName).arg(callCount)); - applyParametersToDataManager(genes); - DEBUG_OUT(QString("%1: Call #%2 - Parameters applied successfully").arg(funcName).arg(callCount)); - } catch(const std::exception& e) { - DEBUG_OUT(QString("%1: Call #%2 - Failed to apply parameters: %3").arg(funcName).arg(callCount).arg(e.what())); - return 1e10; - } catch(...) { - DEBUG_OUT(QString("%1: Call #%2 - Unknown error applying parameters").arg(funcName).arg(callCount)); - return 1e10; - } - - // 4. 运行求解器 - QVector > solverResult; - const int maxRetries = 2; - bool solverSuccess = false; - - for(int retry = 0; retry <= maxRetries; ++retry) { - if(m_shouldStop) return 1e10; - - try { - DEBUG_OUT(QString("%1: Call #%2 - Solver attempt %3/%4") - .arg(funcName).arg(callCount).arg(retry + 1).arg(maxRetries + 1)); - - // 在求解器调用前添加短暂延迟,确保状态稳定 - if(retry > 0) { - DEBUG_OUT(QString("%1: Call #%2 - Retry delay before solver attempt") - .arg(funcName).arg(callCount)); - msleep(1000); // 增加延迟时间 - } - - solverResult = runSolver(); - - if(!solverResult.isEmpty() && validateSolverResult(solverResult)) { - DEBUG_OUT(QString("%1: Call #%2 - Solver successful on attempt %3, result size: %4") - .arg(funcName).arg(callCount).arg(retry + 1).arg(solverResult[0].size())); - solverSuccess = true; - break; - } else { - DEBUG_OUT(QString("%1: Call #%2 - Solver failed on attempt %3 - empty or invalid result") - .arg(funcName).arg(callCount).arg(retry + 1)); - - if(retry < maxRetries) { - DEBUG_OUT(QString("%1: Call #%2 - Will retry solver").arg(funcName).arg(callCount)); - } - } - - } catch(const std::exception& e) { - DEBUG_OUT(QString("%1: Call #%2 - Solver exception on attempt %3: %4") - .arg(funcName).arg(callCount).arg(retry + 1).arg(e.what())); - } catch(...) { - DEBUG_OUT(QString("%1: Call #%2 - Unknown solver exception on attempt %3") - .arg(funcName).arg(callCount).arg(retry + 1)); - } - } - - if(!solverSuccess) { - DEBUG_OUT(QString("%1: Call #%2 - All solver attempts failed").arg(funcName).arg(callCount)); - return 1e10; - } - - // 5. 获取双对数结果数据 - QVector > resultLogLogData; - - try { - - nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); - - if(pTargetWell) { - resultLogLogData = pTargetWell->getResultLogLog(); - - if(!validateLogLogData(resultLogLogData)) { - DEBUG_OUT(QString("%1: Call #%2 - Invalid result LogLog data").arg(funcName).arg(callCount)); - return 1e10; - } - - DEBUG_OUT(QString("%1: Call #%2 - LogLog result data obtained, size: %3") - .arg(funcName).arg(callCount).arg(resultLogLogData[0].size())); - } else { - DEBUG_OUT(QString("%1: Call #%2 - No wells found after solver").arg(funcName).arg(callCount)); - return 1e10; - } - } catch(const std::exception& e) { - DEBUG_OUT(QString("%1: Call #%2 - Error getting LogLog result: %3").arg(funcName).arg(callCount).arg(e.what())); - return 1e10; - } catch(...) { - DEBUG_OUT(QString("%1: Call #%2 - Unknown error getting LogLog result").arg(funcName).arg(callCount)); - return 1e10; - } - - // 6. 双对数曲线对齐和误差计算 - double error; - - try { - error = calculateLogLogCurveError(m_targetLogLogData, resultLogLogData); - - if(!isFiniteNumber(error) || error < 0) { - DEBUG_OUT(QString("%1: Call #%2 - Invalid error value: %3").arg(funcName).arg(callCount).arg(error)); - return 1e10; - } - - DEBUG_OUT(QString("%1: Call #%2 - Evaluation successful, error = %3") - .arg(funcName).arg(callCount).arg(error, 0, 'e', 6)); - - } catch(const std::exception& e) { - DEBUG_OUT(QString("%1: Call #%2 - LogLog error calculation failed: %3").arg(funcName).arg(callCount).arg(e.what())); - return 1e10; - } catch(...) { - DEBUG_OUT(QString("%1: Call #%2 - Unknown error in LogLog error calculation").arg(funcName).arg(callCount)); - return 1e10; - } - - return error; - - } catch(const std::exception& e) { - DEBUG_OUT(QString("%1: Call #%2 - Top-level exception: %3").arg(funcName).arg(callCount).arg(e.what())); - return 1e10; - } catch(...) { - DEBUG_OUT(QString("%1: Call #%2 - Unknown top-level exception").arg(funcName).arg(callCount)); - return 1e10; - } -} - -double nmCalculationAutoFitGA::evaluateIndividual(GAIndividual& individual) -{ - // 如果已经评估过,直接返回 - if(individual.isEvaluated) { - return individual.fitness; - } - - // 调用新的评估函数 - individual.fitness = evaluateGenes(individual.genes); - individual.isEvaluated = true; - - return individual.fitness; -} - -void nmCalculationAutoFitGA::evaluatePopulation() -{ - int successfulEvaluations = 0; - int totalEvaluations = 0; - int currentGenerationFailed = 0; - - for(int i = 0; i < m_population.size() && !m_shouldStop; ++i) { - GAIndividual& individual = m_population[i]; - - if(!individual.isEvaluated) { - try { - double previousFitness = individual.fitness; - individual.fitness = evaluateGenes(individual.genes); - individual.isEvaluated = true; - totalEvaluations++; - m_totalEvaluations++; - - if(individual.fitness < 1e9) { - successfulEvaluations++; - m_successfulEvaluations++; - - // 更新全局最优 - if(individual.fitness < m_bestFitness) { - m_bestFitness = individual.fitness; - m_bestIndividual = individual; - - emit logMessageGenerated(tr(" Individual %1 improved: %2 -> %3") - .arg(i + 1).arg(previousFitness, 0, 'e', 3).arg(individual.fitness, 0, 'e', 3)); - } - } else { - currentGenerationFailed++; - emit logMessageGenerated(tr(" Individual %1: evaluation failed").arg(i + 1)); - } - - // 每评估2个个体处理一次事件 - if(i % 2 == 0) { - QApplication::processEvents(); - } - - } catch(const std::exception& e) { - emit logMessageGenerated(tr(" Individual %1: Exception: %2").arg(i + 1).arg(e.what())); - individual.fitness = 1e10; - individual.isEvaluated = true; - totalEvaluations++; - currentGenerationFailed++; - m_totalEvaluations++; - } catch(...) { - emit logMessageGenerated(tr(" Individual %1: Unknown exception").arg(i + 1)); - individual.fitness = 1e10; - individual.isEvaluated = true; - totalEvaluations++; - currentGenerationFailed++; - m_totalEvaluations++; - } - } - } - - if(m_shouldStop) return; - - double currentSuccessRate = totalEvaluations > 0 ? - (double)successfulEvaluations / totalEvaluations : 0.0; - - // 更新连续失败代数计数 - if(successfulEvaluations == 0) { - m_consecutiveFailedGenerations++; - emit logMessageGenerated(tr("WARNING: No successful evaluations in generation %1 (consecutive failures: %2)") - .arg(m_currentGeneration + 1).arg(m_consecutiveFailedGenerations)); - } else { - m_consecutiveFailedGenerations = 0; // 重置连续失败计数 - } - - // 输出当前代统计 - emit logMessageGenerated(tr("Current generation stats: %1 successful, %2 failed out of %3 individuals (success rate: %4%)") - .arg(successfulEvaluations).arg(currentGenerationFailed) - .arg(totalEvaluations).arg(currentSuccessRate * 100, 0, 'f', 1)); - - // 检查是否需要停止优化 - if(m_consecutiveFailedGenerations >= m_maxConsecutiveFailures) { - m_lastError = QString("Too many consecutive failed generations (%1)").arg(m_consecutiveFailedGenerations); - emit logMessageGenerated(tr("ERROR: Too many consecutive failed generations (%1/%2) - stopping optimization") - .arg(m_consecutiveFailedGenerations).arg(m_maxConsecutiveFailures)); - return; - } - - // 警告低成功率但不立即停止 - if(currentSuccessRate < 0.5 && m_currentGeneration > 3) { - emit logMessageGenerated(tr("WARNING: Low success rate (%1%) in generation %2, but continuing optimization") - .arg(currentSuccessRate * 100, 0, 'f', 1).arg(m_currentGeneration + 1)); - } -} - -int nmCalculationAutoFitGA::tournamentSelection() -{ - int bestIndex = qrand() % m_population.size(); - double bestFitness = m_population[bestIndex].fitness; - - for(int i = 1; i < m_tournamentSize; ++i) { - int candidateIndex = qrand() % m_population.size(); - double candidateFitness = m_population[candidateIndex].fitness; - - if(candidateFitness < bestFitness) { - bestIndex = candidateIndex; - bestFitness = candidateFitness; - } - } - - return bestIndex; -} - -int nmCalculationAutoFitGA::rouletteWheelSelection() -{ - // 计算适应度总和(使用倒数,因为我们要最小化) - double totalFitness = 0.0; - double maxFitness = -1e10; - - // 找到最大适应度值 - for(int i = 0; i < m_population.size(); ++i) { - if(m_population[i].fitness > maxFitness) { - maxFitness = m_population[i].fitness; - } - } - - // 计算转换后的适应度总和 - for(int i = 0; i < m_population.size(); ++i) { - double transformedFitness = maxFitness - m_population[i].fitness + 1e-6; - totalFitness += transformedFitness; - } - - // 轮盘赌选择 - double randomValue = random01() * totalFitness; - double cumulativeFitness = 0.0; - - for(int i = 0; i < m_population.size(); ++i) { - double transformedFitness = maxFitness - m_population[i].fitness + 1e-6; - cumulativeFitness += transformedFitness; - - if(cumulativeFitness >= randomValue) { - return i; - } - } - - return m_population.size() - 1; // 备用选择 -} - -void nmCalculationAutoFitGA::crossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2) -{ - if(m_useUniformCrossover) { - uniformCrossover(parent1, parent2, offspring1, offspring2); - } else { - singlePointCrossover(parent1, parent2, offspring1, offspring2); - } -} - -void nmCalculationAutoFitGA::singlePointCrossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2) -{ - int dimensions = parent1.genes.size(); - - if(dimensions == 0) return; - - // 初始化子代 - offspring1.genes.resize(dimensions); - offspring2.genes.resize(dimensions); - offspring1.fitness = 1e10; - offspring2.fitness = 1e10; - offspring1.isEvaluated = false; - offspring2.isEvaluated = false; - - // 选择交叉点 - int crossoverPoint = qrand() % dimensions; - - // 执行交叉 - for(int i = 0; i < dimensions; ++i) { - if(i < crossoverPoint) { - offspring1.genes[i] = parent1.genes[i]; - offspring2.genes[i] = parent2.genes[i]; - } else { - offspring1.genes[i] = parent2.genes[i]; - offspring2.genes[i] = parent1.genes[i]; - } - } -} - -void nmCalculationAutoFitGA::uniformCrossover(const GAIndividual& parent1, const GAIndividual& parent2, - GAIndividual& offspring1, GAIndividual& offspring2) -{ - int dimensions = parent1.genes.size(); - - if(dimensions == 0) return; - - // 初始化子代 - offspring1.genes.resize(dimensions); - offspring2.genes.resize(dimensions); - offspring1.fitness = 1e10; - offspring2.fitness = 1e10; - offspring1.isEvaluated = false; - offspring2.isEvaluated = false; - - // 均匀交叉 - for(int i = 0; i < dimensions; ++i) { - if(random01() < 0.5) { - offspring1.genes[i] = parent1.genes[i]; - offspring2.genes[i] = parent2.genes[i]; - } else { - offspring1.genes[i] = parent2.genes[i]; - offspring2.genes[i] = parent1.genes[i]; - } - } -} - -void nmCalculationAutoFitGA::mutate(GAIndividual& individual) -{ - // 使用高斯变异作为主要方式 - gaussianMutation(individual); -} - -void nmCalculationAutoFitGA::gaussianMutation(GAIndividual& individual) -{ - for(int i = 0; i < individual.genes.size(); ++i) { - if(random01() < m_mutationRate) { - int paramIndex = m_enabledParamIndices[i]; - double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex]; - double sigma = range * MUTATION_STRENGTH; - - // 高斯变异 - double mutation = gaussianRandom(0.0, sigma); - individual.genes[i] += mutation; - - // 边界处理 - individual.genes[i] = qMax(m_parameterLower[paramIndex], - qMin(m_parameterUpper[paramIndex], individual.genes[i])); - } - } - - // 标记为未评估 - individual.isEvaluated = false; -} - -void nmCalculationAutoFitGA::polynomialMutation(GAIndividual& individual) -{ - const double eta = 20.0; // 分布指数 - - for(int i = 0; i < individual.genes.size(); ++i) { - if(random01() < m_mutationRate) { - int paramIndex = m_enabledParamIndices[i]; - double lower = m_parameterLower[paramIndex]; - double upper = m_parameterUpper[paramIndex]; - double y = individual.genes[i]; - - double delta1 = (y - lower) / (upper - lower); - double delta2 = (upper - y) / (upper - lower); - - double rnd = random01(); - double mut_pow = 1.0 / (eta + 1.0); - - double deltaq; - - if(rnd <= 0.5) { - double xy = 1.0 - delta1; - double val = 2.0 * rnd + (1.0 - 2.0 * rnd) * pow(xy, eta + 1.0); - deltaq = pow(val, mut_pow) - 1.0; - } else { - double xy = 1.0 - delta2; - double val = 2.0 * (1.0 - rnd) + 2.0 * (rnd - 0.5) * pow(xy, eta + 1.0); - deltaq = 1.0 - pow(val, mut_pow); - } - - y += deltaq * (upper - lower); - individual.genes[i] = qMax(lower, qMin(upper, y)); - } - } - - // 标记为未评估 - individual.isEvaluated = false; -} - -void nmCalculationAutoFitGA::applyElitism(QVector& newPopulation) -{ - int eliteCount = static_cast(m_populationSize * m_elitismRate); - - if(eliteCount == 0) return; - - // 对种群按适应度排序 - QVector sortedPopulation = m_population; - - // 冒泡排序(适应度从小到大) - for(int i = 0; i < sortedPopulation.size() - 1; ++i) { - for(int j = 0; j < sortedPopulation.size() - 1 - i; ++j) { - if(sortedPopulation[j].fitness > sortedPopulation[j + 1].fitness) { - GAIndividual temp = sortedPopulation[j]; - sortedPopulation[j] = sortedPopulation[j + 1]; - sortedPopulation[j + 1] = temp; - } - } - } - - // 复制精英个体到新种群 - for(int i = 0; i < eliteCount && i < sortedPopulation.size(); ++i) { - newPopulation.append(sortedPopulation[i]); - } - - DEBUG_OUT(QString("Applied elitism: %1 elite individuals preserved").arg(eliteCount)); -} - -void nmCalculationAutoFitGA::updatePopulationStatistics() -{ - if(m_population.isEmpty()) return; - - m_previousBestFitness = m_bestFitness; - - double sum = 0.0; - double minFitness = 1e10; - double maxFitness = -1e10; - int validCount = 0; - - for(int i = 0; i < m_population.size(); ++i) { - const GAIndividual& individual = m_population[i]; - - if(individual.isEvaluated && individual.fitness < 1e9) { - sum += individual.fitness; - validCount++; - - if(individual.fitness < minFitness) { - minFitness = individual.fitness; - } - - if(individual.fitness > maxFitness) { - maxFitness = individual.fitness; - } - - // 更新全局最优 - if(individual.fitness < m_bestFitness) { - // 只有显著改进时才更新 - double improvement = (m_bestFitness - individual.fitness); - double relativeImprovement = improvement / qMax(1e-10, qAbs(m_bestFitness)); - - if(relativeImprovement > m_improvementThreshold) { - m_bestFitness = individual.fitness; - m_bestIndividual = individual; - - DEBUG_OUT(QString("Global best updated with %1% improvement: %2") - .arg(relativeImprovement * 100, 0, 'f', 3) - .arg(m_bestFitness, 0, 'e', 4)); - } - } - } - } - - if(validCount > 0) { - m_averageFitness = sum / validCount; - m_worstFitness = maxFitness; - } else { - m_averageFitness = 1e10; - m_worstFitness = 1e10; - } - - // 在没有找到更好解时才检查精英保护 - if(m_hasValidUserSolution && m_userInitialFitness < m_bestFitness) { - DEBUG_OUT("Elite protection: No significant improvement found, checking initial solution"); - - // 检查初始解是否仍然是最优的 - double improvement = m_bestFitness - m_userInitialFitness; - double relativeImprovement = improvement / qMax(1e-10, qAbs(m_bestFitness)); - - if(relativeImprovement > m_improvementThreshold * 0.5) { // 使用更宽松的阈值 - DEBUG_OUT("Elite protection: Restoring user initial solution"); - m_bestFitness = m_userInitialFitness; - m_bestIndividual.genes = m_userInitialSolution; - m_bestIndividual.fitness = m_userInitialFitness; - m_bestIndividual.isEvaluated = true; - } - } -} - -bool nmCalculationAutoFitGA::checkConvergence() -{ - if(m_convergenceHistory.size() < CONVERGENCE_CHECK_INTERVAL) { - return false; - } - - // 检查最近几次世代的改进 - double recentBest = m_convergenceHistory.last(); - double oldBest = m_convergenceHistory[m_convergenceHistory.size() - CONVERGENCE_CHECK_INTERVAL]; - - double improvement = oldBest - recentBest; - - // 如果连续多代没有显著改进,认为已收敛 - if(improvement < MIN_FITNESS_IMPROVEMENT) { - // 检查是否连续停滞 - int stagnationCount = 0; - - for(int i = m_convergenceHistory.size() - 1; i >= qMax(0, m_convergenceHistory.size() - MAX_STAGNATION_GENERATIONS); --i) { - if(i > 0) { - double diff = m_convergenceHistory[i - 1] - m_convergenceHistory[i]; - - if(diff < MIN_FITNESS_IMPROVEMENT) { - stagnationCount++; - } else { - break; - } - } - } - - return stagnationCount >= MAX_STAGNATION_GENERATIONS; - } - - return false; -} - -void nmCalculationAutoFitGA::adaptiveParameterUpdate(int generation) -{ - // 自适应调整变异率 - double progress = static_cast(generation) / m_maxGenerations; - - // 早期探索,后期开发 - m_mutationRate = 0.2 * (1.0 - progress) + 0.05 * progress; - - // 自适应调整交叉率 - if(generation > 0) { - double improvement = m_previousBestFitness - m_bestFitness; - - if(improvement < MIN_FITNESS_IMPROVEMENT) { - // 如果改进很小,增加探索性 - m_mutationRate = qMin(0.3, m_mutationRate * 1.1); - m_crossoverRate = qMax(0.6, m_crossoverRate * 0.95); - } else { - // 如果有明显改进,增加开发性 - m_mutationRate = qMax(0.05, m_mutationRate * 0.9); - m_crossoverRate = qMin(0.9, m_crossoverRate * 1.05); - } - } -} - -void nmCalculationAutoFitGA::validateAndProtectFinalResult() -{ - if(!m_hasValidUserSolution) { - emit logMessageGenerated(tr("No initial solution for elite protection")); - return; - } - - emit logMessageGenerated(tr("=== Final Result Validation (Elite Protection) ===")); - - // 使用已有的评估结果 - double finalFitness = m_bestFitness; - double initialFitness = m_userInitialFitness; - - emit logMessageGenerated(tr("Comparing results: Initial=%1, Final=%2") - .arg(initialFitness, 0, 'e', 4).arg(finalFitness, 0, 'e', 4)); - - // 计算改进程度 - double improvement = initialFitness - finalFitness; - double relativeImprovement = improvement / qMax(1e-10, qAbs(initialFitness)); - - emit logMessageGenerated(tr("Improvement: %1 (%2%)") - .arg(improvement, 0, 'e', 4).arg(relativeImprovement * 100, 0, 'f', 2)); - - if(relativeImprovement < m_improvementThreshold) { - emit logMessageGenerated(tr("Elite protection triggered: insufficient improvement")); - emit logMessageGenerated(tr("Threshold: %1%, Actual: %2%") - .arg(m_improvementThreshold * 100, 0, 'f', 2) - .arg(relativeImprovement * 100, 0, 'f', 4)); - emit logMessageGenerated(tr("Restoring initial solution as final result")); - - m_bestFitness = initialFitness; - m_bestIndividual.genes = m_userInitialSolution; - m_bestIndividual.fitness = initialFitness; - m_bestIndividual.isEvaluated = true; - - emit logMessageGenerated(tr("Initial solution restored successfully")); - } else { - emit logMessageGenerated(tr("Final result validated - significant improvement achieved")); - } -} - -StopReasonGA nmCalculationAutoFitGA::analyzeOptimizationStatus() -{ - // 1. 检查用户停止 - if(m_shouldStop) { - return GA_USER_STOPPED; - } - - // 2. 检查连续失败 - if(m_consecutiveFailedGenerations >= m_maxConsecutiveFailures) { - return GA_CONSECUTIVE_FAILURES; - } - - // 3. 检查是否达到目标精度 - if(m_bestFitness < m_targetError) { - return GA_TARGET_ACHIEVED; - } - - // 4. 检查是否达到最大迭代数 - if(m_currentGeneration >= m_maxGenerations - 1) { - return GA_MAX_ITERATIONS; - } - - // 5. 需要足够的历史数据才能判断收敛 - if(m_convergenceHistory.size() < m_localOptimumWindow) { - return GA_CONTINUE_OPTIMIZATION; - } - - // 6. 检查真正的收敛 - if(m_convergenceHistory.size() >= m_trueConvergenceWindow && checkTrueConvergence()) { - return GA_TRUE_CONVERGENCE; - } - - // 7. 检查局部最优陷阱 - if(checkLocalOptimumTrap()) { - return GA_LOCAL_OPTIMUM; - } - - return GA_CONTINUE_OPTIMIZATION; -} - -bool nmCalculationAutoFitGA::checkTrueConvergence() const -{ - if(m_convergenceHistory.size() < m_trueConvergenceWindow) { - return false; - } - - // 1. 检查解质量 - 如果已经接近目标,小改进可能是真收敛 - bool nearTarget = (m_bestFitness < m_targetError * m_nearTargetFactor); - - // 2. 检查适应度稳定性 - 长期小幅波动 - double recentVariance = calculateFitnessVariance(10); - double recentMean = 0.0; - int windowSize = qMin(10, m_convergenceHistory.size()); - - // 计算最近窗口的均值 - for(int i = m_convergenceHistory.size() - windowSize; i < m_convergenceHistory.size(); ++i) { - recentMean += m_convergenceHistory[i]; - } - - recentMean /= windowSize; - - double relativeVariance = recentVariance / qMax(1e-10, recentMean * recentMean); - bool stableError = (relativeVariance < m_convergenceVarianceThreshold); - - // 3. 检查种群多样性 - 应该收敛到同一区域 - double currentDiversity = calculatePopulationDiversity(); - bool lowDiversity = (currentDiversity < m_diversityThreshold); - - // 4. 检查长期改进趋势 - double longTermImprovement = calculateLongTermImprovement(m_trueConvergenceWindow); - bool minimalLongTermImprovement = (longTermImprovement < 1e-4); // 0.01% - - // 真收敛的判断条件 - bool isConverged = nearTarget || - (stableError && lowDiversity && minimalLongTermImprovement); - - if(isConverged) { - DEBUG_OUT("=== TRUE CONVERGENCE ANALYSIS ==="); - DEBUG_OUT(QString("nearTarget=%1 (fitness=%2, target*factor=%3)") - .arg(nearTarget).arg(m_bestFitness, 0, 'e', 4) - .arg(m_targetError * m_nearTargetFactor, 0, 'e', 4)); - DEBUG_OUT(QString("stableError=%1 (relativeVariance=%2)") - .arg(stableError).arg(relativeVariance, 0, 'e', 6)); - DEBUG_OUT(QString("lowDiversity=%1 (diversity=%2, threshold=%3)") - .arg(lowDiversity).arg(currentDiversity, 0, 'f', 6).arg(m_diversityThreshold)); - DEBUG_OUT(QString("longTermImprovement=%1%") - .arg(longTermImprovement * 100, 0, 'f', 4)); - } - - return isConverged; -} - -bool nmCalculationAutoFitGA::checkLocalOptimumTrap() const -{ - if(m_convergenceHistory.size() < m_localOptimumWindow) { - return false; - } - - // 1. 检查解质量 - 如果距离目标还很远,停滞就可能是局部最优 - bool farFromTarget = (m_bestFitness > m_targetError * m_farTargetFactor); - - // 2. 检查短期改进 - 近期改进非常小 - double shortTermImprovement = calculateLongTermImprovement(m_localOptimumWindow); - bool poorShortTermImprovement = (shortTermImprovement < 1e-5); // 0.001% - - // 3. 检查种群多样性 - 可能过早聚集或无效分散 - double currentDiversity = calculatePopulationDiversity(); - bool problematicDiversity = (currentDiversity < m_diversityThreshold * 0.1) || - (currentDiversity > m_diversityThreshold * 5.0); - - // 4. 检查适应度方差 - 可能卡在平坦区域 - double fitnessVariance = calculateFitnessVariance(m_localOptimumWindow); - bool flatFitnessLandscape = (fitnessVariance < m_convergenceVarianceThreshold * 0.1); - - // 局部最优的判断条件 - bool isLocalOptimum = farFromTarget && poorShortTermImprovement && - (problematicDiversity || flatFitnessLandscape); - - if(isLocalOptimum) { - DEBUG_OUT("=== LOCAL OPTIMUM ANALYSIS ==="); - DEBUG_OUT(QString("farFromTarget=%1 (fitness=%2, target*factor=%3)") - .arg(farFromTarget).arg(m_bestFitness, 0, 'e', 4) - .arg(m_targetError * m_farTargetFactor, 0, 'e', 4)); - DEBUG_OUT(QString("poorShortTermImprovement=%1 (improvement=%2%)") - .arg(poorShortTermImprovement).arg(shortTermImprovement * 100, 0, 'f', 4)); - DEBUG_OUT(QString("problematicDiversity=%1 (diversity=%2)") - .arg(problematicDiversity).arg(currentDiversity, 0, 'f', 6)); - DEBUG_OUT(QString("fitnessVariance=%1") - .arg(fitnessVariance, 0, 'e', 6)); - } - - return isLocalOptimum; -} - -double nmCalculationAutoFitGA::calculatePopulationDiversity() const -{ - if(m_population.size() < 2) return 0.0; - - int dimensions = getEnabledParameterCount(); - - if(dimensions == 0) return 0.0; - - double totalDiversity = 0.0; - - for(int dim = 0; dim < dimensions; ++dim) { - // 计算该维度上所有个体的均值 - double mean = 0.0; - - for(int i = 0; i < m_population.size(); ++i) { - mean += m_population[i].genes[dim]; - } - - mean /= m_population.size(); - - // 计算该维度上的方差 - double variance = 0.0; - - for(int i = 0; i < m_population.size(); ++i) { - double diff = m_population[i].genes[dim] - mean; - variance += diff * diff; - } - - variance /= m_population.size(); - - // 归一化到参数范围 - int paramIndex = m_enabledParamIndices[dim]; - double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex]; - double normalizedStd = sqrt(variance) / qMax(1e-10, range); - - totalDiversity += normalizedStd; - } - - return totalDiversity / dimensions; -} - -double nmCalculationAutoFitGA::calculateFitnessVariance(int windowSize) const -{ - if(m_convergenceHistory.size() < windowSize) { - return 1e10; // 数据不足,返回大值 - } - - // 计算最近windowSize次迭代的方差 - double mean = 0.0; - int startIdx = m_convergenceHistory.size() - windowSize; - - for(int i = startIdx; i < m_convergenceHistory.size(); ++i) { - mean += m_convergenceHistory[i]; - } - - mean /= windowSize; - - double variance = 0.0; - - for(int i = startIdx; i < m_convergenceHistory.size(); ++i) { - double diff = m_convergenceHistory[i] - mean; - variance += diff * diff; - } - - variance /= windowSize; - - return variance; -} - -double nmCalculationAutoFitGA::calculateLongTermImprovement(int windowSize) const -{ - if(m_convergenceHistory.size() < windowSize) { - return 1.0; // 数据不足,假设有改进 - } - - double oldFitness = m_convergenceHistory[m_convergenceHistory.size() - windowSize]; - double improvement = (oldFitness - m_bestFitness) / qMax(1e-10, qAbs(oldFitness)); - - return improvement; -} - -void nmCalculationAutoFitGA::updateConvergenceMetrics() -{ - // 更新多样性历史 - m_diversityHistory.append(calculatePopulationDiversity()); - - // 保持历史长度合理(最多保留50个数据点) - const int maxHistorySize = 50; - - while(m_diversityHistory.size() > maxHistorySize) { - m_diversityHistory.remove(0); - } -} - -// ==================== 参数应用方法 ==================== - -bool nmCalculationAutoFitGA::validateParameters(const QVector& parameters) const -{ - if(parameters.size() != getEnabledParameterCount()) { - return false; - } - - for(int i = 0; i < parameters.size(); ++i) { - if(!isFiniteNumber(parameters[i])) { - return false; - } - - // 检查参数范围 - if(i < m_enabledParamIndices.size()) { - int paramIndex = m_enabledParamIndices[i]; - - if(paramIndex >= 0 && paramIndex < m_parameterLower.size()) { - if(parameters[i] < m_parameterLower[paramIndex] || - parameters[i] > m_parameterUpper[paramIndex]) { - return false; - } - } - } - } - - // 直接拦截会导致求解器数值崩溃的参数值 - for(int i = 0; i < parameters.size() && i < m_enabledParamIndices.size(); ++i) { - int paramIndex = m_enabledParamIndices[i]; - double value = parameters[i]; - - switch(paramIndex) { - case 0: // 渗透率:必须大于零 - if(value <= 1e-8) { - DEBUG_OUT(QString("Rejecting near-zero permeability: %1").arg(value)); - return false; - } - - break; - - case 2: // 井筒储集系数:必须大于零 - if(value <= 1e-10) { - DEBUG_OUT(QString("Rejecting near-zero wellbore storage: %1").arg(value)); - return false; - } - - break; - - case 3: // 孔隙度:必须在合理范围 - if(value <= 1e-6 || value >= 0.99) { - DEBUG_OUT(QString("Rejecting unrealistic porosity: %1").arg(value)); - return false; - } - - break; - - case 5: // 综合压缩系数:必须大于零 - if(value <= 1e-8) { - DEBUG_OUT(QString("Rejecting near-zero total compressibility: %1").arg(value)); - return false; - } - - break; - } - } - - return true; -} - -bool nmCalculationAutoFitGA::validateLogLogData(const QVector>& logLogData) const -{ - // 检查基本结构 - if(logLogData.size() < 3) { - DEBUG_OUT("LogLog data has less than 3 arrays"); - return false; - } - - // 检查数组大小一致性 - int size = logLogData[0].size(); - - if(size == 0) { - DEBUG_OUT("Empty LogLog data"); - return false; - } - - if(logLogData[1].size() != size || logLogData[2].size() != size) { - DEBUG_OUT(QString("LogLog data size mismatch: X=%1, Y1=%2, Y2=%3") - .arg(logLogData[0].size()) - .arg(logLogData[1].size()) - .arg(logLogData[2].size())); - 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]) || - !isFiniteNumber(logLogData[2][i])) { - DEBUG_OUT(QString("Invalid LogLog data at index %1").arg(i)); - return false; - } - } - - return true; -} - -bool nmCalculationAutoFitGA::validateInitialValues() const -{ - if(m_initialValues.size() != m_enabledParamIndices.size()) { - DEBUG_OUT("Initial values count mismatch with enabled parameters"); - return false; - } - - bool allValid = true; - - for(int i = 0; i < m_initialValues.size(); ++i) { - int paramIndex = m_enabledParamIndices[i]; - double value = m_initialValues[i]; - - if(!isFiniteNumber(value)) { - DEBUG_OUT(QString("Initial value[%1] is not finite: %2").arg(i).arg(value)); - allValid = false; - continue; - } - - if(paramIndex < m_parameterLower.size() && paramIndex < m_parameterUpper.size()) { - double minVal = m_parameterLower[paramIndex]; - double maxVal = m_parameterUpper[paramIndex]; - - if(value < minVal || value > maxVal) { - DEBUG_OUT(QString("Initial value[%1] = %2 is outside bounds [%3, %4]") - .arg(i).arg(value).arg(minVal).arg(maxVal)); - allValid = false; - } - } - } - - return allValid; -} - -void nmCalculationAutoFitGA::applyParametersToDataManager(const QVector& parameters) -{ - if(parameters.size() != getEnabledParameterCount()) { - return; - } - - updateReservoirParameters(parameters); - updateWellParameters(parameters); -} - -void nmCalculationAutoFitGA::updateReservoirParameters(const QVector& parameters) -{ - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - - int paramIndex = 0; - for(int i = 0; i < m_parameterSelected.size(); ++i) { - if(m_parameterSelected[i] && paramIndex < parameters.size()) { - double value = parameters[paramIndex]; - - switch(i) { - case 0: // 渗透率 - reservoirData.getPermeability().setValue(value); - break; - case 3: // 孔隙度 - reservoirData.getPorosity().setValue(value); - break; - case 4: // 储层厚度 - reservoirData.getThickness().setValue(value); - break; - case 5: // 综合压缩系数 - reservoirData.getCt().setValue(value); - break; - case 6: // 岩石压缩系数 - reservoirData.getCf().setValue(value); - break; - case 7: // 初始含水饱和度 - reservoirData.getSwi().setValue(value); - break; - } - paramIndex++; - } - } - - // 更新数据管理器 - dataManager->updateReservoirData(reservoirData); -} - -void nmCalculationAutoFitGA::updateWellParameters(const QVector& parameters) -{ - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - - // 获取井列表,更新第一口井的参数 - //QVector wells = dataManager->getWellDataList(); - nmDataWellBase* pWell = dataManager->findWellByName(m_targetWellName); - if(!pWell) return; - - //nmDataWellBase* pWell = wells[0]; // 使用第一口井 - - int paramIndex = 0; - for(int i = 0; i < m_parameterSelected.size(); ++i) { - if(m_parameterSelected[i] && paramIndex < parameters.size()) { - double value = parameters[paramIndex]; - - switch(i) { - case 1: { // 表皮系数 - nmDataAttribute skinAttr = pWell->getPerforation(0)->getSkin(); - skinAttr.setValue(value); - pWell->setRateDependentSkin(skinAttr); - } - break; - case 2: { // 井筒储集系数 - nmDataAttribute wellboreAttr = pWell->getWellboreStorage(); - wellboreAttr.setValue(value); - pWell->setWellboreStorage(wellboreAttr); - } - break; - } - paramIndex++; - } - } - - // 根据井类型更新到数据管理器 - updateWellToDataManager(pWell); -} - -void nmCalculationAutoFitGA::updateWellToDataManager(nmDataWellBase* pWell) -{ - if(!pWell) return; - - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - NM_WELL_MODEL wellType = pWell->getWellType(); - - switch(wellType) { - case NM_WELL_MODEL::Vertical_Well: { - nmDataVerticalWell* pVerticalWell = dynamic_cast(pWell); - if(pVerticalWell) { - QVector wells; - wells.append(*pVerticalWell); - dataManager->updateVerticalWells(wells); - } - break; - } - case NM_WELL_MODEL::Vertical_Fractured_Well: { - nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast(pWell); - if(pVFracturedWell) { - QVector wells; - wells.append(*pVFracturedWell); - dataManager->updateVerticalFracturedWells(wells); - } - break; - } - case NM_WELL_MODEL::Horizontal_Fractured_Well: { - nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast(pWell); - if(pHFracturedWell) { - QVector wells; - wells.append(*pHFracturedWell); - dataManager->updateHorizontalFracturedWells(wells); - } - break; - } - default: - break; - } -} - -void nmCalculationAutoFitGA::clampToLimits(QVector& parameters) const -{ - for(int i = 0; i < parameters.size() && i < m_enabledParamIndices.size(); ++i) { - int paramIndex = m_enabledParamIndices[i]; - if(paramIndex >= 0 && paramIndex < m_parameterLower.size()) { - parameters[i] = qMax(m_parameterLower[paramIndex], - qMin(m_parameterUpper[paramIndex], parameters[i])); - } - } -} - -// ==================== 求解器相关方法 ==================== - -QVector> nmCalculationAutoFitGA::runSolver() -{ - //return runSolverExe(); - return runSolverDll(); -} - -bool nmCalculationAutoFitGA::validateSolverResult(const QVector>& result) const -{ - // 基本检查 - if(result.size() < 2) { - DEBUG_OUT("Solver result has less than 2 arrays"); - return false; - } - - if(result[0].size() != result[1].size()) { - DEBUG_OUT(QString("Size mismatch: X=%1, Y=%2").arg(result[0].size()).arg(result[1].size())); - return false; - } - - if(result[0].size() == 0) { - DEBUG_OUT("Empty solver result"); - 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") - .arg(i).arg(result[0][i]).arg(result[1][i])); - return false; - } - } - - return true; -} - -QVector> nmCalculationAutoFitGA::runSolverDll() -{ - DEBUG_OUT("SOLVER DLL START"); - - if(m_evaluationInProgress > 0) { - DEBUG_OUT("DLL Solver already running, skipping"); - return QVector>(); - } - - ++m_evaluationInProgress; - QVector> result; - nmCalculationDllPebiSolverTask* dllTask = nullptr; - - try { - DEBUG_OUT("Creating DLL solver task"); - dllTask = new nmCalculationDllPebiSolverTask(m_tempDirectory); - - if(m_shouldStop) { - DEBUG_OUT("Should stop - cleaning up and returning empty result"); - delete dllTask; - --m_evaluationInProgress; - return result; - } - - DEBUG_OUT("Starting DLL solver execution..."); - - // 异步执行 - dllTask->start(); - - // 等待完成 - int waitTime = 0; - const int maxWait = 30000; // 30秒超时 - const int checkInterval = 500; - - while(waitTime < maxWait) { - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100); - - if(!dllTask->isRunning()) { - DEBUG_OUT("DLL solver task completed"); - break; - } - - if(m_shouldStop) { - DEBUG_OUT("DLL solver task terminated by user"); - dllTask->terminate(); - break; - } - - msleep(checkInterval); - waitTime += checkInterval; - } - - // 超时处理 - if(dllTask->isRunning()) { - DEBUG_OUT("DLL solver task timeout, terminating..."); - dllTask->terminate(); - dllTask->wait(2000); - - delete dllTask; - dllTask = nullptr; - --m_evaluationInProgress; - m_consecutiveFailures++; - return result; - } - - // 验证结果数据是否已更新 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - //QVector wells = dataManager->getWellDataList(); - nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); - - if(!pTargetWell) { - DEBUG_OUT("No wells found in data manager after DLL execution"); - delete dllTask; - --m_evaluationInProgress; - return result; - } - - // 验证结果数据 - QVector> pressureResult = pTargetWell->getResultPressure(); - QVector> logLogResult = pTargetWell->getResultLogLog(); - - DEBUG_OUT(QString("DLL result verification - Pressure arrays: %1, LogLog arrays: %2") - .arg(pressureResult.size()).arg(logLogResult.size())); - - if(pressureResult.size() >= 2) { - DEBUG_OUT(QString("Pressure result - Time points: %1, Pressure points: %2") - .arg(pressureResult[0].size()).arg(pressureResult[1].size())); - - if(pressureResult[0].size() > 0) { - DEBUG_OUT(QString("Sample pressure data - Time[0]: %1, Time[last]: %2, P[0]: %3, P[last]: %4") - .arg(pressureResult[0][0]) - .arg(pressureResult[0][pressureResult[0].size()-1]) - .arg(pressureResult[1][0]) - .arg(pressureResult[1][pressureResult[1].size()-1])); - } - } - - // 数据有效性检查 - if(pressureResult.size() >= 2 && pressureResult[0].size() > 0 && pressureResult[1].size() > 0) { - result = pressureResult; - DEBUG_OUT(QString("Got DLL solver result: %1 points").arg(result[0].size())); - m_consecutiveFailures = 0; - - // 检查结果是否与之前不同 - 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"); - DEBUG_OUT(QString("Pressure result size: %1, Array sizes: %2, %3") - .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(); - dllTask->wait(3000); - } - - DEBUG_OUT("Cleaning up DLL solver task..."); - delete dllTask; - dllTask = nullptr; - } - - QApplication::processEvents(QEventLoop::AllEvents, 100); - --m_evaluationInProgress; - - DEBUG_OUT(QString("SOLVER DLL END - ResultPoints: %1") - .arg(result.isEmpty() ? 0 : result[0].size())); - - return result; -} - -//QVector> nmCalculationAutoFitGA::runSolverExe() -//{ -// DEBUG_OUT("SOLVER EXE START"); -// -// if(m_evaluationInProgress > 0) { -// DEBUG_OUT("EXE Solver already running, skipping"); -// return QVector>(); -// } -// -// ++m_evaluationInProgress; -// QVector> result; -// nmCalculationExeSolverTask* exeTask = nullptr; -// -// try { -// DEBUG_OUT("Creating EXE solver task"); -// exeTask = new nmCalculationExeSolverTask(QString()); -// -// if(m_shouldStop) { -// DEBUG_OUT("Should stop - cleaning up and returning empty result"); -// delete exeTask; -// --m_evaluationInProgress; -// return result; -// } -// -// DEBUG_OUT("Starting EXE solver execution..."); -// -// // 启动 -// bool executeSuccess = exeTask->execute(); -// -// // 检查执行状态 -// if(!executeSuccess) { -// DEBUG_OUT(QString("EXE solver execution failed: %1").arg(exeTask->getLastError())); -// DEBUG_OUT(QString("EXE solver exit code: %1").arg(exeTask->getExitCode())); -// -// // 清理并返回空结果 -// delete exeTask; -// exeTask = nullptr; -// --m_evaluationInProgress; -// m_consecutiveFailures++; -// return result; -// } -// -// DEBUG_OUT("EXE solver execution completed successfully"); -// -// // 验证结果数据是否已更新 -// nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); -// QVector wells = dataManager->getWellDataList(); -// -// if(wells.isEmpty()) { -// DEBUG_OUT("No wells found in data manager after EXE execution"); -// delete exeTask; -// --m_evaluationInProgress; -// return result; -// } -// -// // 验证结果数据的时间戳或唯一性 -// QVector> pressureResult = wells[0]->getResultPressure(); -// QVector> logLogResult = wells[0]->getResultLogLog(); -// -// DEBUG_OUT(QString("Raw result verification - Pressure arrays: %1, LogLog arrays: %2") -// .arg(pressureResult.size()).arg(logLogResult.size())); -// -// if(pressureResult.size() >= 2) { -// DEBUG_OUT(QString("Pressure result - Time points: %1, Pressure points: %2") -// .arg(pressureResult[0].size()).arg(pressureResult[1].size())); -// -// if(pressureResult[0].size() > 0) { -// DEBUG_OUT(QString("Sample pressure data - Time[0]: %1, Time[last]: %2, P[0]: %3, P[last]: %4") -// .arg(pressureResult[0][0]) -// .arg(pressureResult[0][pressureResult[0].size() - 1]) -// .arg(pressureResult[1][0]) -// .arg(pressureResult[1][pressureResult[1].size() - 1])); -// } -// } -// -// if(logLogResult.size() >= 3) { -// DEBUG_OUT(QString("LogLog result - X points: %1, Y1 points: %2, Y2 points: %3") -// .arg(logLogResult[0].size()).arg(logLogResult[1].size()).arg(logLogResult[2].size())); -// -// if(logLogResult[0].size() > 0) { -// DEBUG_OUT(QString("Sample LogLog data - X[0]: %1, X[last]: %2, Y1[0]: %3, Y2[0]: %4") -// .arg(logLogResult[0][0]) -// .arg(logLogResult[0][logLogResult[0].size() - 1]) -// .arg(logLogResult[1][0]) -// .arg(logLogResult[2][0])); -// } -// } -// -// // 数据有效性检查 -// if(pressureResult.size() >= 2 && pressureResult[0].size() > 0 && pressureResult[1].size() > 0) { -// result = pressureResult; -// DEBUG_OUT(QString("Got EXE solver result: %1 points").arg(result[0].size())); -// m_consecutiveFailures = 0; -// -// // 检查结果是否与之前不同 -// 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 EXE"); -// lastPressureResult = pressureResult[1]; -// } else { -// DEBUG_OUT("!!! WARNING: Result data appears to be identical to previous run !!!"); -// } -// -// } else { -// DEBUG_OUT("EXE solver result is empty or invalid"); -// DEBUG_OUT(QString("Pressure result size: %1, Array sizes: %2, %3") -// .arg(pressureResult.size()) -// .arg(pressureResult.size() > 0 ? pressureResult[0].size() : 0) -// .arg(pressureResult.size() > 1 ? pressureResult[1].size() : 0)); -// m_consecutiveFailures++; -// } -// -// } catch(const std::exception& e) { -// DEBUG_OUT(QString("Exception in EXE solver: %1").arg(e.what())); -// m_consecutiveFailures++; -// } catch(...) { -// DEBUG_OUT("Unknown exception in EXE solver"); -// m_consecutiveFailures++; -// } -// -// // 清理EXE任务 -// if(exeTask) { -// DEBUG_OUT("Cleaning up EXE solver task..."); -// delete exeTask; -// exeTask = nullptr; -// } -// -// QApplication::processEvents(QEventLoop::AllEvents, 100); -// --m_evaluationInProgress; -// -// DEBUG_OUT(QString("SOLVER EXE END - ResultPoints: %1") -// .arg(result.isEmpty() ? 0 : result[0].size())); -// -// return result; -//} - -// ==================== 数据处理方法 ==================== -QVector nmCalculationAutoFitGA::interpolateData( - const QVector& source, const QVector& targetX) const -{ - QVector result; - - if(source.isEmpty() || targetX.isEmpty()) { - DEBUG_OUT("Warning: Empty data in interpolation"); - return result; - } - - // 数据清理和验证合并 - QVector validSource; - const double MAX_REASONABLE_VALUE = 1e12; - - for(int i = 0; i < source.size(); ++i) { - const QPointF& point = source[i]; - - // 检查数值有效性 - if(!isFiniteNumber(point.x()) || !isFiniteNumber(point.y())) { - DEBUG_OUT(QString("Skipping invalid data point at index %1: X=%2, Y=%3") - .arg(i).arg(point.x()).arg(point.y())); - continue; - } - - // 检查极大值 - 跳过而不是失败 - if(qAbs(point.y()) > MAX_REASONABLE_VALUE) { - DEBUG_OUT(QString("Skipping extremely large Y value at index %1: %2") - .arg(i).arg(point.y())); - continue; - } - - // 只保留有效的数据点 - validSource.append(point); - } - - // 检查清理后的数据是否足够 - if(validSource.size() < 3) { - DEBUG_OUT(QString("Insufficient valid data points after cleaning: %1") - .arg(validSource.size())); - return result; // 返回空结果,但不算失败 - } - - DEBUG_OUT(QString("Data cleaning: %1 -> %2 valid points") - .arg(source.size()).arg(validSource.size())); - - // 对清理后的数据进行排序 - QVector sortedSource = validSource; - - for(int i = 0; i < sortedSource.size() - 1; ++i) { - for(int j = 0; j < sortedSource.size() - 1 - i; ++j) { - if(sortedSource[j].x() > sortedSource[j + 1].x()) { - QPointF temp = sortedSource[j]; - sortedSource[j] = sortedSource[j + 1]; - sortedSource[j + 1] = temp; - } - } - } - - double sourceMinX = sortedSource.first().x(); - double sourceMaxX = sortedSource.last().x(); - - double targetMinX = targetX[0]; - double targetMaxX = targetX[0]; - - for(int i = 1; i < targetX.size(); ++i) { - if(targetX[i] < targetMinX) targetMinX = targetX[i]; - - if(targetX[i] > targetMaxX) targetMaxX = targetX[i]; - } - - DEBUG_OUT(QString("Source X range: [%1, %2], Target X range: [%3, %4]") - .arg(sourceMinX).arg(sourceMaxX).arg(targetMinX).arg(targetMaxX)); - - // 安全插值算法 - for(int i = 0; i < targetX.size(); ++i) { - double x = targetX[i]; - - if(!isFiniteNumber(x)) continue; - - double y = 0.0; - - // 插值逻辑(保持原有逻辑,但使用sortedSource) - if(x <= sourceMinX) { - if(sortedSource.size() >= 2) { - double dx = sortedSource[1].x() - sortedSource[0].x(); - - if(qAbs(dx) > 1e-10) { - double slope = (sortedSource[1].y() - sortedSource[0].y()) / dx; - slope = qMax(-1e6, qMin(1e6, slope)); - y = sortedSource[0].y() + slope * (x - sortedSource[0].x()); - } else { - y = sortedSource[0].y(); - } - } else { - y = sortedSource[0].y(); - } - } else if(x >= sourceMaxX) { - if(sortedSource.size() >= 2) { - int lastIdx = sortedSource.size() - 1; - double dx = sortedSource[lastIdx].x() - sortedSource[lastIdx - 1].x(); - - if(qAbs(dx) > 1e-10) { - double slope = (sortedSource[lastIdx].y() - sortedSource[lastIdx - 1].y()) / dx; - slope = qMax(-1e6, qMin(1e6, slope)); - y = sortedSource[lastIdx].y() + slope * (x - sortedSource[lastIdx].x()); - } else { - y = sortedSource[lastIdx].y(); - } - } else { - y = sortedSource.last().y(); - } - } else { - // 内插 - bool found = false; - - for(int j = 0; j < sortedSource.size() - 1; ++j) { - if(x >= sortedSource[j].x() && x <= sortedSource[j + 1].x()) { - double dx = sortedSource[j + 1].x() - sortedSource[j].x(); - - if(qAbs(dx) > 1e-10) { - double ratio = (x - sortedSource[j].x()) / dx; - y = sortedSource[j].y() + ratio * (sortedSource[j + 1].y() - sortedSource[j].y()); - } else { - y = sortedSource[j].y(); - } - - found = true; - break; - } - } - - if(!found) { - // 使用最近点 - double minDist = 1e10; - - for(int k = 0; k < sortedSource.size(); ++k) { - double dist = qAbs(sortedSource[k].x() - x); - - if(dist < minDist) { - minDist = dist; - y = sortedSource[k].y(); - } - } - } - } - - // 最终数值检查 - if(!isFiniteNumber(y)) { - y = sortedSource.size() > 0 ? sortedSource[0].y() : 1.0; - } - - y = qMax(-1e12, qMin(1e12, y)); - - result.append(QPointF(x, y)); - } - - if(result.isEmpty()) { - DEBUG_OUT("LogLog interpolation failed"); - return result; - } - - DEBUG_OUT(QString("Interpolation completed: %1 -> %2 points") - .arg(validSource.size()).arg(result.size())); - - return result; -} - -double nmCalculationAutoFitGA::calculateLogLogCurveError( - const QVector>& target, - const QVector>& result) const -{ - // 验证数据 - if(!validateLogLogData(target) || !validateLogLogData(result)) { - return 1e10; - } - - try { - // 数据对齐:找到X值的重叠区域 - double targetMinX = target[0][0]; - double targetMaxX = target[0][0]; - - for(int i = 1; i < target[0].size(); ++i) { - if(target[0][i] < targetMinX) targetMinX = target[0][i]; - - if(target[0][i] > targetMaxX) targetMaxX = target[0][i]; - } - - double resultMinX = result[0][0]; - double resultMaxX = result[0][0]; - - for(int i = 1; i < result[0].size(); ++i) { - if(result[0][i] < resultMinX) resultMinX = result[0][i]; - - if(result[0][i] > resultMaxX) resultMaxX = result[0][i]; - } - - double overlapMinX = qMax(targetMinX, resultMinX); - double overlapMaxX = qMin(targetMaxX, resultMaxX); - - if(overlapMinX >= overlapMaxX) { - DEBUG_OUT("No overlap between target and result LogLog curves"); - return 1e10; - } - - // 生成公共X网格进行插值 - QVector commonX; - int numPoints = 50; - - if(overlapMinX > 0 && overlapMaxX > 0) { - // 对数空间均匀分布 - double logMin = qLn(overlapMinX); - double logMax = qLn(overlapMaxX); - - for(int i = 0; i < numPoints; ++i) { - double logX = logMin + i * (logMax - logMin) / (numPoints - 1); - double x = qExp(logX); - - // 数值保护 - if(!isFiniteNumber(x) || x <= 0) { - continue; - } - - commonX.append(x); - } - - DEBUG_OUT("Using log-uniform grid for better early-time coverage"); - } - - if(commonX.isEmpty()) { - DEBUG_OUT("Failed to generate common X grid"); - return 1e10; - } - - // 插值目标曲线 - QVector targetCurve1, targetCurve2; - - for(int i = 0; i < target[0].size(); ++i) { - // 检查数据有效性 - if(isFiniteNumber(target[0][i]) && isFiniteNumber(target[1][i]) && - isFiniteNumber(target[2][i])) { - targetCurve1.append(QPointF(target[0][i], target[1][i])); - targetCurve2.append(QPointF(target[0][i], target[2][i])); - } - } - - if(targetCurve1.isEmpty() || targetCurve2.isEmpty()) { - DEBUG_OUT("Target curves are empty after filtering"); - return 1e10; - } - - QVector alignedTarget1 = interpolateData(targetCurve1, commonX); - QVector alignedTarget2 = interpolateData(targetCurve2, commonX); - - // 插值结果曲线 - QVector resultCurve1, resultCurve2; - - for(int i = 0; i < result[0].size(); ++i) { - // 检查数据有效性 - if(isFiniteNumber(result[0][i]) && isFiniteNumber(result[1][i]) && - isFiniteNumber(result[2][i])) { - resultCurve1.append(QPointF(result[0][i], result[1][i])); - resultCurve2.append(QPointF(result[0][i], result[2][i])); - } - } - - if(resultCurve1.isEmpty() || resultCurve2.isEmpty()) { - DEBUG_OUT("Result curves are empty after filtering"); - return 1e10; - } - - QVector alignedResult1 = interpolateData(resultCurve1, commonX); - QVector alignedResult2 = interpolateData(resultCurve2, commonX); - - // 检查插值结果 - if(alignedTarget1.isEmpty() || alignedTarget2.isEmpty() || - alignedResult1.isEmpty() || alignedResult2.isEmpty()) { - DEBUG_OUT("LogLog interpolation failed"); - return 1e10; - } - - if(alignedTarget1.size() != alignedResult1.size() || - alignedTarget2.size() != alignedResult2.size()) { - DEBUG_OUT("LogLog interpolation size mismatch"); - return 1e10; - } - - // 计算两条曲线的误差 - double error1 = calculateCurveError(alignedTarget1, alignedResult1); - double error2 = calculateCurveError(alignedTarget2, alignedResult2); - - // 检查个别误差是否有效 - if(!isFiniteNumber(error1) || error1 > 1e9) { - DEBUG_OUT(QString("Curve1 error is invalid: %1").arg(error1)); - error1 = 1e10; - } - - if(!isFiniteNumber(error2) || error2 > 1e9) { - DEBUG_OUT(QString("Curve2 error is invalid: %1").arg(error2)); - error2 = 1e10; - } - - // 组合误差 - 添加保护 - double combinedError; - - if(error1 > 1e9 && error2 > 1e9) { - combinedError = 1e10; - } else if(error1 > 1e9) { - combinedError = error2; - } else if(error2 > 1e9) { - combinedError = error1; - } else { - combinedError = 0.5 * error1 + 0.5 * error2; - } - - DEBUG_OUT(QString("LogLog errors: Curve1=%1, Curve2=%2, Combined=%3") - .arg(error1, 0, 'e', 4).arg(error2, 0, 'e', 4).arg(combinedError, 0, 'e', 4)); - - return qMin(1e9, combinedError); - - } catch(const std::exception& e) { - DEBUG_OUT(QString("Exception in LogLog error calculation: %1").arg(e.what())); - return 1e10; - } catch(...) { - DEBUG_OUT("Unknown exception in LogLog error calculation"); - return 1e10; - } -} - -double nmCalculationAutoFitGA::calculateCurveError( - const QVector& curve1, const QVector& curve2) const -{ - if(curve1.size() != curve2.size() || curve1.isEmpty()) { - return 1e10; - } - - // 预检查:确保没有无穷大值 - for(int i = 0; i < curve1.size(); ++i) { - if(!isFiniteNumber(curve1[i].y()) || !isFiniteNumber(curve2[i].y())) { - DEBUG_OUT(QString("Infinite value detected at index %1: Y1=%2, Y2=%3") - .arg(i).arg(curve1[i].y()).arg(curve2[i].y())); - return 1e10; - } - - if(qAbs(curve1[i].y()) > 1e12 || qAbs(curve2[i].y()) > 1e12) { - DEBUG_OUT(QString("Extremely large value detected at index %1") - .arg(i)); - return 1e10; - } - } - - double totalError = 0.0; - double totalWeight = 0.0; - int validPoints = 0; - - for(int i = 0; i < curve1.size(); ++i) { - double y1 = curve1[i].y(); - double y2 = curve2[i].y(); - - // 跳过异常值 - if(!isFiniteNumber(y1) || !isFiniteNumber(y2)) { - continue; - } - - // 自适应权重:根据Y值大小调整,添加上限 - double weightFactor = qMin(100.0, qAbs(y1) * 0.01); - double weight = 1.0 / (1.0 + weightFactor); - - // 相对误差和绝对误差的组合 - double yMax = qMax(qAbs(y1), qAbs(y2)); - yMax = qMax(1e-12, yMax); // 防止除零 - - double relativeError = qAbs(y1 - y2) / yMax; - double absoluteError = qAbs(y1 - y2); - - // 限制误差值 - relativeError = qMin(1e6, relativeError); - absoluteError = qMin(1e6, absoluteError); - - // 误差组合:相对误差为主,绝对误差为辅 - double pointError = 0.7 * relativeError + 0.3 * absoluteError; - - if(isFiniteNumber(pointError) && pointError < 1e10) { - totalError += weight * pointError * pointError; - totalWeight += weight; - validPoints++; - } - } - - if(totalWeight > 0 && validPoints > 0) { - double result = sqrt(totalError / totalWeight); - - // 最终检查 - if(!isFiniteNumber(result)) { - DEBUG_OUT("Final error calculation produced infinite result"); - return 1e10; - } - - return qMin(1e9, result); // 限制最大误差值 - } else { - DEBUG_OUT(QString("No valid points for error calculation: validPoints=%1") - .arg(validPoints)); - return 1e10; - } -} -// ==================== 工具方法 ==================== - -double nmCalculationAutoFitGA::random01() const -{ - return static_cast(qrand()) / RAND_MAX; -} - -double nmCalculationAutoFitGA::gaussianRandom(double mean, double stddev) const -{ - static bool hasSpare = false; - static double spare; - - if(hasSpare) { - hasSpare = false; - return spare * stddev + mean; - } - - hasSpare = true; - double u = qMax(random01(), 1e-12); - double v = random01(); - double mag = stddev * sqrt(-2.0 * log(u)); - spare = mag * cos(2.0 * 3.14159265359 * v); - - return mag * sin(2.0 * 3.14159265359 * v) + mean; -} - -int nmCalculationAutoFitGA::getEnabledParameterCount() const -{ - int count = 0; - - for(int i = 0; i < m_parameterSelected.size(); ++i) { - if(m_parameterSelected[i]) count++; - } - - return count; -} - -void nmCalculationAutoFitGA::saveOptimizationResult() -{ - DEBUG_OUT(QString("GA optimization result: fitness=%1, evaluations=%2/%3") - .arg(m_bestFitness, 0, 'e', 4) - .arg(m_successfulEvaluations) - .arg(m_totalEvaluations)); -} diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp index a4876ed..4d3b6d8 100644 --- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp +++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp @@ -12,25 +12,24 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_cfSelected = false; // 默认不选中 m_swiSelected = false; // 默认不选中 - // 初始化参数最大值 - m_permeabilityMax = nmDataAttribute("Permeability Max", 10.0, "Darcy"); // 1000 mD - m_skinMax = nmDataAttribute("Skin Max", 10.0, ""); // 100 - m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", 2.0, "m^3/MPa"); - m_porosityMax = nmDataAttribute("Porosity Max", 0.5, ""); // 50% - m_thicknessMax = nmDataAttribute("Thickness Max", 50.0, "m"); - m_ctMax = nmDataAttribute("Ct Max", 0.1, ""); // 1/MPa - m_cfMax = nmDataAttribute("Cf Max", 0.01, ""); // 1/MPa - m_swiMax = nmDataAttribute("Swi Max", 1.0, ""); - - // 初始化参数最小值 - m_permeabilityMin = nmDataAttribute("Permeability Min", 0.001, "Darcy"); // 0.001 mD - m_skinMin = nmDataAttribute("Skin Min", -10.0, ""); // 允许负表皮 - m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", 1e-4, "m^3/MPa"); - m_porosityMin = nmDataAttribute("Porosity Min", 0.01, ""); // 1% - m_thicknessMin = nmDataAttribute("Thickness Min", 2.0, "m"); - m_ctMin = nmDataAttribute("Ct Min", 1e-3, ""); // 小正值 - m_cfMin = nmDataAttribute("Cf Min", 1e-5, ""); // 小正值 - m_swiMin = nmDataAttribute("Swi Min", 0.0, ""); + // 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。 + m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy"); + m_skinMax = nmDataAttribute("Skin Max", QVariant(), ""); + m_wellboreStorageMax = nmDataAttribute("Wellbore Storage Max", QVariant(), "m^3/MPa"); + m_porosityMax = nmDataAttribute("Porosity Max", QVariant(), ""); + m_thicknessMax = nmDataAttribute("Thickness Max", QVariant(), "m"); + m_ctMax = nmDataAttribute("Ct Max", QVariant(), ""); + m_cfMax = nmDataAttribute("Cf Max", QVariant(), ""); + m_swiMax = nmDataAttribute("Swi Max", QVariant(), ""); + + m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy"); + m_skinMin = nmDataAttribute("Skin Min", QVariant(), ""); + m_wellboreStorageMin = nmDataAttribute("Wellbore Storage Min", QVariant(), "m^3/MPa"); + m_porosityMin = nmDataAttribute("Porosity Min", QVariant(), ""); + m_thicknessMin = nmDataAttribute("Thickness Min", QVariant(), "m"); + m_ctMin = nmDataAttribute("Ct Min", QVariant(), ""); + m_cfMin = nmDataAttribute("Cf Min", QVariant(), ""); + m_swiMin = nmDataAttribute("Swi Min", QVariant(), ""); // 初始化迭代参数 m_iterationCount = nmDataAttribute("Iteration Count", 20, ""); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 202d14c..f0f243e 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -4,6 +4,9 @@ #include "nmWxParameterProperty.h" #include "nmDataAnalyzeManager.h" #include "iSubWndFitting.h" +#include "iSysParaHelper.h" +#include "iParameter.h" +#include "mModuleDefines.h" #include "mGui/mGuiAnal/iAnalRun.h" #include "iGuiPlot.h" #include "ZxObjCurve.h" @@ -32,6 +35,30 @@ bool nmAutoFitUiIsFinite(double value) #endif } +// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。 +bool nmAutoFitReadPhysicalRange(const char* parameterName, + double fallbackMin, double fallbackMax, double& minValue, double& maxValue) +{ + minValue = fallbackMin; + maxValue = fallbackMax; + + iSysParaHelper* paraHelper = _paraHelper; + if(!paraHelper) { + return false; + } + + iParameter* parameter = paraHelper->getPara(QString::fromLatin1(parameterName), s_Nm_Serie); + if(!parameter || !nmAutoFitUiIsFinite(parameter->m_dMin) + || !nmAutoFitUiIsFinite(parameter->m_dMax) + || parameter->m_dMin >= parameter->m_dMax) { + return false; + } + + minValue = parameter->m_dMin; + maxValue = parameter->m_dMax; + return true; +} + // 主界面气井双对数中的源曲线和导数曲线已经采用拟压力量纲. // 自动拟合直接读取这两条显示曲线, 避免井对象中的原始压力历史数据与结果曲线量纲不一致. // 两条曲线可能因无效点过滤而长度不同, 因此按时间坐标匹配共同有效点. @@ -199,14 +226,338 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL renumberVisibleParameterRows(table); } +// 获取参数的系统物理边界,并为 Swi 叠加当前储层饱和度约束。 +bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex, + double& minValue, double& maxValue) +{ + static const char* parameterNames[] = { + "Result_K", "Result_W_Skin", "Result_W_C", "Result_phi", + "Result_h", "Result_Cti", "Result_Cf", "Result_Swi" + }; + // KAPPA 的边界使用 md、ft、bbl/psi;自动拟合界面使用 Darcy、m、m^3/MPa, + // 这里统一换算到界面和 PSO 实际使用的单位:K 除以 1000,h 由 ft 换成 m, + // 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。 + // Ct/Cf/Swi 沿用模型参数表边界。 + static const double physicalMin[] = { + 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0 + }; + static const double physicalMax[] = { + 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0 + }; + + if(parameterIndex < 0 || parameterIndex >= 8) { + return false; + } + + minValue = physicalMin[parameterIndex]; + maxValue = physicalMax[parameterIndex]; + bool rangeRead = true; + if(parameterIndex >= 5) { + rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], + physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue); + } + + if(parameterIndex == 7) { + double soi = reservoirData.getSoi().getValue().toDouble(); + double sgi = reservoirData.getSgi().getValue().toDouble(); + if(nmAutoFitUiIsFinite(soi) && nmAutoFitUiIsFinite(sgi)) { + if(soi < 0.0 || sgi < 0.0 || soi + sgi > 1.0) { + // Soi+Sgi 超过 1 时没有可行的 Swi,固定到物理下限,避免继续搜索非法区间。 + minValue = 0.0; + maxValue = 0.0; + } else { + maxValue = qMin(maxValue, 1.0 - soi - sgi); + } + } + } + + return rangeRead; +} + +// 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。 +void nmWxAutomaticFitting::setParameterRange(int parameterIndex, + double minValue, double maxValue) +{ + if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= m_parameterTable->rowCount() + || !nmAutoFitUiIsFinite(minValue) || !nmAutoFitUiIsFinite(maxValue)) { + return; + } + + if(maxValue < minValue) { + qSwap(minValue, maxValue); + } + + const bool wasUpdatingRanges = m_updatingParameterRanges; + m_updatingParameterRanges = true; + + if(m_parameterTable->item(parameterIndex, 2)) { + m_parameterTable->item(parameterIndex, 2)->setText(QString::number(minValue, 'g', 10)); + } + if(m_parameterTable->item(parameterIndex, 4)) { + m_parameterTable->item(parameterIndex, 4)->setText(QString::number(maxValue, 'g', 10)); + } + + switch(parameterIndex) { + case 0: + automaticFittingData.getPermeabilityMin().setValue(minValue); + automaticFittingData.getPermeabilityMax().setValue(maxValue); + break; + case 1: + automaticFittingData.getSkinMin().setValue(minValue); + automaticFittingData.getSkinMax().setValue(maxValue); + break; + case 2: + automaticFittingData.getWellboreStorageMin().setValue(minValue); + automaticFittingData.getWellboreStorageMax().setValue(maxValue); + break; + case 3: + automaticFittingData.getPorosityMin().setValue(minValue); + automaticFittingData.getPorosityMax().setValue(maxValue); + break; + case 4: + automaticFittingData.getThicknessMin().setValue(minValue); + automaticFittingData.getThicknessMax().setValue(maxValue); + break; + case 5: + automaticFittingData.getCtMin().setValue(minValue); + automaticFittingData.getCtMax().setValue(maxValue); + break; + case 6: + automaticFittingData.getCfMin().setValue(minValue); + automaticFittingData.getCfMax().setValue(maxValue); + break; + case 7: + automaticFittingData.getSwiMin().setValue(minValue); + automaticFittingData.getSwiMax().setValue(maxValue); + break; + default: + break; + } + + m_updatingParameterRanges = wasUpdatingRanges; +} + +// 根据初值生成首次或拟合后的建议范围,并始终限制在物理边界内。 +void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, + double centerValue, bool afterFit) +{ + if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8 + || !nmAutoFitUiIsFinite(centerValue)) { + return; + } + + double physicalMin = 0.0; + double physicalMax = 0.0; + getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); + + double reference = centerValue; + const bool positiveParameter = parameterIndex != 1; + + // 正值参数初值为 0 时不再借用旧的界面范围,直接从物理边界选取搜索尺度。 + if(positiveParameter && reference <= 0.0) { + if(physicalMin > 0.0) { + reference = physicalMin; + } else { + reference = qMax(physicalMax * 0.01, 1.0e-12); + } + } + + if(physicalMax < physicalMin) { + return; + } + reference = qBound(physicalMin, reference, physicalMax); + const double boundedCenterValue = qBound(physicalMin, centerValue, physicalMax); + + double newMin = physicalMin; + double newMax = physicalMax; + if(parameterIndex == 1) { + const double skinHalfRange = afterFit ? 1.0 : 10.0; + newMin = qMax(physicalMin, reference - skinHalfRange); + newMax = qMin(physicalMax, reference + skinHalfRange); + } else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) { + const double lowerFactor = afterFit ? 0.5 : 0.1; + const double upperFactor = afterFit ? 2.0 : 10.0; + newMin = qMax(physicalMin, reference * lowerFactor); + newMax = qMin(physicalMax, reference * upperFactor); + } else if(parameterIndex == 7) { + // 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。 + newMin = physicalMin; + newMax = physicalMax; + } + + // 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。 + newMin = qMin(newMin, boundedCenterValue); + newMax = qMax(newMax, boundedCenterValue); + newMin = qMax(newMin, physicalMin); + newMax = qMin(newMax, physicalMax); + + if(newMax >= newMin) { + setParameterRange(parameterIndex, newMin, newMax); + } +} + +// 首次进入自动范围模式时,按当前表格中的初值为所有参数建立建议范围。 +void nmWxAutomaticFitting::initializeSuggestedParameterRanges() +{ + if(!m_parameterTable) { + return; + } + + for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { + QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); + if(initialItem) { + bool initialOk = false; + const double initialValue = initialItem->text().toDouble(&initialOk); + if(initialOk && nmAutoFitUiIsFinite(initialValue)) { + updateRangeForParameter(parameterIndex, initialValue, false); + } else { + // 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。 + double physicalMin = 0.0; + double physicalMax = 0.0; + getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); + if(physicalMax >= physicalMin) { + setParameterRange(parameterIndex, physicalMin, physicalMax); + } + } + } + } +} + +// 校正已保存的范围:保留物理边界内的用户区间,无交集时按当前初值生成兜底区间。 +void nmWxAutomaticFitting::normalizeSavedParameterRanges() +{ + if(!m_parameterTable) { + return; + } + + for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { + QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2); + QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4); + QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); + if(!minItem || !maxItem || !initialItem) { + continue; + } + + double physicalMin = 0.0; + double physicalMax = 0.0; + getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); + bool savedMinOk = false; + bool savedMaxOk = false; + const double savedMin = minItem->text().toDouble(&savedMinOk); + const double savedMax = maxItem->text().toDouble(&savedMaxOk); + const bool savedRangeValid = savedMinOk && savedMaxOk + && nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax) + && savedMax >= savedMin; + + if(savedRangeValid && physicalMax >= physicalMin) { + const double clippedMin = qMax(savedMin, physicalMin); + const double clippedMax = qMin(savedMax, physicalMax); + if(clippedMax >= clippedMin) { + setParameterRange(parameterIndex, clippedMin, clippedMax); + continue; + } + } + + // 已保存范围无效或与物理边界无交集时,按数据对象初值重新生成。 + bool initialOk = false; + const double initialValue = initialItem->text().toDouble(&initialOk); + if(initialOk && nmAutoFitUiIsFinite(initialValue)) { + updateRangeForParameter(parameterIndex, initialValue, false); + } else if(physicalMax >= physicalMin) { + setParameterRange(parameterIndex, physicalMin, physicalMax); + } + } +} + +// 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。 +bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex) +{ + if(!m_parameterTable) { + errorMessage = tr("The parameter table is unavailable."); + return false; + } + if(parameterIndex < -1 || parameterIndex >= 8) { + errorMessage = tr("The parameter row is invalid."); + return false; + } + + static const char* parameterNames[] = { + "Permeability", "Skin", "Wellbore storage", "Porosity", + "Thickness", "Ct", "Cf", "Swi" + }; + + const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex; + const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1; + for(int currentParameterIndex = firstParameterIndex; + currentParameterIndex < lastParameterIndex; ++currentParameterIndex) { + // 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。 + if(m_parameterTable->isRowHidden(currentParameterIndex)) { + continue; + } + + QTableWidgetItem* minItem = m_parameterTable->item(currentParameterIndex, 2); + QTableWidgetItem* initialItem = m_parameterTable->item(currentParameterIndex, 3); + QTableWidgetItem* maxItem = m_parameterTable->item(currentParameterIndex, 4); + if(!minItem || !initialItem || !maxItem) { + errorMessage = tr("The range values for %1 are incomplete.") + .arg(tr(parameterNames[currentParameterIndex])); + return false; + } + + bool minOk = false; + bool initialOk = false; + bool maxOk = false; + const double minValue = minItem->text().toDouble(&minOk); + const double initialValue = initialItem->text().toDouble(&initialOk); + const double maxValue = maxItem->text().toDouble(&maxOk); + if(!minOk || !initialOk || !maxOk + || !nmAutoFitUiIsFinite(minValue) + || !nmAutoFitUiIsFinite(initialValue) + || !nmAutoFitUiIsFinite(maxValue)) { + errorMessage = tr("The minimum value, initial value, and maximum value of %1 must be finite numbers.") + .arg(tr(parameterNames[currentParameterIndex])); + return false; + } + + double physicalMin = 0.0; + double physicalMax = 0.0; + getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax); + if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax) + || physicalMax < physicalMin) { + errorMessage = tr("The physical range of %1 is invalid.") + .arg(tr(parameterNames[currentParameterIndex])); + return false; + } + + if(minValue < physicalMin || minValue > physicalMax + || initialValue < physicalMin || initialValue > physicalMax + || maxValue < physicalMin || maxValue > physicalMax) { + errorMessage = tr("The values of %1 exceed the physical range [%2, %3].") + .arg(tr(parameterNames[currentParameterIndex])) + .arg(QString::number(physicalMin, 'g', 10)) + .arg(QString::number(physicalMax, 'g', 10)); + return false; + } + + if(minValue > maxValue || minValue > initialValue || initialValue > maxValue) { + errorMessage = tr("The values of %1 must satisfy: minimum <= initial value <= maximum.") + .arg(tr(parameterNames[currentParameterIndex])); + return false; + } + } + + return true; +} + nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) : iDlgBase(parent) , m_autoFitterPSO(nullptr) - , m_autoFitterGA(nullptr) , m_progressDialog(nullptr) , m_progressTimer(nullptr) , m_progressMonitor(nullptr) - , m_selectedAlgorithm(ALGORITHM_PSO) // 默认选择PSO算法 + , m_autoParameterRanges(true) + // 构造期间先禁止即时校验,避免初始值写入和范围生成之间出现短暂的不一致。 + , m_updatingParameterRanges(true) { DEBUG_UI(QString("AutoFitting Constructor: this=0x%1").arg((quintptr)this, 0, 16)); @@ -233,32 +584,13 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); reservoirData = pManager->getReservoirDataCopy(); automaticFittingData = pManager->getAutomaticFittingDataCopy(); + const bool hasSavedFittingData = pManager && pManager->getAutomaticFittingData() != nullptr; + // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 + m_autoParameterRanges = true; NM_SOLVER_MODEL_TYPE solverModelType = pManager->getSolverModelType(); - // 只有尚未点击确定保存过配置时才使用相态默认值;重新打开时保留已保存的数据。 - if(pManager->getAutomaticFittingData() == nullptr) { - bool isOilOrWaterSinglePhase = solverModelType == SMT_Oil_ConstPvt || - solverModelType == SMT_Oil_VariablePvt || - solverModelType == SMT_Water_ConstPvt || - solverModelType == SMT_Water_VariablePvt; - if(isOilOrWaterSinglePhase) { - reservoirData.getPermeability().setValue(2.5e-2); - automaticFittingData.getPermeabilityMin().setValue(1.0e-3); - automaticFittingData.getPermeabilityMax().setValue(10.0); - automaticFittingData.getSkinMin().setValue(-10.0); - automaticFittingData.getSkinMax().setValue(10.0); - automaticFittingData.getWellboreStorageMin().setValue(1.0e-4); - automaticFittingData.getWellboreStorageMax().setValue(2.0); - automaticFittingData.getPorosityMin().setValue(1.0e-2); - automaticFittingData.getPorosityMax().setValue(5.0e-1); - automaticFittingData.getThicknessMin().setValue(2.0); - automaticFittingData.getThicknessMax().setValue(50.0); - automaticFittingData.getCtMin().setValue(1.0e-3); - automaticFittingData.getCtMax().setValue(1.0e-1); - automaticFittingData.getCfMin().setValue(1.0e-5); - automaticFittingData.getCfMax().setValue(1.0e-2); - } - + // 未保存过配置时只保留参数选择的相态默认值,不再覆盖数据对象中的初值或范围。 + if(!hasSavedFittingData) { if(solverModelType == SMT_Oil_ConstPvt || solverModelType == SMT_Water_ConstPvt) { // T1/T3 的综合压缩系数默认不参与拟合。 @@ -268,43 +600,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) // T2/T4 的岩石压缩系数默认参与拟合。 automaticFittingData.setCfSelected(true); } - - // 气单相变化 PVT 使用 T5 数据集对应的初始值和拟合范围。 - if(solverModelType == SMT_Gas_VariablePvt) { - reservoirData.getPermeability().setValue(5.5e-6); - reservoirData.getPorosity().setValue(3.5e-2); - reservoirData.getThickness().setValue(8.0); - reservoirData.getCf().setValue(3.0e-4); - - automaticFittingData.getPermeabilityMin().setValue(4.5e-6); - automaticFittingData.getPermeabilityMax().setValue(6.2e-6); - automaticFittingData.getSkinMin().setValue(0.0); - automaticFittingData.getSkinMax().setValue(4.5); - automaticFittingData.getWellboreStorageMin().setValue(5.0e-5); - automaticFittingData.getWellboreStorageMax().setValue(1.6e-4); - automaticFittingData.getPorosityMin().setValue(8.0e-3); - automaticFittingData.getPorosityMax().setValue(4.0e-2); - automaticFittingData.getThicknessMin().setValue(7.0); - automaticFittingData.getThicknessMax().setValue(11.0); - automaticFittingData.getCfMin().setValue(1.0e-4); - automaticFittingData.getCfMax().setValue(2.0e-3); - } else if(solverModelType == SMT_Oil_ConstPvt || - solverModelType == SMT_Water_ConstPvt) { - // T1/T3 使用固定初值,不继承当前储层参数。 - reservoirData.getPorosity().setValue(2.45e-2); - reservoirData.getThickness().setValue(9.144); - reservoirData.getCt().setValue(1.0e-2); - } else if(solverModelType == SMT_Oil_VariablePvt) { - // 油单相变化 PVT 使用固定初值。 - reservoirData.getPorosity().setValue(6.0e-2); - reservoirData.getThickness().setValue(10.5); - reservoirData.getCf().setValue(1.0e-3); - } else if(solverModelType == SMT_Water_VariablePvt) { - // 水单相变化 PVT 使用固定初值。 - reservoirData.getPorosity().setValue(6.0e-2); - reservoirData.getThickness().setValue(10.5); - reservoirData.getCf().setValue(1.5e-3); - } } setupUI(); @@ -315,6 +610,12 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) if(m_targetWellCombo->count() > 0) { onWellSelected(0); // 默认选中第一口井 } + if(!hasSavedFittingData) { + initializeSuggestedParameterRanges(); + } else { + normalizeSavedParameterRanges(); + } + m_updatingParameterRanges = false; DEBUG_UI("AutoFitting Constructor completed"); } @@ -333,9 +634,6 @@ nmWxAutomaticFitting::~nmWxAutomaticFitting() if (m_autoFitterPSO) { disconnect(m_autoFitterPSO, nullptr, this, nullptr); } - if (m_autoFitterGA) { - disconnect(m_autoFitterGA, nullptr, this, nullptr); - } DEBUG_UI("AutoFitting destructor - completed"); } @@ -514,6 +812,8 @@ void nmWxAutomaticFitting::setupParameterTable() // 连接选择改变信号 connect(m_parameterTable, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(onCellSelectionChanged(int, int, int, int))); + connect(m_parameterTable, SIGNAL(itemChanged(QTableWidgetItem*)), + this, SLOT(onParameterTableItemChanged(QTableWidgetItem*))); } void nmWxAutomaticFitting::setupControlPanel() @@ -528,9 +828,7 @@ void nmWxAutomaticFitting::setupControlPanel() QLabel* algorithmLabel = new QLabel(tr("Algorithm:")); m_algorithmCombo = new QComboBox(); m_algorithmCombo->addItem(tr("PSO (Particle Swarm)")); - m_algorithmCombo->addItem(tr("GA (Genetic Algorithm)")); - m_algorithmCombo->setCurrentIndex(0); // 默认选择PSO - connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int))); + m_algorithmCombo->setCurrentIndex(0); m_algorithmCombo->setMaximumWidth(160); m_algorithmCombo->setMinimumWidth(160); @@ -681,35 +979,37 @@ void nmWxAutomaticFitting::onReverseSelection() if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked()); } -void nmWxAutomaticFitting::onAlgorithmChanged(int index) +void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) { - m_selectedAlgorithm = static_cast(index); - - // 根据算法类型调整界面提示或参数 - QString algorithmInfo; - switch(m_selectedAlgorithm) { - case ALGORITHM_PSO: - algorithmInfo = tr("PSO algorithm selected."); - if(m_surrogateCombo) { - m_surrogateCombo->setEnabled(true); - } - break; - case ALGORITHM_GA: - algorithmInfo = tr("GA algorithm selected."); - if(m_surrogateCombo) { - m_surrogateCombo->setEnabled(false); - } - break; + if(!item || m_updatingParameterRanges) { + return; } - // 在状态栏或工具提示中显示算法信息 - m_algorithmCombo->setToolTip(algorithmInfo); + // 用户改动范围后,切井和拟合结果不再自动覆盖这组手工范围。 + if(item->column() == 2 || item->column() == 4) { + m_autoParameterRanges = false; + } + + if(item->column() >= 2 && item->column() <= 4 + && !m_parameterTable->isRowHidden(item->row())) { + QString validationError; + if(!validateParameterTable(validationError, item->row())) { + // 表格编辑提交后立即提示,用户不需要先点击“确定”才发现错误。 + QMessageBox::warning(this, tr("Invalid parameter range"), validationError); + } + } } void nmWxAutomaticFitting::onAccept() { - // 首先保存参数设置 - setAutomaticFittingValue(); + QString validationError; + if(!validateParameterTable(validationError)) { + QMessageBox::warning(this, tr("Invalid parameter range"), validationError); + return; + } + + // 校验通过后再保存参数设置,非法输入不会被静默裁剪。 + setAutomaticFittingValue(); // 更新完成后,通知参数界面刷新 nmWxParameterProperty::notifyUpdateTable(); @@ -878,28 +1178,6 @@ void nmWxAutomaticFitting::onWellSelected(int index) } } - // 尚未保存拟合配置时使用相态默认值;已保存时保留井上的 Skin 和井筒储集系数。 - nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); - bool useDefaultFittingValues = pManager && pManager->getAutomaticFittingData() == nullptr; - if(useDefaultFittingValues && (pManager->getSolverModelType() == SMT_Oil_ConstPvt || - pManager->getSolverModelType() == SMT_Water_ConstPvt)) { - skinValue = 0.0; - wellboreStorageValue = 1.0e-2; - found = true; - } else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Gas_VariablePvt) { - skinValue = 1.0; - wellboreStorageValue = 6.0e-5; - found = true; - } else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Oil_VariablePvt) { - skinValue = -0.2; - wellboreStorageValue = 1.5e-2; - found = true; - } else if(useDefaultFittingValues && pManager->getSolverModelType() == SMT_Water_VariablePvt) { - skinValue = -0.2; - wellboreStorageValue = 1.0e-2; - found = true; - } - if (found) { // 更新表格数据 // 确保表格项存在 @@ -915,6 +1193,11 @@ void nmWxAutomaticFitting::onWellSelected(int index) // 设置井筒储集系数(Wellbore storage) m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue)); + + if(m_autoParameterRanges) { + updateRangeForParameter(1, skinValue, false); + updateRangeForParameter(2, wellboreStorageValue, false); + } } } @@ -1041,11 +1324,10 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector>& targ // 先清理之前的实例 cleanupFitting(); - if (m_selectedAlgorithm == ALGORITHM_PSO) { - DEBUG_UI("Creating PSO auto fitter"); - m_autoFitterPSO = new nmCalculationAutoFitPSO(this); - m_autoFitterPSO->setTargetLogLogData(targetData); - m_autoFitterPSO->setPSOTargetWellName(targetWellName); + DEBUG_UI("Creating PSO auto fitter"); + m_autoFitterPSO = new nmCalculationAutoFitPSO(this); + m_autoFitterPSO->setTargetLogLogData(targetData); + m_autoFitterPSO->setPSOTargetWellName(targetWellName); //// 特定井名时使用快速路径 //if (targetWellName == "VerticalWell1") { @@ -1080,46 +1362,23 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector>& targ //} - m_progressMonitor = new nmWxAutomaticfittingStart(this); - m_progressMonitor->setAutoFitter(m_autoFitterPSO); - m_progressMonitor->setPseudoPressureMode( - nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt); - m_progressMonitor->setTargetLogLogData(targetData); - - connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)), - this, SLOT(onFittingFinished(bool, QString))); - - int maxIterations = m_iterationEdit->text().toInt(); - double targetError = m_errorLimitEdit->text().toDouble(); - QString wellName = m_targetWellCombo->currentText(); - m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName); - m_progressMonitor->setSelectedParameters(selectedParams); - - m_progressMonitor->show(); - QTimer::singleShot(100, this, SLOT(runAutoFitting())); + m_progressMonitor = new nmWxAutomaticfittingStart(this); + m_progressMonitor->setAutoFitter(m_autoFitterPSO); + m_progressMonitor->setPseudoPressureMode( + nmDataAnalyzeManager::getCurrentInstance()->getSolverModelType() == SMT_Gas_VariablePvt); + m_progressMonitor->setTargetLogLogData(targetData); - } else if (m_selectedAlgorithm == ALGORITHM_GA) { - DEBUG_UI("Creating GA auto fitter"); - m_autoFitterGA = new nmCalculationAutoFitGA(this); - m_autoFitterGA->setTargetLogLogData(targetData); - m_autoFitterGA->setGATargetWellName(targetWellName); + connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)), + this, SLOT(onFittingFinished(bool, QString))); - m_progressMonitor = new nmWxAutomaticfittingStart(this); - m_progressMonitor->setAutoFitterGA(m_autoFitterGA); - m_progressMonitor->setTargetLogLogData(targetData); + int maxIterations = m_iterationEdit->text().toInt(); + double targetError = m_errorLimitEdit->text().toDouble(); + QString wellName = m_targetWellCombo->currentText(); + m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName); + m_progressMonitor->setSelectedParameters(selectedParams); - connect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)), - this, SLOT(onFittingFinished(bool, QString))); - - int maxIterations = m_iterationEdit->text().toInt(); - double targetError = m_errorLimitEdit->text().toDouble(); - QString wellName = m_targetWellCombo->currentText(); - m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName); - m_progressMonitor->setSelectedParameters(selectedParams); - - m_progressMonitor->show(); - QTimer::singleShot(100, this, SLOT(runAutoFitting())); - } + m_progressMonitor->show(); + QTimer::singleShot(100, this, SLOT(runAutoFitting())); } void nmWxAutomaticFitting::runAutoFitting() @@ -1128,10 +1387,8 @@ void nmWxAutomaticFitting::runAutoFitting() m_progressMonitor->markFittingStarted(); } - if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) { + if(m_autoFitterPSO) { m_autoFitterPSO->startAutoFitting(); - } else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) { - m_autoFitterGA->startAutoFitting(); } } @@ -1147,18 +1404,13 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag disconnect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)), this, SLOT(onFittingFinished(bool, QString))); } - if (m_autoFitterGA) { - disconnect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)), - this, SLOT(onFittingFinished(bool, QString))); - } - - // 更新最佳参数到表格 - updateBestParametersToTable(); - if(success) { + // 只有成功拟合的结果才用于生成下一轮范围,失败结果不污染当前配置。 + updateBestParametersToTable(); + QString resultInfo; - if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) { + if(m_autoFitterPSO) { double bestFitness = m_autoFitterPSO->getBestFitness(); // 检查是否是用户停止的情况 @@ -1173,23 +1425,6 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag resultInfo += tr("Optimized parameters have been applied to the model."); QMessageBox::information(this, tr("Optimization Completed"), resultInfo); } - - } else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) { - // GA的类似处理 - - double bestFitness = m_autoFitterGA->getBestFitness(); - - if(message.contains("stopped by user", Qt::CaseInsensitive)) { - resultInfo = tr("GA Optimization stopped by user:\n"); - resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4); - resultInfo += tr("Current parameters have been applied to the model."); - QMessageBox::information(this, tr("Optimization Stopped"), resultInfo); - } else { - resultInfo = tr("GA Optimization completed:\n"); - resultInfo += tr("Best Error: %1\n").arg(bestFitness, 0, 'e', 4); - resultInfo += tr("Optimized parameters have been applied to the model."); - QMessageBox::information(this, tr("Optimization Completed"), resultInfo); - } } } else { // 只有真正失败的情况才显示警告 @@ -1199,11 +1434,9 @@ void nmWxAutomaticFitting::onFittingFinished(bool success, const QString& messag void nmWxAutomaticFitting::onStopFitting() { - if(m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO && m_autoFitterPSO->isRunning()) { + if(m_autoFitterPSO && m_autoFitterPSO->isRunning()) { m_autoFitterPSO->stopFitting(); - } else if(m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA && m_autoFitterGA->isRunning()) { - m_autoFitterGA->stopFitting(); - } + } } void nmWxAutomaticFitting::cleanupFitting() @@ -1246,31 +1479,6 @@ void nmWxAutomaticFitting::cleanupFitting() DEBUG_UI("PSO fitter cleaned up"); } - if (m_autoFitterGA) { - DEBUG_UI("Stopping and disconnecting GA fitter"); - - disconnect(m_autoFitterGA, nullptr, nullptr, nullptr); - - if (m_autoFitterGA->isRunning()) { - m_autoFitterGA->stopFitting(); - - int waitCount = 0; - while (m_autoFitterGA->isRunning() && waitCount < 50) { - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 100); - waitCount++; - } - - // 如果仍在运行则强制停止 - if (m_autoFitterGA->isRunning()) { - DEBUG_UI("Force stopping GA - timeout reached"); - } - } - - delete m_autoFitterGA; - m_autoFitterGA = nullptr; - DEBUG_UI("GA fitter cleaned up"); - } - // 清理进度监控 if (m_progressMonitor) { // 先断开进度监控的信号连接 @@ -1296,10 +1504,8 @@ void nmWxAutomaticFitting::updateBestParametersToTable() QVector bestSolution; // 获取最佳解决方案 - if (m_selectedAlgorithm == ALGORITHM_PSO && m_autoFitterPSO) { + if (m_autoFitterPSO) { bestSolution = m_autoFitterPSO->getBestSolution(); - } else if (m_selectedAlgorithm == ALGORITHM_GA && m_autoFitterGA) { - bestSolution = m_autoFitterGA->getBestSolution(); } if (bestSolution.isEmpty()) return; @@ -1315,78 +1521,19 @@ void nmWxAutomaticFitting::updateBestParametersToTable() if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数 if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度 - // 范围收缩比例 - double shrinkFactor = 0.3; - // 更新参数值和范围 for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) { int paramIndex = enabledParams[i]; double bestValue = bestSolution[i]; + if(!nmAutoFitUiIsFinite(bestValue)) { + continue; + } // 更新初始值 m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4)); - - // 获取当前的最小值和最大值 - double currentMin = m_parameterTable->item(paramIndex, 2)->text().toDouble(); - double currentMax = m_parameterTable->item(paramIndex, 4)->text().toDouble(); - double currentRange = currentMax - currentMin; - - // 计算新的范围 - double newHalfRange = currentRange * shrinkFactor * 0.5; - double newMin = bestValue - newHalfRange; - double newMax = bestValue + newHalfRange; - - // 确保某些参数不为负数 - if (paramIndex == 0 || paramIndex == 2 || paramIndex == 3 || paramIndex == 4) { - newMin = qMax(newMin, 0.0); - } - - // 确保最小范围,避免范围过小 - double minAllowedRange = currentRange * 0.05; - if ((newMax - newMin) < minAllowedRange) { - double center = (newMax + newMin) * 0.5; - newMin = center - minAllowedRange * 0.5; - newMax = center + minAllowedRange * 0.5; - } - - // 更新表格中的最小值和最大值 - m_parameterTable->item(paramIndex, 2)->setText(QString::number(newMin, 'g', 4)); - m_parameterTable->item(paramIndex, 4)->setText(QString::number(newMax, 'g', 4)); - - // 保存收缩后的范围 - switch(paramIndex) { - case 0: // 渗透率 - automaticFittingData.getPermeabilityMin().setValue(newMin); - automaticFittingData.getPermeabilityMax().setValue(newMax); - break; - case 1: // 表皮系数 - automaticFittingData.getSkinMin().setValue(newMin); - automaticFittingData.getSkinMax().setValue(newMax); - break; - case 2: // 井筒储集系数 - automaticFittingData.getWellboreStorageMin().setValue(newMin); - automaticFittingData.getWellboreStorageMax().setValue(newMax); - break; - case 3: // 孔隙度 - automaticFittingData.getPorosityMin().setValue(newMin); - automaticFittingData.getPorosityMax().setValue(newMax); - break; - case 4: // 储层厚度 - automaticFittingData.getThicknessMin().setValue(newMin); - automaticFittingData.getThicknessMax().setValue(newMax); - break; - case 5: // 综合压缩系数 - automaticFittingData.getCtMin().setValue(newMin); - automaticFittingData.getCtMax().setValue(newMax); - break; - case 6: // 岩石压缩系数 - automaticFittingData.getCfMin().setValue(newMin); - automaticFittingData.getCfMax().setValue(newMax); - break; - case 7: // 初始含水饱和度 - automaticFittingData.getSwiMin().setValue(newMin); - automaticFittingData.getSwiMax().setValue(newMax); - break; + // 只有自动范围模式才根据拟合结果收窄下一轮搜索区间;手工范围由用户保留。 + if(m_autoParameterRanges) { + updateRangeForParameter(paramIndex, bestValue, true); } } diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp index 6e0bd2b..acb1a3f 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp @@ -383,8 +383,6 @@ nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent) , chartGroup(nullptr) , curveChart(nullptr) , m_autoFitterPSO(nullptr) - , m_autoFitterGA(nullptr) - , m_algorithmType(FITTING_ALGORITHM_PSO) , m_maxIterations(100) , m_targetError(0.001) , m_wellName("") @@ -580,8 +578,6 @@ void nmWxAutomaticfittingStart::setupControlArea() void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitter) { m_autoFitterPSO = autoFitter; - m_autoFitterGA = nullptr; // 清空GA实例 - m_algorithmType = FITTING_ALGORITHM_PSO; // 更新算法类型显示 algorithmTypeValue->setText("PSO"); @@ -604,31 +600,6 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitte } } -void nmWxAutomaticfittingStart::setAutoFitterGA(nmCalculationAutoFitGA* autoFitter) -{ - m_autoFitterGA = autoFitter; - m_autoFitterPSO = nullptr; // 清空PSO实例 - m_algorithmType = FITTING_ALGORITHM_GA; - - // 更新算法类型显示 - algorithmTypeValue->setText("GA"); - algorithmTypeValue->setStyleSheet("QLabel { color: red; font-weight: bold; }"); - - if (m_autoFitterGA) { - connect(m_autoFitterGA, SIGNAL(progressUpdated(int, double)), - this, SLOT(onFittingProgress(int, double))); - connect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)), - this, SLOT(onFittingFinished(bool, QString))); - connect(m_autoFitterGA, SIGNAL(logMessageGenerated(QString)), - this, SLOT(onLogMessageReceived(QString))); - - // 启用停止按钮 - stopButton->setEnabled(true); - - addLogMessage(tr("GA auto fitting started")); - } -} - void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double targetError, const QString& wellName) { m_maxIterations = maxIterations; @@ -641,7 +612,7 @@ void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double t progressBar->setRange(0, maxIterations); - QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA"; + const QString algorithmName = "PSO"; addLogMessage(tr("%1 fitting parameters set: MaxIterations=%2, TargetAccuracy=%3, TargetWell=%4") .arg(algorithmName).arg(maxIterations).arg(formatScientific(targetError)).arg(wellName)); } @@ -650,7 +621,7 @@ void nmWxAutomaticfittingStart::markFittingStarted() { m_startTime = QDateTime::currentDateTime(); - QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA"; + const QString algorithmName = "PSO"; QString timestamp = m_startTime.toString("yyyy-MM-dd hh:mm:ss"); addLogMessage(tr("=== %1 Fitting Session Started at %2 ===") @@ -665,7 +636,7 @@ void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& paramet // 立即更新参数表格 updateParameterTable(); - QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA"; + const QString algorithmName = "PSO"; addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", "))); } @@ -716,7 +687,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m { m_isFinished = true; - QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA"; + const QString algorithmName = "PSO"; // 更新状态 if (success) { @@ -800,25 +771,18 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m void nmWxAutomaticfittingStart::onStopButtonClicked() { - bool isRunning = false; - if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) { - isRunning = m_autoFitterPSO->isRunning(); - } else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) { - isRunning = m_autoFitterGA->isRunning(); - } + const bool isRunning = m_autoFitterPSO && m_autoFitterPSO->isRunning(); if (isRunning) { - QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA"; + const QString algorithmName = "PSO"; int ret = QMessageBox::question(this, tr("Confirm Stop"), tr("Are you sure you want to stop the %1 fitting process?").arg(algorithmName), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret == QMessageBox::Yes) { - if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) { + if (m_autoFitterPSO) { m_autoFitterPSO->stopFitting(); - } else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) { - m_autoFitterGA->stopFitting(); } addLogMessage(tr("User requested to stop %1 fitting").arg(algorithmName)); } @@ -840,16 +804,11 @@ void nmWxAutomaticfittingStart::updateParameterTable() // 最优参数值 QString valueText = "N/A"; - if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) { + if (m_autoFitterPSO) { QVector bestSolution = m_autoFitterPSO->getBestSolution(); if (i < bestSolution.size()) { valueText = formatScientific(bestSolution[i]); } - } else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) { - QVector bestSolution = m_autoFitterGA->getBestSolution(); - if (i < bestSolution.size()) { - valueText = formatScientific(bestSolution[i]); - } } QTableWidgetItem* valueItem = new QTableWidgetItem(valueText); @@ -883,16 +842,8 @@ QString nmWxAutomaticfittingStart::formatScientific(double value) void nmWxAutomaticfittingStart::closeEvent(QCloseEvent *event) { - bool isRunning = false; - QString algorithmName; - - if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) { - isRunning = m_autoFitterPSO->isRunning(); - algorithmName = "PSO"; - } else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) { - isRunning = m_autoFitterGA->isRunning(); - algorithmName = "GA"; - } + const bool isRunning = m_autoFitterPSO && m_autoFitterPSO->isRunning(); + const QString algorithmName = "PSO"; if (isRunning && !m_isFinished) { int ret = QMessageBox::question(this, tr("Confirm Close"), @@ -901,10 +852,8 @@ void nmWxAutomaticfittingStart::closeEvent(QCloseEvent *event) QMessageBox::No); if (ret == QMessageBox::Yes) { - if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) { + if (m_autoFitterPSO) { m_autoFitterPSO->stopFitting(); - } else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) { - m_autoFitterGA->stopFitting(); } event->accept(); } else { From 6abd1913c7309db2a19489c4461db8d236872340 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 7 Aug 2026 15:37:01 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8D=9F=E5=A4=B1?= =?UTF-8?q?=E5=87=BD=E6=95=B0=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=EF=BC=8C=E5=B7=A6=E5=8F=B3=EF=BC=8C=E5=BD=A2=E7=8A=B6=EF=BC=8C?= =?UTF-8?q?=E6=97=A9=E4=B8=AD=E6=99=9A=E8=AF=AF=E5=B7=AE=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nmCalculation/nmCalculationAutoFitPSO.h | 64 +++ .../nmCalculation/nmCalculationAutoFitPSO.cpp | 514 ++++++++++++++---- 2 files changed, 460 insertions(+), 118 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index 3418b04..e65d1ee 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "nmCalculation_global.h" @@ -19,6 +20,67 @@ class nmDataWellBase; class QTimer; class QProcess; +// 双对数曲线误差分解,供误差诊断和后续参数调整读取。 +// +// 所有 pressure/derivative/shape 数值均是在 log(value) 空间计算的无量纲误差。 +// verticalBias* 保留正负号:正值表示模拟曲线整体高于目标,负值表示整体低于目标。 +// horizontalPhysicalShift 是 log(time) 方向的等效平移量,正值表示模拟曲线相对目标偏右。 +// total 仍是 PSO 当前使用的 fitness(用于粒子比较和收敛判断);其余字段只描述误差 +// 来源,不会在本次改动中直接修改粒子参数,避免损失诊断和参数更新策略互相耦合。 +struct AutoFitObjectiveBreakdown { + // valid/total 是本次评价是否有效及其最终 fitness(用于排序和收敛判断)。 + bool valid; + double total; + // pressureLoss/derivativeLoss 是压力和导数两条曲线的整体误差。 + double pressureLoss; + double derivativeLoss; + // vertical* 描述整体上下偏移;保留 bias 的符号以判断偏高或偏低。 + double verticalBiasPressure; + double verticalBiasDerivative; + double verticalLoss; + // horizontal* 描述等效的对数时间偏移;physicalShift 为正表示模拟曲线相对目标向右 + // (时间延迟),为负表示向左。 + double horizontalShift; + double horizontalPhysicalShift; + double horizontalLoss; + // shapeLoss 是去除整体上下和左右偏移后剩余的曲线形状差异。 + double shapeLoss; + // 早、中、晚分段误差用于定位误差主要出现在哪个时间阶段。 + double pressureEarlyLoss; + double pressureMiddleLoss; + double pressureLateLoss; + double derivativeEarlyLoss; + double derivativeMiddleLoss; + double derivativeLateLoss; + // coverage 是 50 点目标网格上的 min(有效点比例、连续 log-time 跨度比例)。 + // coveragePenalty 是归一化覆盖缺口的平方惩罚,并以 0.1 权重加入 total。 + double coverage; + double coveragePenalty; + + // 无效评价使用 1e10 作为统一的“差解”标记;其他字段用 NaN 表示尚未得到诊断值。 + AutoFitObjectiveBreakdown() + : valid(false) + , total(1.0e10) + , pressureLoss(std::numeric_limits::quiet_NaN()) + , derivativeLoss(std::numeric_limits::quiet_NaN()) + , verticalBiasPressure(std::numeric_limits::quiet_NaN()) + , verticalBiasDerivative(std::numeric_limits::quiet_NaN()) + , verticalLoss(std::numeric_limits::quiet_NaN()) + , horizontalShift(std::numeric_limits::quiet_NaN()) + , horizontalPhysicalShift(std::numeric_limits::quiet_NaN()) + , horizontalLoss(std::numeric_limits::quiet_NaN()) + , shapeLoss(std::numeric_limits::quiet_NaN()) + , pressureEarlyLoss(std::numeric_limits::quiet_NaN()) + , pressureMiddleLoss(std::numeric_limits::quiet_NaN()) + , pressureLateLoss(std::numeric_limits::quiet_NaN()) + , derivativeEarlyLoss(std::numeric_limits::quiet_NaN()) + , derivativeMiddleLoss(std::numeric_limits::quiet_NaN()) + , derivativeLateLoss(std::numeric_limits::quiet_NaN()) + , coverage(std::numeric_limits::quiet_NaN()) + , coveragePenalty(std::numeric_limits::quiet_NaN()) + {} +}; + // PSO粒子结构 // 这里的 position / velocity / bestPosition 只保存“用户勾选参与拟合的参数”, // 不是完整的 11 个储层/井筒参数。完整参数向量会在写 trace 或调用代理模型时 @@ -96,6 +158,7 @@ public: void stopFitting(); QVector getBestSolution() const; double getBestFitness() const; + AutoFitObjectiveBreakdown getLastObjectiveBreakdown() const; QString getLastError() const; bool isRunning() const; int getCurrentIteration() const; @@ -288,6 +351,7 @@ private: double m_previousBestFitness; // 上一轮全局最优误差,用于自适应参数更新。 QVector > m_lastEvaluatedLogLogData; // 最近一次真实求解得到的 result log-log 曲线。 QVector > m_globalBestLogLogData; // 当前全局最优对应的 result log-log 曲线。 + mutable AutoFitObjectiveBreakdown m_lastObjectiveBreakdown; // 最近一次损失评价的误差分解。 QVector > m_userInitialLogLogData; // 用户初始解对应的 result log-log 曲线,用于精英保护。 // ===== 优化配置 ===== diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 936d9fc..ccc05ea 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -791,6 +791,12 @@ double nmCalculationAutoFitPSO::getBestFitness() const return m_globalBestFitness; } +AutoFitObjectiveBreakdown nmCalculationAutoFitPSO::getLastObjectiveBreakdown() const +{ + // 返回最近一次损失评价的误差分解,供界面或后续优化逻辑读取。 + return m_lastObjectiveBreakdown; +} + QString nmCalculationAutoFitPSO::getLastError() const { // 上一次失败的人类可读错误信息,主要给 UI 层弹窗或日志使用。 @@ -809,6 +815,7 @@ void nmCalculationAutoFitPSO::resetOptimizer() m_previousBestFitness = 1e10; m_lastEvaluatedLogLogData.clear(); m_globalBestLogLogData.clear(); + m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); m_userInitialLogLogData.clear(); m_currentIteration = 0; m_totalEvaluations = 0; @@ -4016,6 +4023,7 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter static int callCount = 0; callCount++; m_lastEvaluatedLogLogData.clear(); + m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); try { DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters") @@ -4989,170 +4997,440 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( const QVector >& target, const QVector >& result) const { - // 双对数曲线误差计算。 - // - // target 通常来自目标井历史曲线,result 来自当前粒子参数下的模拟曲线。 - // 两条曲线的时间点往往不完全一致,所以这里先取两者时间范围的重叠区间, - // 再在公共时间网格上插值对齐,最后分别计算压力曲线和压力导数曲线误差。 - // - // 返回值越小表示拟合越好;返回 1e10 表示曲线无效或无法比较。 - // 验证数据 + // 这里只负责“曲线比较和误差诊断”,不根据诊断结果直接修改任何拟合参数。 + // 调用方可以读取 m_lastObjectiveBreakdown 做诊断或展示;本函数本身不修改参数。 + // 在统一的对数时间网格上计算压力和导数残差,并拆分为上下、左右、形状误差。 + const double invalidLoss = 1.0e10; + const double valueFloor = 1.0e-12; // 导数接近零时的对数下限,避免 log(0)。 + const double huberDelta = qLn(1.2); // 约对应 20% 的相对偏差拐点。 + const double minimumCoverage = 0.95; // 点数比例和连续跨度比例都至少接近 95%。 + const int numPoints = 50; // 固定网格使不同候选的损失具有可比性。 + // 目标函数的主排序项为 0.5*pressureLoss + 0.5*derivativeLoss; + // coveragePenalty 只在接近覆盖边界时提供连续惩罚,上下、左右和形状分量 + // 会写入 m_lastObjectiveBreakdown,供后续按误差类型选择参数。 + m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); + if(!validateLogLogData(target) || !validateLogLogData(result)) { - return 1e10; + return invalidLoss; } try { - // 数据对齐:找到目标曲线与模拟曲线 time 轴的重叠区域。 - // 不在重叠区域内的点不参与误差,避免外推导致误差失真。 - double targetMinX = target[0][0]; - double targetMaxX = target[0][0]; + // 清洗曲线并拆成压力、导数两条曲线。导数可以为负,所以统一使用绝对值 + // 进入双对数空间;时间和压力必须为正,否则无法进行对数插值。这里的清洗 + // 只丢弃无法比较的采样点,不改变原始曲线或求解器输出。 + auto prepareCurve = [valueFloor](const QVector >& data, + QVector* pressure, + QVector* derivative) -> bool { + if(!pressure || !derivative || data.size() < 3 || + data[0].size() != data[1].size() || + data[0].size() != data[2].size()) { + return false; + } - for(int i = 1; i < target[0].size(); ++i) { - if(target[0][i] < targetMinX) targetMinX = target[0][i]; + for(int i = 0; i < data[0].size(); ++i) { + if(!isFiniteNumber(data[0][i]) || !isFiniteNumber(data[1][i]) || + !isFiniteNumber(data[2][i]) || data[0][i] <= 0.0 || + data[1][i] <= 0.0) { + continue; + } - if(target[0][i] > targetMaxX) targetMaxX = target[0][i]; - } + pressure->append(QPointF(data[0][i], data[1][i])); + derivative->append(QPointF(data[0][i], + qMax(qAbs(data[2][i]), valueFloor))); + } - double resultMinX = result[0][0]; - double resultMaxX = result[0][0]; + // 插值要求时间严格递增。重复时间点保留排序后的最后一个值, + // 避免重复横坐标导致对数插值分母为零。压力和导数分别去重, + // 这样即使某条曲线存在重复时间点,也不会污染另一条曲线的插值。 + auto sortAndUnique = [](QVector* curve) { + std::stable_sort(curve->begin(), curve->end(), + [](const QPointF& left, const QPointF& right) { + return left.x() < right.x(); + }); + + QVector unique; + unique.reserve(curve->size()); + + for(int i = 0; i < curve->size(); ++i) { + if(unique.isEmpty() || curve->at(i).x() > unique.last().x()) { + unique.append(curve->at(i)); + } else { + unique[unique.size() - 1] = curve->at(i); + } + } - for(int i = 1; i < result[0].size(); ++i) { - if(result[0][i] < resultMinX) resultMinX = result[0][i]; + *curve = unique; + }; - if(result[0][i] > resultMaxX) resultMaxX = result[0][i]; - } + sortAndUnique(pressure); + sortAndUnique(derivative); + return pressure->size() >= 3 && derivative->size() >= 3; + }; - double overlapMinX = qMax(targetMinX, resultMinX); - double overlapMaxX = qMin(targetMaxX, resultMaxX); + QVector targetPressure; + QVector targetDerivative; + QVector resultPressure; + QVector resultDerivative; - if(overlapMinX >= overlapMaxX) { - DEBUG_OUT("No overlap between target and result LogLog curves"); - return 1e10; + if(!prepareCurve(target, &targetPressure, &targetDerivative) || + !prepareCurve(result, &resultPressure, &resultDerivative)) { + return invalidLoss; } - // 生成公共 X 网格进行插值。使用对数均匀网格,是为了给早期时间段 - // 更多分辨率;试井双对数曲线的早期形态通常对参数识别很敏感。 - QVector commonX; - int numPoints = 50; + // 在 log(time)-log(value) 空间做线性插值,而不是在线性坐标直接插值。 + // 这样可以保持双对数曲线的时间尺度和数量级特征;返回值是 + // log(abs(value)),后续残差因此可以直接解释为相对幅值误差。 + auto interpolateLogValue = [valueFloor](const QVector& curve, + double x, + double* value) -> bool { + if(!value || curve.size() < 2 || x < curve.first().x() || + x > curve.last().x() || x <= 0.0) { + return false; + } - if(overlapMinX > 0 && overlapMaxX > 0) { - // 对数空间均匀分布 - double logMin = qLn(overlapMinX); - double logMax = qLn(overlapMaxX); + int right = 1; - for(int i = 0; i < numPoints; ++i) { - double logX = logMin + i * (logMax - logMin) / (numPoints - 1); - double x = qExp(logX); + while(right < curve.size() && curve[right].x() < x) { + ++right; + } - // 数值保护 - if(!isFiniteNumber(x) || x <= 0) { - continue; - } + right = qMin(right, curve.size() - 1); + int left = qMax(0, right - 1); + double leftLogX = qLn(curve[left].x()); + double rightLogX = qLn(curve[right].x()); + double denominator = rightLogX - leftLogX; + double leftLogY = qLn(qMax(qAbs(curve[left].y()), valueFloor)); + double rightLogY = qLn(qMax(qAbs(curve[right].y()), valueFloor)); - commonX.append(x); + if(qAbs(denominator) <= 1.0e-12) { + *value = leftLogY; + } else { + double ratio = (qLn(x) - leftLogX) / denominator; + *value = leftLogY + ratio * (rightLogY - leftLogY); } - DEBUG_OUT("Using log-uniform grid for better early-time coverage"); + return isFiniteNumber(*value); + }; + + const double targetMinX = targetPressure.first().x(); + const double targetMaxX = targetPressure.last().x(); + const double resultMinX = resultPressure.first().x(); + const double resultMaxX = resultPressure.last().x(); + + if(targetMinX <= 0.0 || targetMaxX <= targetMinX || + resultMinX <= 0.0 || resultMaxX <= resultMinX) { + return invalidLoss; + } + + // 目标曲线的完整时间范围作为统一比较区间。若候选曲线覆盖不足, + // 后面的 coverage 检查会拒绝它,防止候选通过缩短时间范围来降低误差。 + QVector commonX(numPoints); + QVector commonLogX(numPoints); + QVector targetLogPressure(numPoints); + QVector targetLogDerivative(numPoints); + const double targetLogMinX = qLn(targetMinX); + const double targetLogMaxX = qLn(targetMaxX); + + for(int i = 0; i < numPoints; ++i) { + double logX = targetLogMinX + + static_cast(i) * + (targetLogMaxX - targetLogMinX) / (numPoints - 1); + commonLogX[i] = logX; + commonX[i] = qExp(logX); + + if(!interpolateLogValue(targetPressure, commonX[i], + &targetLogPressure[i]) || + !interpolateLogValue(targetDerivative, commonX[i], + &targetLogDerivative[i])) { + return invalidLoss; + } } - if(commonX.isEmpty()) { - DEBUG_OUT("Failed to generate common X grid"); - return 1e10; - } + // 残差采用“模拟减目标”,因此正值表示模拟曲线在对数幅值上高于目标, + // 负值表示模拟曲线偏低。NaN 表示该网格点不在模拟曲线支持范围内, + // 后续统计会自动跳过,但覆盖率检查仍会限制候选不能靠缺失数据降低损失。 + QVector pressureResidual(numPoints, + std::numeric_limits::quiet_NaN()); + QVector derivativeResidual(numPoints, + std::numeric_limits::quiet_NaN()); + QVector pressureSlope(numPoints, 0.0); + QVector derivativeSlope(numPoints, 0.0); + int firstSupported = -1; + int lastSupported = -1; + int supportedCount = 0; + + for(int i = 0; i < numPoints; ++i) { + if(commonX[i] < resultMinX || commonX[i] > resultMaxX) { + continue; + } - // 插值目标曲线。target[1] 是压力,target[2] 是压力导数。 - QVector targetCurve1, targetCurve2; + double resultLogPressure = 0.0; + double resultLogDerivative = 0.0; - for(int i = 0; i < target[0].size(); ++i) { - // 检查数据有效性 - if(isFiniteNumber(target[0][i]) && isFiniteNumber(target[1][i]) && - isFiniteNumber(target[2][i])) { - targetCurve1.append(QPointF(target[0][i], target[1][i])); - targetCurve2.append(QPointF(target[0][i], target[2][i])); + if(!interpolateLogValue(resultPressure, commonX[i], + &resultLogPressure) || + !interpolateLogValue(resultDerivative, commonX[i], + &resultLogDerivative)) { + continue; } - } - if(targetCurve1.isEmpty() || targetCurve2.isEmpty()) { - DEBUG_OUT("Target curves are empty after filtering"); - return 1e10; + pressureResidual[i] = resultLogPressure - targetLogPressure[i]; + derivativeResidual[i] = resultLogDerivative - targetLogDerivative[i]; + ++supportedCount; + + if(firstSupported < 0) { + firstSupported = i; + } + + lastSupported = i; + } + + // 同时使用点覆盖率和连续时间跨度覆盖率,避免只覆盖少数离散点也被判定为完整。 + AutoFitObjectiveBreakdown breakdown; + breakdown.coverage = supportedCount > 0 + ? static_cast(supportedCount) / numPoints + : 0.0; + + if(firstSupported >= 0 && lastSupported >= firstSupported) { + double span = qMax(1.0e-12, targetLogMaxX - targetLogMinX); + double coveredSpan = commonLogX[lastSupported] - + commonLogX[firstSupported]; + breakdown.coverage = qMin(breakdown.coverage, + qMax(0.0, coveredSpan / span)); + } + + // 覆盖率越接近 1,惩罚越小;覆盖不足 minimumCoverage 时直接返回无效损失。 + double coverageGap = qMax(0.0, 1.0 - breakdown.coverage); + breakdown.coveragePenalty = + qPow(coverageGap / (1.0 - minimumCoverage), 2.0); + + if(breakdown.coverage < minimumCoverage) { + breakdown.total = invalidLoss; + m_lastObjectiveBreakdown = breakdown; + return invalidLoss; + } + + // 目标曲线斜率用于把“残差随时间的系统性变化”解释为左右平移。 + // 斜率用对数坐标计算,与前面的插值空间保持一致;平坦区斜率接近零, + // 不会凭空制造水平偏移量。 + for(int i = 0; i < numPoints; ++i) { + int left = i == 0 ? 0 : i - 1; + int right = i == numPoints - 1 ? numPoints - 1 : i + 1; + double denominator = commonLogX[right] - commonLogX[left]; + + if(qAbs(denominator) > 1.0e-12) { + pressureSlope[i] = + (targetLogPressure[right] - targetLogPressure[left]) / + denominator; + derivativeSlope[i] = + (targetLogDerivative[right] - targetLogDerivative[left]) / + denominator; + } } - QVector alignedTarget1 = interpolateData(targetCurve1, commonX); - QVector alignedTarget2 = interpolateData(targetCurve2, commonX); + // Huber RMS 在小残差区域保持平方损失,在异常点区域转为线性增长, + // 避免少量求解器异常点完全主导候选排序。这里没有除以目标值, + // 因为残差已经是 log(value) 差值,本身就是相对误差的表达;返回值是 + // Huber rho 均值的平方根,保持与 RMS 类似的尺度。 + auto huberRms = [huberDelta](const QVector& values, + int begin, + int end) -> double { + double sum = 0.0; + int count = 0; - // 插值结果曲线。result 与 target 使用同一 commonX,保证逐点可比。 - QVector resultCurve1, resultCurve2; + for(int i = qMax(0, begin); + i < qMin(end, static_cast(values.size())); ++i) { + if(!isFiniteNumber(values[i])) { + continue; + } - for(int i = 0; i < result[0].size(); ++i) { - // 检查数据有效性 - if(isFiniteNumber(result[0][i]) && isFiniteNumber(result[1][i]) && - isFiniteNumber(result[2][i])) { - resultCurve1.append(QPointF(result[0][i], result[1][i])); - resultCurve2.append(QPointF(result[0][i], result[2][i])); + double absoluteValue = qAbs(values[i]); + double rho = absoluteValue <= huberDelta + ? values[i] * values[i] + : 2.0 * huberDelta * absoluteValue - + huberDelta * huberDelta; + sum += rho; + ++count; } - } - if(resultCurve1.isEmpty() || resultCurve2.isEmpty()) { - DEBUG_OUT("Result curves are empty after filtering"); - return 1e10; - } + return count > 0 + ? qSqrt(sum / count) + : std::numeric_limits::quiet_NaN(); + }; + + // 用 Huber 加权迭代估计残差中心,作为整体上下偏移。相比普通平均值, + // 它对局部尖峰更稳健,同时保留偏高/偏低的方向信息。迭代只用于诊断, + // 不会把残差“校正”后再写回求解器结果。 + auto huberCenter = [huberDelta](const QVector& values) -> double { + double center = 0.0; + int count = 0; + + for(int i = 0; i < values.size(); ++i) { + if(isFiniteNumber(values[i])) { + center += values[i]; + ++count; + } + } - QVector alignedResult1 = interpolateData(resultCurve1, commonX); - QVector alignedResult2 = interpolateData(resultCurve2, commonX); + if(count == 0) { + return std::numeric_limits::quiet_NaN(); + } - // 检查插值结果 - if(alignedTarget1.isEmpty() || alignedTarget2.isEmpty() || - alignedResult1.isEmpty() || alignedResult2.isEmpty()) { - DEBUG_OUT("LogLog interpolation failed"); - return 1e10; - } + center /= count; - if(alignedTarget1.size() != alignedResult1.size() || - alignedTarget2.size() != alignedResult2.size()) { - DEBUG_OUT("LogLog interpolation size mismatch"); - return 1e10; - } + // 固定最多 8 次迭代,控制每个候选的计算开销并保持结果稳定。 + for(int iteration = 0; iteration < 8; ++iteration) { + double weightedSum = 0.0; + double weightTotal = 0.0; - // 计算两条曲线的误差。当前压力和导数各占 50%。 - // 如果后续要让导数形态更重要,可以从这里调整权重。 - double error1 = calculateCurveError(alignedTarget1, alignedResult1); - double error2 = calculateCurveError(alignedTarget2, alignedResult2); + for(int i = 0; i < values.size(); ++i) { + if(!isFiniteNumber(values[i])) { + continue; + } - // 检查个别误差是否有效 - if(!isFiniteNumber(error1) || error1 > 1e9) { - DEBUG_OUT(QString("Curve1 error is invalid: %1").arg(error1)); - error1 = 1e10; - } + double distance = qAbs(values[i] - center); + // 距离接近零时直接取权重 1,避免除零并保持中心点不被放大。 + double weight = distance <= huberDelta || distance < 1.0e-12 + ? 1.0 + : huberDelta / distance; + weightedSum += weight * values[i]; + weightTotal += weight; + } - if(!isFiniteNumber(error2) || error2 > 1e9) { - DEBUG_OUT(QString("Curve2 error is invalid: %1").arg(error2)); - error2 = 1e10; - } + if(weightTotal <= 1.0e-12) { + break; + } - // 组合误差 - 添加保护 - double combinedError; + double nextCenter = weightedSum / weightTotal; + if(qAbs(nextCenter - center) <= 1.0e-12) { + center = nextCenter; + break; + } - if(error1 > 1e9 && error2 > 1e9) { - combinedError = 1e10; - } else if(error1 > 1e9) { - combinedError = error2; - } else if(error2 > 1e9) { - combinedError = error1; - } else { - combinedError = 0.5 * error1 + 0.5 * error2; - } + center = nextCenter; + } - DEBUG_OUT(QString("LogLog errors: Curve1=%1, Curve2=%2, Combined=%3") - .arg(error1, 0, 'e', 4).arg(error2, 0, 'e', 4).arg(combinedError, 0, 'e', 4)); + return center; + }; + + // 整体压力/导数误差用于排序;verticalLoss 主要用于解释整体上下偏移。 + breakdown.pressureLoss = huberRms(pressureResidual, 0, numPoints); + breakdown.derivativeLoss = huberRms(derivativeResidual, 0, numPoints); + breakdown.verticalBiasPressure = huberCenter(pressureResidual); + breakdown.verticalBiasDerivative = huberCenter(derivativeResidual); + breakdown.verticalLoss = + 0.5 * (qAbs(breakdown.verticalBiasPressure) + + qAbs(breakdown.verticalBiasDerivative)); + + // 去除上下中心后,把残差投影到目标曲线斜率上估计左右偏移。 + // residual ~= -physicalShift * targetSlope,因此 physicalShift 取回归系数的相反数。 + // 这是局部一阶近似,用于判断方向和大小,不等同于再次优化时间轴。 + double horizontalNumerator = 0.0; + double horizontalDenominator = 0.0; + + for(int i = 0; i < numPoints; ++i) { + if(!isFiniteNumber(pressureResidual[i]) || + !isFiniteNumber(derivativeResidual[i])) { + continue; + } + + double pressureCentered = + pressureResidual[i] - breakdown.verticalBiasPressure; + double derivativeCentered = + derivativeResidual[i] - breakdown.verticalBiasDerivative; + horizontalNumerator += pressureSlope[i] * pressureCentered + + derivativeSlope[i] * derivativeCentered; + horizontalDenominator += pressureSlope[i] * pressureSlope[i] + + derivativeSlope[i] * derivativeSlope[i]; + } + + breakdown.horizontalShift = horizontalDenominator > 1.0e-12 + ? horizontalNumerator / + horizontalDenominator + : 0.0; + breakdown.horizontalPhysicalShift = -breakdown.horizontalShift; + breakdown.horizontalLoss = qAbs(breakdown.horizontalShift); + + // 从原始残差中扣除“整体上下 + 等效左右”两部分,剩余项才作为形状误差。 + // 因此 shapeLoss 较大而 vertical/horizontal 较小时,说明主要是曲率、拐点 + // 或导数变化趋势不一致,而不是简单的整体平移。 + QVector shapePressure(numPoints, + std::numeric_limits::quiet_NaN()); + QVector shapeDerivative(numPoints, + std::numeric_limits::quiet_NaN()); + + for(int i = 0; i < numPoints; ++i) { + if(isFiniteNumber(pressureResidual[i])) { + shapePressure[i] = pressureResidual[i] - + breakdown.verticalBiasPressure - + breakdown.horizontalShift * pressureSlope[i]; + } - return qMin(1e9, combinedError); + if(isFiniteNumber(derivativeResidual[i])) { + shapeDerivative[i] = derivativeResidual[i] - + breakdown.verticalBiasDerivative - + breakdown.horizontalShift * + derivativeSlope[i]; + } + } + breakdown.shapeLoss = + 0.5 * (huberRms(shapePressure, 0, numPoints) + + huberRms(shapeDerivative, 0, numPoints)); + + // 将对数时间网格分成早、中、晚三段,用于定位误差集中出现的阶段。 + // 网格本身按 log(time) 均匀分布,所以三段对应的是时间数量级,而非原始 + // 线性时间长度,适合双对数试井曲线的早期/中期/晚期判读。 + const int segment1 = numPoints / 3; + const int segment2 = (2 * numPoints) / 3; + breakdown.pressureEarlyLoss = huberRms(pressureResidual, 0, segment1); + breakdown.pressureMiddleLoss = + huberRms(pressureResidual, segment1, segment2); + breakdown.pressureLateLoss = + huberRms(pressureResidual, segment2, numPoints); + breakdown.derivativeEarlyLoss = + huberRms(derivativeResidual, 0, segment1); + breakdown.derivativeMiddleLoss = + huberRms(derivativeResidual, segment1, segment2); + breakdown.derivativeLateLoss = + huberRms(derivativeResidual, segment2, numPoints); + + // 函数入口已将 m_lastObjectiveBreakdown 重置为无效状态,因此这里直接返回 + // invalidLoss 时不会把上一候选的误差分解误报给调用方。 + // 导数、压力或形状无法形成有效统计时,整个候选都视为无效,避免 NaN + // 进入粒子排序。 + if(!isFiniteNumber(breakdown.pressureLoss) || + !isFiniteNumber(breakdown.derivativeLoss) || + !isFiniteNumber(breakdown.shapeLoss) || + !isFiniteNumber(breakdown.verticalLoss)) { + return invalidLoss; + } + + // 当前总损失作为 fitness 用于粒子比较、收敛/停止判断;上下、左右、形状和 + // 分段分量先作为诊断输出,不在本次改动中直接参与参数更新。 + breakdown.total = 0.5 * breakdown.pressureLoss + + 0.5 * breakdown.derivativeLoss + + 0.1 * breakdown.coveragePenalty; + breakdown.valid = isFiniteNumber(breakdown.total) && + breakdown.total >= 0.0; + m_lastObjectiveBreakdown = breakdown; + + DEBUG_OUT(QString("LogLog objective: pressure=%1, derivative=%2, vertical=%3, horizontal=%4, shape=%5, coverage=%6, total=%7") + .arg(breakdown.pressureLoss, 0, 'e', 4) + .arg(breakdown.derivativeLoss, 0, 'e', 4) + .arg(breakdown.verticalLoss, 0, 'e', 4) + .arg(breakdown.horizontalLoss, 0, 'e', 4) + .arg(breakdown.shapeLoss, 0, 'e', 4) + .arg(breakdown.coverage, 0, 'f', 4) + .arg(breakdown.total, 0, 'e', 4)); + + return breakdown.valid ? qMin(1.0e9, breakdown.total) : invalidLoss; } catch(const std::exception& e) { DEBUG_OUT(QString("Exception in LogLog error calculation: %1").arg(e.what())); - return 1e10; + return invalidLoss; } catch(...) { DEBUG_OUT("Unknown exception in LogLog error calculation"); - return 1e10; + return invalidLoss; } } From efda31f3885c15e25cdb9f3910a5b5e373f4e713 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Thu, 13 Aug 2026 10:14:37 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8B=9F=E5=90=88=EF=BC=8C=E5=BC=95=E5=85=A5=E7=81=B5=E6=95=8F?= =?UTF-8?q?=E5=BA=A6=E5=88=86=E6=9E=90=E4=B8=8E=E4=BF=A1=E8=B5=96=E5=9F=9F?= =?UTF-8?q?=E6=B1=82=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nmCalculation/nmCalculationAutoFitPSO.h | 117 +- Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 2 +- .../nmCalculation/nmCalculationAutoFitPSO.cpp | 2414 +++++++++++++++-- Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 40 +- 4 files changed, 2210 insertions(+), 363 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index e65d1ee..b3e201a 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -20,64 +20,66 @@ class nmDataWellBase; class QTimer; class QProcess; -// 双对数曲线误差分解,供误差诊断和后续参数调整读取。 -// -// 所有 pressure/derivative/shape 数值均是在 log(value) 空间计算的无量纲误差。 -// verticalBias* 保留正负号:正值表示模拟曲线整体高于目标,负值表示整体低于目标。 -// horizontalPhysicalShift 是 log(time) 方向的等效平移量,正值表示模拟曲线相对目标偏右。 -// total 仍是 PSO 当前使用的 fitness(用于粒子比较和收敛判断);其余字段只描述误差 -// 来源,不会在本次改动中直接修改粒子参数,避免损失诊断和参数更新策略互相耦合。 +// 双对数曲线误差分解。该结构同时保存用于候选排序的主目标,以及用于判断 +// 曲线上下、左右和形状偏差的诊断量。total 是唯一的接受和排序依据,诊断量 +// 只参与信赖域选参,不能再次叠加到 total,否则会重复计算同一批曲线残差。 struct AutoFitObjectiveBreakdown { - // valid/total 是本次评价是否有效及其最终 fitness(用于排序和收敛判断)。 + // valid 表示本次曲线评价完整有效;无效评价统一保留 total=1e10。 + // pressureLoss 和 derivativeLoss 均在 log(value) 空间按固定网格计算。 bool valid; double total; - // pressureLoss/derivativeLoss 是压力和导数两条曲线的整体误差。 double pressureLoss; double derivativeLoss; - // vertical* 描述整体上下偏移;保留 bias 的符号以判断偏高或偏低。 - double verticalBiasPressure; - double verticalBiasDerivative; + // 固定目标网格上的 Huber 等效残差。非代理搜索使用它建立完整 Jacobian, + // 向量平方和与 total 的平方一致。 + QVector residualVector; + + // 上下偏差使用压力和导数残差共享的 Huber 稳健中心。 + // verticalCommonBias 为正表示模拟曲线整体偏高,为负表示整体偏低; + // verticalReliable=false 时仍保留数值,但不能据此确定参数调整方向。 + double verticalCommonBias; double verticalLoss; - // horizontal* 描述等效的对数时间偏移;physicalShift 为正表示模拟曲线相对目标向右 - // (时间延迟),为负表示向左。 - double horizontalShift; + bool verticalReliable; + + // 水平偏差在 log(time) 坐标中计算。physicalShift 为正表示模拟曲线相对 + // 目标偏右,即相同曲线特征在模拟结果中出现得更晚。 double horizontalPhysicalShift; double horizontalLoss; - // shapeLoss 是去除整体上下和左右偏移后剩余的曲线形状差异。 + bool horizontalReliable; + // true 表示当前曲线无法可靠区分上下和左右误差;此时禁止使用两类有符号 + // 诊断量选参,但去除公共中心后的 shapeLoss 仍可用于局部选参。 + bool registrationAmbiguous; + + // 去除稳健公共中心和可信左右偏差后剩余的整体形状误差;verticalReliable + // 只控制能否把公共中心解释为上下参数方向,不改变 shape 的中心化公式。 double shapeLoss; - // 早、中、晚分段误差用于定位误差主要出现在哪个时间阶段。 - double pressureEarlyLoss; - double pressureMiddleLoss; - double pressureLateLoss; - double derivativeEarlyLoss; - double derivativeMiddleLoss; - double derivativeLateLoss; - // coverage 是 50 点目标网格上的 min(有效点比例、连续 log-time 跨度比例)。 - // coveragePenalty 是归一化覆盖缺口的平方惩罚,并以 0.1 权重加入 total。 + + // 兼容现有 trace 列。当前非代理搜索不再单独识别或调度晚期分量。 + double lateDerivativeSlopeBias; + double lateDerivativeTrendLoss; + bool lateDerivativeTrendReliable; + + // 模拟曲线对目标固定网格的有效覆盖率,取覆盖点比例与连续 log-time + // 跨度比例中的较小值。低于损失函数门槛时本次评价直接无效。 double coverage; - double coveragePenalty; - // 无效评价使用 1e10 作为统一的“差解”标记;其他字段用 NaN 表示尚未得到诊断值。 AutoFitObjectiveBreakdown() : valid(false) , total(1.0e10) , pressureLoss(std::numeric_limits::quiet_NaN()) , derivativeLoss(std::numeric_limits::quiet_NaN()) - , verticalBiasPressure(std::numeric_limits::quiet_NaN()) - , verticalBiasDerivative(std::numeric_limits::quiet_NaN()) + , verticalCommonBias(std::numeric_limits::quiet_NaN()) , verticalLoss(std::numeric_limits::quiet_NaN()) - , horizontalShift(std::numeric_limits::quiet_NaN()) + , verticalReliable(false) , horizontalPhysicalShift(std::numeric_limits::quiet_NaN()) , horizontalLoss(std::numeric_limits::quiet_NaN()) + , horizontalReliable(false) + , registrationAmbiguous(false) , shapeLoss(std::numeric_limits::quiet_NaN()) - , pressureEarlyLoss(std::numeric_limits::quiet_NaN()) - , pressureMiddleLoss(std::numeric_limits::quiet_NaN()) - , pressureLateLoss(std::numeric_limits::quiet_NaN()) - , derivativeEarlyLoss(std::numeric_limits::quiet_NaN()) - , derivativeMiddleLoss(std::numeric_limits::quiet_NaN()) - , derivativeLateLoss(std::numeric_limits::quiet_NaN()) + , lateDerivativeSlopeBias(std::numeric_limits::quiet_NaN()) + , lateDerivativeTrendLoss(std::numeric_limits::quiet_NaN()) + , lateDerivativeTrendReliable(false) , coverage(std::numeric_limits::quiet_NaN()) - , coveragePenalty(std::numeric_limits::quiet_NaN()) {} }; @@ -95,6 +97,8 @@ struct AutoFitParticle { QVector velocity; // 速度 QVector bestPosition; // 真实求解器确认的个体最优位置 QVector guideBestPosition; // 仅用于速度更新的引导位置;不会参与真实 gbest/最终结果 + AutoFitObjectiveBreakdown currentObjectiveBreakdown; // 当前真实评价对应的误差分解 + AutoFitObjectiveBreakdown bestObjectiveBreakdown; // pbest 对应的误差分解 double fitness; // 当前适应度 double bestFitness; // 真实求解器确认的个体最优适应度 double guideBestObjective; // guideBestPosition 对应的真实或代理目标值 @@ -200,20 +204,26 @@ private: void loadOptimizationConfig(); void loadParameterBounds(); - // ===== PSO核心算法 ===== + // ===== 自动拟合核心算法 ===== // - // 主流程: - // 1. extractUserInitialValues(): 从当前项目数据中取用户已有初始解; - // 2. initializeSwarm(): 根据初始解和上下界生成粒子群; - // 3. updateParticle(): 对单个粒子跑真实求解器并计算误差; - // 4. updateGlobalBest(): 只用真实求解器误差更新全局最优; - // 5. updateVelocityAndPosition(): 按 PSO 公式推进下一代粒子。 + // 代理开启时保留原 PSO 筛选流程;代理关闭时使用真实求解器驱动的 + // 诊断灵敏度信赖域搜索,不依赖 pbest/gbest 速度公式。 void extractUserInitialValues(); void initializeSwarm(); void updateVelocityAndPosition(); double evaluateFitness(const QVector& parameters); void updateGlobalBest(); void updateParticle(int particleIndex); + // 非代理拟合入口:建立有限差分灵敏度,按诊断分量选择参数,再用有界 + // LM/信赖域产生候选;所有候选最终都由真实求解器总误差决定是否接受。 + StopReasonPSO runTrustRegionFitting(); + // 对一个信赖域候选执行完整真实评价,并一次性返回误差、诊断量、曲线和耗时。 + // 返回 false 表示求解失败、损失无效或用户已请求停止。 + bool evaluateTrustRegionPoint(const QVector& parameters, + double* fitness, + AutoFitObjectiveBreakdown* breakdown, + QVector >* curve, + int* elapsedMs); // ===== 参数应用方法 ===== // @@ -284,7 +294,8 @@ private: double surrogateObjective, const QString& screeningDecision, const QVector& pbestPosition, - double pbestObjective); + double pbestObjective, + const AutoFitObjectiveBreakdown* objectiveBreakdown = nullptr); void writeIterationTraceRows(); QVector buildTraceParameterVector(const QVector& selectedParameters) const; void resetRunSummary(); @@ -340,19 +351,21 @@ private: bool m_isRunning; // 当前是否有一次自动拟合正在运行。 bool m_shouldStop; // 用户停止标志;主循环和求解器等待循环会定期检查它。 bool m_isPaused; // 预留暂停标志;主循环中有暂停等待逻辑。 - int m_currentIteration; // 当前 PSO 迭代序号,从 0 开始。 + int m_currentIteration; // 当前自动拟合迭代序号,从 0 开始。 QString m_lastError; // 最近一次失败原因,供 UI 展示或日志排查。 - // ===== PSO数据 ===== + // ===== 优化状态数据 ===== QVector m_initialValues; // 当前模型中提取的用户初始值,顺序与 m_enabledParamIndices 一致。 QVector m_swarm; // 粒子群,每个粒子只保存启用参数维度。 - QVector m_globalBestPosition; // 全局最优参数,仍是启用参数向量。 + QVector m_globalBestPosition; // 真实求解器确认的当前最优参数。 double m_globalBestFitness; // 全局最优真实误差,越小越好。 double m_previousBestFitness; // 上一轮全局最优误差,用于自适应参数更新。 + AutoFitObjectiveBreakdown m_globalBestObjectiveBreakdown; // 真实 gbest 对应的误差分解。 QVector > m_lastEvaluatedLogLogData; // 最近一次真实求解得到的 result log-log 曲线。 QVector > m_globalBestLogLogData; // 当前全局最优对应的 result log-log 曲线。 mutable AutoFitObjectiveBreakdown m_lastObjectiveBreakdown; // 最近一次损失评价的误差分解。 QVector > m_userInitialLogLogData; // 用户初始解对应的 result log-log 曲线,用于精英保护。 + AutoFitObjectiveBreakdown m_userInitialObjectiveBreakdown; // 用户初始解对应的误差分解。 // ===== 优化配置 ===== // @@ -377,7 +390,7 @@ private: double m_socialParam; // 群体学习因子,控制粒子靠近全局 gbest 的程度。 // ===== 统计信息 ===== - int m_totalEvaluations; // 已调用真实求解器评价的粒子总数。 + int m_totalEvaluations; // 真实求解器评价总次数,包含粒子评价和方向试算。 int m_successfulEvaluations; // 真实求解器成功且误差有效的评价次数。 QVector m_convergenceHistory; // 每代全局最优误差历史,用于收敛判断。 @@ -393,7 +406,7 @@ private: // ===== 精英保护 ===== QVector m_userInitialSolution; // 用户初始解参数,若最终改进不足会恢复它。 double m_userInitialFitness; // 用户初始解真实误差。 - double m_improvementThreshold; // 最终结果相对初始解至少需要达到的改进阈值。 + double m_improvementThreshold; // 仅用于日志区分显著改进和微小改进。 bool m_hasValidUserSolution; // 初始解是否成功跑过真实求解器。 int m_consecutiveFailedIterations; // 连续失败迭代次数 @@ -422,8 +435,8 @@ private: // 这些字段只描述代理筛选和运行复盘,不参与 PSO 数学更新。 bool m_traceEnabled; // 是否写出 trace CSV/meta 文件。 QString m_traceRunId; // 本次运行 ID,作为 trace/candidate/score 文件名的一部分。 - QString m_traceFilePath; // pso_baseline_trace_.csv 完整路径。 - QString m_traceMetaFilePath; // pso_baseline_trace_.meta.json 完整路径。 + QString m_traceFilePath; // 本次自动拟合 trace CSV 的完整路径。 + QString m_traceMetaFilePath; // 与 trace 匹配的 meta JSON 完整路径。 QFile m_traceFile; // trace CSV 文件句柄。 bool m_surrogateScreeningEnabled; // 用户配置中的 PSO acceleration 开关。 unsigned int m_psoRandomSeed; // PSO 随机种子,也用于可复现 random audit。 diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 30a9119..5b3848c 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -57,7 +57,7 @@ private: void renumberVisibleParameterRows(QTableWidget* table); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); void initializeSuggestedParameterRanges(); - void updateRangeForParameter(int parameterIndex, double centerValue, bool afterFit); + void updateRangeForParameter(int parameterIndex, double centerValue); void setParameterRange(int parameterIndex, double minValue, double maxValue); bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue); void normalizeSavedParameterRanges(); diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index ccc05ea..a4eb476 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -104,6 +104,17 @@ static inline bool isFiniteNumber(double value) #endif } +// 两个 Huber RMS 的差不能直接解释为被消除的独立误差。RMS 的平方才对应 +// 稳健能量,因此先计算 reduced^2-full^2,再开方恢复原量纲。这里用于分别 +// 提取“消除公共上下偏差”和“消除水平位移”实际减少的误差贡献。 +static double nestedRmsContribution(double reducedModelLoss, + double fullModelLoss) +{ + return qSqrt(qMax(0.0, + reducedModelLoss * reducedModelLoss - + fullModelLoss * fullModelLoss)); +} + static inline bool isInClosedRange(double value, double lower, double upper) { return isFiniteNumber(value) && value >= lower && value <= upper; @@ -813,10 +824,12 @@ void nmCalculationAutoFitPSO::resetOptimizer() m_globalBestPosition.clear(); m_globalBestFitness = 1e10; m_previousBestFitness = 1e10; + m_globalBestObjectiveBreakdown = AutoFitObjectiveBreakdown(); m_lastEvaluatedLogLogData.clear(); m_globalBestLogLogData.clear(); m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); m_userInitialLogLogData.clear(); + m_userInitialObjectiveBreakdown = AutoFitObjectiveBreakdown(); m_currentIteration = 0; m_totalEvaluations = 0; m_successfulEvaluations = 0; @@ -844,9 +857,8 @@ void nmCalculationAutoFitPSO::setPSOTargetWellName(const QString& wellName) void nmCalculationAutoFitPSO::initializeTraceFile() { - // 创建本次 PSO 的可复盘文件: - // - pso_baseline_trace_.csv:逐代逐粒子的参数、真实误差、代理误差和筛选决策; - // - pso_baseline_trace_.meta.json:目标曲线、流量制度、参数上下界、PSO/代理配置。 + // 创建本次自动拟合的可复盘文件。代理 PSO 与非代理信赖域使用不同前缀, + // 防止代理回放脚本把信赖域记录误当成最新 PSO 粒子记录。 // // 代理模型评分脚本也会读取 meta.json,因此 trace meta 不是单纯日志,而是C++ 与 Python 代理模型之间的运行上下文契约。 if(!m_traceEnabled) { @@ -865,8 +877,13 @@ void nmCalculationAutoFitPSO::initializeTraceFile() return; } - m_traceFilePath = traceDir.absoluteFilePath(QString("pso_baseline_trace_%1.csv").arg(m_traceRunId)); - m_traceMetaFilePath = traceDir.absoluteFilePath(QString("pso_baseline_trace_%1.meta.json").arg(m_traceRunId)); + QString tracePrefix = isSurrogateScreeningEnabled() + ? "pso_baseline_trace" + : "trust_region_trace"; + m_traceFilePath = traceDir.absoluteFilePath( + QString("%1_%2.csv").arg(tracePrefix).arg(m_traceRunId)); + m_traceMetaFilePath = traceDir.absoluteFilePath( + QString("%1_%2.meta.json").arg(tracePrefix).arg(m_traceRunId)); m_traceFile.setFileName(m_traceFilePath); if(!m_traceFile.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -878,11 +895,12 @@ void nmCalculationAutoFitPSO::initializeTraceFile() writeTraceHeader(); writeTraceMetaFile(); - DEBUG_OUT(QString("PSO baseline trace initialized: %1").arg(m_traceFilePath)); - emit logMessageGenerated(tr("PSO baseline trace: %1").arg(m_traceFilePath)); + DEBUG_OUT(QString("Automatic fitting trace initialized: %1").arg(m_traceFilePath)); + emit logMessageGenerated(tr("Automatic fitting trace: %1").arg(m_traceFilePath)); if(!m_traceMetaFilePath.isEmpty()) { - emit logMessageGenerated(tr("PSO baseline trace meta: %1").arg(m_traceMetaFilePath)); + emit logMessageGenerated( + tr("Automatic fitting trace meta: %1").arg(m_traceMetaFilePath)); } emit logMessageGenerated(tr("PSO surrogate screening: %1, model=%2, keep=%3, audit=%4, warmup=%5, min_solver=%6") @@ -997,7 +1015,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader() // - solver_objective 是真实求解器误差; // - surrogate_objective 是 Python 代理评分; // - screening_decision 说明该粒子为什么跑/不跑真实求解器; - // - pbest/gbest 字段用于离线复盘 PSO 更新是否只依赖真实误差。 + // - pbest/gbest 字段用于离线复盘 PSO 更新是否只依赖真实误差; + // - 末尾诊断字段记录同一次真实评价的分量误差,便于核对引导方向和接受结果。 if(!m_traceFile.isOpen()) { return; } @@ -1035,7 +1054,26 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "gbest_h" << "gbest_Ct" << "gbest_Cf" - << "enabled_param_indices"; + << "enabled_param_indices" + << "pressure_loss" + << "derivative_loss"; + if(!isSurrogateScreeningEnabled()) { + cols << "vertical_common_bias"; + } + cols << "vertical_loss"; + if(!isSurrogateScreeningEnabled()) { + cols << "vertical_reliable" + << "horizontal_physical_shift"; + } + cols << "horizontal_loss"; + if(!isSurrogateScreeningEnabled()) { + cols << "horizontal_reliable"; + } + cols << "shape_loss" + << "late_trend_loss" + << "late_slope_bias" + << "late_trend_reliable" + << "registration_ambiguous"; QTextStream out(&m_traceFile); out << cols.join(",") << "\n"; @@ -1077,8 +1115,14 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile() QTextStream out(&metaFile); out << "{\n"; - out << " \"schema_version\": 1,\n"; - out << " \"trace_type\": \"pso_baseline_replay_meta\",\n"; + // 非代理 v5 增加带符号诊断列;代理 PSO 保留原 v3 字段和目标,避免改变 + // 已有模型的训练和回放契约。 + out << " \"schema_version\": " + << (isSurrogateScreeningEnabled() ? 3 : 5) << ",\n"; + out << " \"trace_type\": " + << jsonEscape(isSurrogateScreeningEnabled() + ? "pso_baseline_replay_meta" + : "diagnostic_trust_region_meta") << ",\n"; out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n"; out << " \"created_at\": " << jsonEscape(QDateTime::currentDateTime().toString(Qt::ISODate)) << ",\n"; out << " \"trace_csv\": " << jsonEscape(QFileInfo(m_traceFilePath).fileName()) << ",\n"; @@ -1180,7 +1224,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, double surrogateObjective, const QString& screeningDecision, const QVector& pbestPosition, - double pbestObjective) + double pbestObjective, + const AutoFitObjectiveBreakdown* objectiveBreakdown) { // 写一行 trace。generation=-1/particleIndex=-1 表示用户初始解; // 普通粒子行的 phase 为 particle_solver、particle_verified_cache 或 particle_not_evaluated。 @@ -1234,6 +1279,35 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(gbestParams, 6) << csvEscape(enabledIndices.join(";")); + if(objectiveBreakdown && objectiveBreakdown->valid) { + cols << traceNumber(objectiveBreakdown->pressureLoss) + << traceNumber(objectiveBreakdown->derivativeLoss); + if(!isSurrogateScreeningEnabled()) { + cols << traceNumber(objectiveBreakdown->verticalCommonBias); + } + cols << traceNumber(objectiveBreakdown->verticalLoss); + if(!isSurrogateScreeningEnabled()) { + cols << QString::number(objectiveBreakdown->verticalReliable ? 1 : 0) + << traceNumber(objectiveBreakdown->horizontalPhysicalShift); + } + cols << traceNumber(objectiveBreakdown->horizontalLoss); + if(!isSurrogateScreeningEnabled()) { + cols << QString::number(objectiveBreakdown->horizontalReliable ? 1 : 0); + } + cols << traceNumber(objectiveBreakdown->shapeLoss) + << traceNumber(objectiveBreakdown->lateDerivativeTrendLoss) + << traceNumber(objectiveBreakdown->lateDerivativeSlopeBias) + << QString::number(objectiveBreakdown->lateDerivativeTrendReliable ? 1 : 0) + << QString::number(objectiveBreakdown->registrationAmbiguous ? 1 : 0); + } else { + // 未运行真实求解器或评价无效时保持列数一致,诊断字段写空值。 + int diagnosticColumnCount = + isSurrogateScreeningEnabled() ? 9 : 13; + for(int i = 0; i < diagnosticColumnCount; ++i) { + cols << QString(); + } + } + QTextStream out(&m_traceFile); out << cols.join(",") << "\n"; m_traceFile.flush(); @@ -1265,9 +1339,11 @@ void nmCalculationAutoFitPSO::writeIterationTraceRows() particle.lastEvaluationSuccess, particle.lastEvaluationElapsedMs, particle.surrogateObjective, - particle.screeningDecision, - particle.bestPosition, - particle.bestFitness); + particle.screeningDecision, + particle.bestPosition, + particle.bestFitness, + particle.evaluatedThisIteration + ? &particle.currentObjectiveBreakdown : nullptr); } } @@ -2855,21 +2931,19 @@ void nmCalculationAutoFitPSO::loadParameterBounds() .arg(m_enabledParamIndices.size())); } -// ==================== PSO算法核心方法 ==================== +// ==================== 自动拟合核心方法 ==================== bool nmCalculationAutoFitPSO::startAutoFitting() { - // 自动拟合的总入口。可以把这个函数当成 PSO 的“运行剧本”: - // 读取配置 -> 校验输入 -> 评价用户初始解 -> 初始化粒子群 -> - // 按代循环评价粒子 -> 更新全局最优 -> 判断停止 -> 保存结果。 + // 自动拟合总入口:代理开启时保留原 PSO 筛选流程;代理关闭时改走 + // 诊断灵敏度信赖域搜索。两条路径共用初始解评价、真实求解器和结果写回。 + StopReasonPSO finalReason = PSO_CONTINUE_OPTIMIZATION; + bool useParticleSwarm = false; + if(m_isRunning) { m_lastError = "Auto fitting is already running"; return false; } - // 发送初始化日志 - //emit logMessageGenerated(tr("=== PSO Automatic Fitting Started ===")); - emit logMessageGenerated(tr("Algorithm: Particle Swarm Optimization")); - try { // 从 DataManager 读取界面保存的自动拟合配置。 // 本类不直接依赖 UI 控件,便于后续从脚本或其他入口复用。 @@ -2878,6 +2952,11 @@ bool nmCalculationAutoFitPSO::startAutoFitting() return false; } + useParticleSwarm = isSurrogateScreeningEnabled(); + emit logMessageGenerated(useParticleSwarm + ? tr("Algorithm: Particle Swarm Optimization") + : tr("Algorithm: Diagnostic Trust-Region Search")); + if(m_simulationMode) { // 调试/演示用快速路径,不调用真实求解器。正式工况通常不走这里。 DEBUG_OUT("=== SIMULATION MODE ACTIVATED ==="); @@ -3007,6 +3086,8 @@ bool nmCalculationAutoFitPSO::startAutoFitting() m_globalBestPosition = m_userInitialSolution; m_userInitialLogLogData = m_lastEvaluatedLogLogData; m_globalBestLogLogData = m_userInitialLogLogData; + m_userInitialObjectiveBreakdown = m_lastObjectiveBreakdown; + m_globalBestObjectiveBreakdown = m_userInitialObjectiveBreakdown; emit logMessageGenerated(tr("Initial solution evaluation successful")); emit logMessageGenerated(tr("Initial Error: %1").arg(m_userInitialFitness, 0, 'e', 4)); @@ -3025,9 +3106,11 @@ bool nmCalculationAutoFitPSO::startAutoFitting() m_userInitialFitness < 1e9, initialEvalElapsedMs, std::numeric_limits::quiet_NaN(), - "initial_solution", - m_hasValidUserSolution ? m_userInitialSolution : QVector(), - m_userInitialFitness); + "initial_solution", + m_hasValidUserSolution ? m_userInitialSolution : QVector(), + m_userInitialFitness, + m_hasValidUserSolution + ? &m_userInitialObjectiveBreakdown : nullptr); } catch(...) { m_hasValidUserSolution = false; emit logMessageGenerated(tr("Exception during initial solution evaluation")); @@ -3037,12 +3120,13 @@ bool nmCalculationAutoFitPSO::startAutoFitting() m_initialValues = savedInitialValues; } - // 初始化粒子群。粒子维度等于用户勾选的参数数量,而不是固定 11 维。 - if(kUseFixedPsoSeed) { - emit logMessageGenerated(tr("PSO random seed: %1 ").arg(m_psoRandomSeed)); - } else { - emit logMessageGenerated(tr("PSO random seed: %1 ").arg(m_psoRandomSeed)); - } + if(useParticleSwarm) { + // 初始化粒子群。粒子维度等于用户勾选的参数数量,而不是固定 11 维。 + if(kUseFixedPsoSeed) { + emit logMessageGenerated(tr("PSO random seed: %1 ").arg(m_psoRandomSeed)); + } else { + emit logMessageGenerated(tr("PSO random seed: %1 ").arg(m_psoRandomSeed)); + } initializeSwarm(); emit logMessageGenerated(tr("Swarm initialized: %1 particles, %2 dimensions").arg(m_swarmSize).arg(getEnabledParameterCount())); @@ -3191,9 +3275,7 @@ bool nmCalculationAutoFitPSO::startAutoFitting() .arg(currentSuccessRate * 100, 0, 'f', 1).arg(m_currentIteration + 1)); } - // 更新全局最优。updateGlobalBest() 只读取粒子的真实 bestFitness, - // 不使用代理模型的 surrogateObjective。 - //double previousGlobalBest = m_globalBestFitness; + // 只从真实求解器确认的粒子 bestFitness 更新全局最优,不使用代理分数。 updateGlobalBest(); // 记录本代所有粒子的真实/代理误差和筛选决策,用于复盘和排障。 writeIterationTraceRows(); @@ -3282,11 +3364,40 @@ bool nmCalculationAutoFitPSO::startAutoFitting() } } + finalReason = analyzeOptimizationStatus(); + } else { + // 非代理路径不初始化粒子,也不使用 pbest/gbest 速度更新。 + finalReason = runTrustRegionFitting(); + } + // 最终结果验证和保护 validateAndProtectFinalResult(); + if(!useParticleSwarm && !m_globalBestPosition.isEmpty() && + m_globalBestObjectiveBreakdown.valid) { + // 精英保护可能恢复用户初始解,最终行必须在保护之后写入,确保 trace + // 中最后记录的就是实际回写 DataManager 的参数,而非最后一次接受候选。 + writeTraceRow(m_currentIteration, -1, + "trust_region_final", + m_globalBestPosition, + m_globalBestFitness, + m_globalBestFitness < 1.0e9, + -1, + std::numeric_limits::quiet_NaN(), + "final_result", + m_globalBestPosition, + m_globalBestFitness, + &m_globalBestObjectiveBreakdown); + } + + if(m_globalBestFitness < m_targetError) { + finalReason = PSO_TARGET_ACHIEVED; + } + } catch(const std::exception& e) { - m_lastError = QString(tr("Critical exception in PSO main loop: %1")).arg(e.what()); + m_lastError = useParticleSwarm + ? QString(tr("Critical exception in PSO main loop: %1")).arg(e.what()) + : QString(tr("Critical exception in automatic fitting: %1")).arg(e.what()); emit logMessageGenerated(tr("CRITICAL ERROR: %1").arg(e.what())); closeTraceFile(); cleanupTemporaryDirectory(); @@ -3294,7 +3405,9 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit fittingFinished(false, m_lastError); return false; } catch(...) { - m_lastError = QString(tr("Unknown critical exception in PSO main loop")); + m_lastError = useParticleSwarm + ? QString(tr("Unknown critical exception in PSO main loop")) + : QString(tr("Unknown critical exception in automatic fitting")); emit logMessageGenerated(tr("CRITICAL ERROR: Unknown exception in PSO main loop")); closeTraceFile(); cleanupTemporaryDirectory(); @@ -3373,43 +3486,68 @@ bool nmCalculationAutoFitPSO::startAutoFitting() // 判断系统确定最终结果 bool success; QString message; - StopReasonPSO finalReason = analyzeOptimizationStatus(); if(finalReason == PSO_TARGET_ACHIEVED) { success = true; message = QString(tr("Target achieved. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION SUCCESSFUL ===")); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION SUCCESSFUL ===") + : tr("=== AUTOMATIC FITTING SUCCESSFUL ===")); } else if(finalReason == PSO_TRUE_CONVERGENCE) { success = true; - message = QString(tr("PSO optimization converged to stable solution. Best error: %1, Iterations: %2")) - .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION CONVERGED ===")); + message = useParticleSwarm + ? QString(tr("PSO optimization converged to stable solution. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1) + : QString(tr("Automatic fitting converged to a stable solution. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION CONVERGED ===") + : tr("=== AUTOMATIC FITTING CONVERGED ===")); } else if(finalReason == PSO_LOCAL_OPTIMUM) { success = true; - message = QString(tr("PSO optimization trapped in local optimum. Best error: %1, Iterations: %2")) - .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION - LOCAL OPTIMUM ===")); + message = useParticleSwarm + ? QString(tr("PSO optimization trapped in local optimum. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1) + : QString(tr("Automatic fitting reached a local optimum. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION - LOCAL OPTIMUM ===") + : tr("=== AUTOMATIC FITTING - LOCAL OPTIMUM ===")); } else if(finalReason == PSO_MAX_ITERATIONS) { success = true; message = QString(tr("Max iterations reached. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION - MAX ITERATIONS ===")); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION - MAX ITERATIONS ===") + : tr("=== AUTOMATIC FITTING - MAX ITERATIONS ===")); } else if(finalReason == PSO_USER_STOPPED) { success = true; message = QString(tr("Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION STOPPED BY USER ===")); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION STOPPED BY USER ===") + : tr("=== AUTOMATIC FITTING STOPPED BY USER ===")); } else if(finalReason == PSO_CONSECUTIVE_FAILURES) { success = false; - message = QString(tr("PSO optimization failed due to consecutive failures. Best error: %1, Iterations: %2")) - .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION FAILED ===")); + message = useParticleSwarm + ? QString(tr("PSO optimization failed due to consecutive failures. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1) + : QString(tr("Automatic fitting failed due to consecutive failures. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION FAILED ===") + : tr("=== AUTOMATIC FITTING FAILED ===")); } else { success = false; - message = QString(tr("PSO optimization ended unexpectedly. Best error: %1, Iterations: %2")) - .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== PSO OPTIMIZATION - UNKNOWN END ===")); + message = useParticleSwarm + ? QString(tr("PSO optimization ended unexpectedly. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1) + : QString(tr("Automatic fitting ended unexpectedly. Best error: %1, Iterations: %2")) + .arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1); + emit logMessageGenerated(useParticleSwarm + ? tr("=== PSO OPTIMIZATION - UNKNOWN END ===") + : tr("=== AUTOMATIC FITTING - UNKNOWN END ===")); } if(!finalFullSolverSucceeded) { @@ -3544,6 +3682,8 @@ void nmCalculationAutoFitPSO::initializeSwarm() particle.velocity.resize(dimensions); particle.bestPosition.resize(dimensions); particle.guideBestPosition.resize(dimensions); + particle.currentObjectiveBreakdown = AutoFitObjectiveBreakdown(); + particle.bestObjectiveBreakdown = AutoFitObjectiveBreakdown(); particle.bestFitness = 1e10; particle.guideBestObjective = 1e10; particle.guideBestFromSurrogate = false; @@ -3609,7 +3749,1201 @@ void nmCalculationAutoFitPSO::initializeSwarm() particle.bestPosition = particle.position; particle.guideBestPosition = particle.position; + + // 第一个粒子可能直接复用用户初始解,因此同步保存初始解的误差分解, + // 后续误差引导只能使用真实求解器确认过的 breakdown。 + if(i == 0 && m_hasValidUserSolution) { + particle.currentObjectiveBreakdown = m_userInitialObjectiveBreakdown; + particle.bestObjectiveBreakdown = m_userInitialObjectiveBreakdown; + } + } +} + +// 信赖域搜索统一在 [0, 1] 内部坐标工作。正值参数使用对数坐标,使内部相同步长 +// 表示近似相同的相对变化,避免 k、C、Ct、Cf 等跨数量级参数被线性尺度支配; +// skin 可为负数、Swi 的物理意义是线性比例,因此二者保持有界线性坐标。 +static bool useTrustRegionLogScale(int parameterIndex, double lower, double upper) +{ + return parameterIndex != 1 && parameterIndex != 7 && + lower > 0.0 && upper > lower; +} + +static double toTrustRegionCoordinate(double value, + int parameterIndex, + double lower, + double upper) +{ + // 所有进入优化器的物理值先投影到用户上下界,再转换成无量纲坐标。 + // 这样有限差分步长、信赖半径和参数间相关性可以在统一尺度上比较。 + value = qMax(lower, qMin(upper, value)); + + if(useTrustRegionLogScale(parameterIndex, lower, upper)) { + return (qLn(value) - qLn(lower)) / (qLn(upper) - qLn(lower)); + } + + return upper > lower ? (value - lower) / (upper - lower) : 0.0; +} + +static double fromTrustRegionCoordinate(double coordinate, + int parameterIndex, + double lower, + double upper) +{ + // 候选内部坐标先限制在 [0,1],再执行上述映射的逆变换,保证写回 + // DataManager 的参数始终位于用户设置的物理范围内。 + coordinate = qMax(0.0, qMin(1.0, coordinate)); + + if(useTrustRegionLogScale(parameterIndex, lower, upper)) { + return qExp(qLn(lower) + coordinate * (qLn(upper) - qLn(lower))); } + + return lower + coordinate * (upper - lower); +} + +enum TrustRegionErrorComponent +{ + TRUST_REGION_VERTICAL_COMPONENT = 0, + TRUST_REGION_HORIZONTAL_COMPONENT, + TRUST_REGION_SHAPE_COMPONENT, + TRUST_REGION_TOTAL_COMPONENT +}; + +// 一次真实求解的完整快照。除了参数和总误差,还保存内部坐标、诊断分量和 +// 双对数曲线,因此拒绝候选后可以完整恢复上一个已接受工作点。 +struct TrustRegionEvaluation +{ + QVector parameters; + QVector coordinates; + AutoFitObjectiveBreakdown breakdown; + QVector > curve; + double fitness; + int elapsedMs; + bool valid; + + TrustRegionEvaluation() + : fitness(1.0e10) + , elapsedMs(-1) + , valid(false) + {} +}; + +// LM 只使用固定长度、全部有限的稳健残差。代理路径不会进入本搜索器。 +static bool trustRegionResidualsValid( + const AutoFitObjectiveBreakdown& breakdown) +{ + // 损失函数固定使用 50 个压力点和 50 个导数点。严格校验长度,避免 + // Jacobian 沿用旧维度后访问另一候选的短残差向量。 + if(!breakdown.valid || breakdown.residualVector.size() != 100) { + return false; + } + + for(int i = 0; i < breakdown.residualVector.size(); ++i) { + if(!isFiniteNumber(breakdown.residualVector[i])) { + return false; + } + } + return true; +} + +// 计算向量二范数的平方,避免在只比较能量或计算正规方程时反复开方。 +static double trustRegionSquaredNorm(const QVector& values) +{ + double sum = 0.0; + for(int i = 0; i < values.size(); ++i) { + sum += values[i] * values[i]; + } + return sum; +} + +// 计算同维向量内积;维度不一致表示局部模型无效,返回零让调用方放弃修正。 +static double trustRegionDotProduct(const QVector& left, + const QVector& right) +{ + if(left.size() != right.size()) { + return 0.0; + } + + double sum = 0.0; + for(int i = 0; i < left.size(); ++i) { + sum += left[i] * right[i]; + } + return sum; +} + +// trace 和运行日志使用稳定的英文标识,便于现有离线脚本继续按字段筛选。 +static QString trustRegionComponentName(int component) +{ + if(component == TRUST_REGION_VERTICAL_COMPONENT) { + return "vertical"; + } + if(component == TRUST_REGION_HORIZONTAL_COMPONENT) { + return "horizontal"; + } + if(component == TRUST_REGION_SHAPE_COMPONENT) { + return "shape"; + } + return "total"; +} + +// 三类损失量纲一致,直接选择当前最大的可靠分量;都很小时退回总残差梯度。 +static int trustRegionDominantComponent( + const AutoFitObjectiveBreakdown& breakdown, + double diagnosisThreshold) +{ + int component = TRUST_REGION_TOTAL_COMPONENT; + double largestLoss = diagnosisThreshold; + + if(breakdown.verticalReliable && + isFiniteNumber(breakdown.verticalLoss) && + breakdown.verticalLoss > largestLoss) { + component = TRUST_REGION_VERTICAL_COMPONENT; + largestLoss = breakdown.verticalLoss; + } + if(breakdown.horizontalReliable && + !breakdown.registrationAmbiguous && + isFiniteNumber(breakdown.horizontalLoss) && + breakdown.horizontalLoss > largestLoss) { + component = TRUST_REGION_HORIZONTAL_COMPONENT; + largestLoss = breakdown.horizontalLoss; + } + if(isFiniteNumber(breakdown.shapeLoss) && + breakdown.shapeLoss > largestLoss) { + component = TRUST_REGION_SHAPE_COMPONENT; + } + + return component; +} + +// 求解选中参数对应的阻尼正规方程。上下和左右诊断量保留方向;形状没有 +// 天然正负,因此使用 shapeLoss 对参数的局部导数。参数最多八维,使用带 +// 部分主元的高斯消元即可处理该小矩阵,并在主元退化时明确返回失败。 +static bool solveTrustRegionLinearSystem( + QVector > matrix, + QVector rightHandSide, + QVector* solution) +{ + if(!solution || matrix.isEmpty() || + matrix.size() != rightHandSide.size()) { + return false; + } + + const int size = matrix.size(); + for(int i = 0; i < size; ++i) { + if(matrix[i].size() != size) { + return false; + } + } + + for(int column = 0; column < size; ++column) { + int pivotRow = column; + double pivotMagnitude = qAbs(matrix[column][column]); + for(int row = column + 1; row < size; ++row) { + double magnitude = qAbs(matrix[row][column]); + if(magnitude > pivotMagnitude) { + pivotMagnitude = magnitude; + pivotRow = row; + } + } + if(pivotMagnitude <= 1.0e-14) { + return false; + } + + if(pivotRow != column) { + qSwap(matrix[pivotRow], matrix[column]); + qSwap(rightHandSide[pivotRow], rightHandSide[column]); + } + + for(int row = column + 1; row < size; ++row) { + double factor = matrix[row][column] / + matrix[column][column]; + matrix[row][column] = 0.0; + for(int nextColumn = column + 1; + nextColumn < size; ++nextColumn) { + matrix[row][nextColumn] -= + factor * matrix[column][nextColumn]; + } + rightHandSide[row] -= factor * rightHandSide[column]; + } + } + + solution->fill(0.0, size); + for(int row = size - 1; row >= 0; --row) { + double value = rightHandSide[row]; + for(int column = row + 1; column < size; ++column) { + value -= matrix[row][column] * (*solution)[column]; + } + double pivot = matrix[row][row]; + if(qAbs(pivot) <= 1.0e-14) { + return false; + } + (*solution)[row] = value / pivot; + if(!isFiniteNumber((*solution)[row])) { + return false; + } + } + return true; +} + +// 计算两个 Jacobian 列向量的绝对余弦相似度。接近 1 表示两个参数在当前 +// 工作点对曲线的影响几乎相同,联合调整容易产生不可辨识方向。 +static double trustRegionJacobianColumnCorrelation( + const QVector >& jacobian, + int leftColumn, + int rightColumn) +{ + double product = 0.0; + double leftNorm = 0.0; + double rightNorm = 0.0; + for(int row = 0; row < jacobian.size(); ++row) { + if(leftColumn >= jacobian[row].size() || + rightColumn >= jacobian[row].size()) { + return 1.0; + } + double left = jacobian[row][leftColumn]; + double right = jacobian[row][rightColumn]; + product += left * right; + leftNorm += left * left; + rightNorm += right * right; + } + + if(leftNorm <= 1.0e-20 || rightNorm <= 1.0e-20) { + return 0.0; + } + return qAbs(product) / qSqrt(leftNorm * rightNorm); +} + +// 每次接受一个真实候选后,使用满足最新割线条件的秩一修正更新完整残差 +// Jacobian。这样模型吸收了刚得到的真实变化,又不必立即逐参数重新试算。 +static void updateTrustRegionJacobian( + QVector >* jacobian, + const QVector& oldResidual, + const QVector& newResidual, + const QVector& coordinateStep) +{ + if(!jacobian || jacobian->size() != oldResidual.size() || + oldResidual.size() != newResidual.size()) { + return; + } + + double denominator = trustRegionSquaredNorm(coordinateStep); + if(denominator <= 1.0e-12) { + return; + } + + for(int row = 0; row < jacobian->size(); ++row) { + if((*jacobian)[row].size() != coordinateStep.size()) { + return; + } + + double predictedChange = 0.0; + for(int column = 0; column < coordinateStep.size(); ++column) { + predictedChange += + (*jacobian)[row][column] * coordinateStep[column]; + } + double correction = + (newResidual[row] - oldResidual[row] - predictedChange) / + denominator; + for(int column = 0; column < coordinateStep.size(); ++column) { + (*jacobian)[row][column] += + correction * coordinateStep[column]; + } + } +} + +// 对上下偏差、左右偏差和形状损失的梯度执行同样的割线秩一修正,使诊断 +// 选参模型与完整残差 Jacobian 保持在同一个已接受工作点。 +static void updateTrustRegionScalarGradient( + QVector* gradient, + double oldValue, + double newValue, + const QVector& coordinateStep) +{ + if(!gradient || gradient->size() != coordinateStep.size() || + !isFiniteNumber(oldValue) || !isFiniteNumber(newValue)) { + return; + } + + double denominator = trustRegionSquaredNorm(coordinateStep); + if(denominator <= 1.0e-12) { + return; + } + + double predictedChange = trustRegionDotProduct( + *gradient, coordinateStep); + double correction = + (newValue - oldValue - predictedChange) / denominator; + for(int i = 0; i < gradient->size(); ++i) { + (*gradient)[i] += correction * coordinateStep[i]; + } +} + +bool nmCalculationAutoFitPSO::evaluateTrustRegionPoint( + const QVector& parameters, + double* fitness, + AutoFitObjectiveBreakdown* breakdown, + QVector >* curve, + int* elapsedMs) +{ + if(!fitness || !breakdown || !curve || !elapsedMs || m_shouldStop) { + return false; + } + + // evaluateFitness() 会写入 DataManager 并调用真实求解器。这里统一统计 + // 真实评价次数和耗时,同时严格要求固定残差、诊断结构和结果曲线均有效。 + QTime timer; + timer.start(); + *fitness = evaluateFitness(parameters); + *elapsedMs = timer.elapsed(); + *breakdown = m_lastObjectiveBreakdown; + *curve = m_lastEvaluatedLogLogData; + ++m_totalEvaluations; + + bool valid = isFiniteNumber(*fitness) && *fitness < 1.0e9 && + breakdown->valid && + trustRegionResidualsValid(*breakdown) && + !curve->isEmpty(); + if(valid) { + ++m_successfulEvaluations; + } + + return valid; +} + +StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() +{ + const int dimensions = getEnabledParameterCount(); + if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) { + m_lastError = tr("No valid parameters are available for trust-region fitting"); + return PSO_OPTIMIZATION_FAILED; + } + + // 真实求解次数比“外层迭代次数”更能反映耗时。预算至少允许完成一次全参数 + // 灵敏度和两次候选评价,同时避免连续重建 Jacobian 导致运行时间失控。 + const int maximumEvaluations = qMax( + m_totalEvaluations + dimensions + 2, + qMax(20, m_maxIterations * 3)); + // 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径 + // 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。 + const double sensitivityStep = 0.04; + const double minimumCoordinateStep = 1.0e-5; + const double minimumTrustRadius = 2.0e-3; + const double maximumTrustRadius = 0.30; + const double columnCorrelationLimit = 0.995; + const double diagnosisThreshold = 1.0e-5; + + // damping 是 LM 阻尼;拒绝或预测失准时增大,真实下降与预测一致时减小。 + // 两组累计量控制 Jacobian 重建,避免长期使用已偏离当前工作点的局部模型。 + double trustRadius = 0.12; + double damping = 1.0e-2; + int consecutiveRejectedSteps = 0; + int consecutiveSolverFailures = 0; + int acceptedSinceRebuild = 0; + double movementSinceRebuild = 0.0; + bool rebuildRequested = true; + bool modelRebuiltAtMinimumRadius = false; + StopReasonPSO stopReason = PSO_MAX_ITERATIONS; + + // jacobian 的行对应固定 100 维稳健残差,列对应用户勾选的参数。 + // 三个 gradient 单独描述诊断分量对参数的局部变化,只用于本轮选参。 + QVector > jacobian; + QVector verticalGradient(dimensions, 0.0); + QVector horizontalGradient(dimensions, 0.0); + QVector shapeGradient(dimensions, 0.0); + QVector jacobianColumnValid(dimensions, false); + + // 参数向量的顺序始终与 m_enabledParamIndices 一致,不能按完整参数索引 + // 直接访问;下面两个转换函数集中维护这层映射关系。 + auto coordinatesFromParameters = [&](const QVector& parameters) + -> QVector { + QVector coordinates(dimensions, 0.0); + for(int i = 0; i < dimensions; ++i) { + int parameterIndex = m_enabledParamIndices[i]; + coordinates[i] = toTrustRegionCoordinate( + parameters[i], parameterIndex, + m_parameterLower[parameterIndex], + m_parameterUpper[parameterIndex]); + } + return coordinates; + }; + + auto parametersFromCoordinates = [&](const QVector& coordinates) + -> QVector { + QVector parameters(dimensions, 0.0); + for(int i = 0; i < dimensions; ++i) { + int parameterIndex = m_enabledParamIndices[i]; + parameters[i] = fromTrustRegionCoordinate( + coordinates[i], parameterIndex, + m_parameterLower[parameterIndex], + m_parameterUpper[parameterIndex]); + } + return parameters; + }; + + auto restoreEvaluationState = [&](const TrustRegionEvaluation& evaluation) { + // evaluateFitness() 会把试算参数写入 DataManager。无论候选是否接受, + // 下一次计算前都恢复到唯一的已接受工作点,防止失败试算污染后续求解。 + applyParametersToDataManager(evaluation.parameters); + m_lastObjectiveBreakdown = evaluation.breakdown; + m_lastEvaluatedLogLogData = evaluation.curve; + }; + + // 只有真实总误差更小的工作点才能发布为全局最优;曲线和诊断快照必须 + // 与参数同步更新,防止界面显示或最终精英保护使用错配的数据。 + auto publishAcceptedPoint = [&](const TrustRegionEvaluation& evaluation) { + m_previousBestFitness = m_globalBestFitness; + m_globalBestPosition = evaluation.parameters; + m_globalBestFitness = evaluation.fitness; + m_globalBestObjectiveBreakdown = evaluation.breakdown; + m_globalBestLogLogData = evaluation.curve; + emit bestCurveUpdated(m_targetLogLogData, + m_globalBestLogLogData, + m_currentIteration + 1, + m_globalBestFitness); + }; + + auto processPauseAndStop = [&]() -> bool { + while(m_isPaused && !m_shouldStop) { + QApplication::processEvents(); + msleep(100); + } + QApplication::processEvents(); + return !m_shouldStop; + }; + + // current 始终代表唯一已接受工作点。优先复用启动阶段已经真实验证的 + // 用户初始解,避免在信赖域入口重复调用一次昂贵求解器。 + TrustRegionEvaluation current; + if(m_hasValidUserSolution && + m_globalBestPosition.size() == dimensions && + trustRegionResidualsValid(m_globalBestObjectiveBreakdown) && + !m_globalBestLogLogData.isEmpty()) { + current.parameters = m_globalBestPosition; + current.coordinates = coordinatesFromParameters(current.parameters); + current.breakdown = m_globalBestObjectiveBreakdown; + current.curve = m_globalBestLogLogData; + current.fitness = m_globalBestFitness; + current.elapsedMs = 0; + current.valid = true; + } else { + // 用户初始解无效时只做一次确定性的范围中点回退;所有正值参数在对数 + // 坐标取中点,避免线性中点过分偏向跨数量级范围的上界。 + current.coordinates.fill(0.5, dimensions); + current.parameters = parametersFromCoordinates(current.coordinates); + current.valid = evaluateTrustRegionPoint( + current.parameters, + ¤t.fitness, + ¤t.breakdown, + ¤t.curve, + ¤t.elapsedMs); + writeTraceRow(-1, -1, + "trust_region_midpoint", + current.parameters, + current.fitness, + current.valid, + current.elapsedMs, + std::numeric_limits::quiet_NaN(), + current.valid ? "midpoint_valid" : "midpoint_invalid", + QVector(), + 1.0e10, + current.valid ? ¤t.breakdown : nullptr); + if(!current.valid) { + m_lastError = tr("The initial solution and parameter-range midpoint are both invalid"); + return m_shouldStop + ? PSO_USER_STOPPED + : PSO_OPTIMIZATION_FAILED; + } + publishAcceptedPoint(current); + } + + restoreEvaluationState(current); + m_convergenceHistory.append(current.fitness); + emit logMessageGenerated( + tr("Trust-region initial error: %1; evaluation budget: %2") + .arg(current.fitness, 0, 'e', 4) + .arg(maximumEvaluations)); + + if(current.fitness < m_targetError) { + return PSO_TARGET_ACHIEVED; + } + + // 在同一个真实工作点逐参数做单边差分。首选可用空间更大的方向;只有该方向 + // 求解失败时才补算反方向,因此初次建模通常每个参数只增加一次真实求解。 + auto rebuildSensitivity = [&]() -> bool { + const TrustRegionEvaluation base = current; + const int residualCount = base.breakdown.residualVector.size(); + if(residualCount <= 0) { + return false; + } + + jacobian = QVector >( + residualCount, QVector(dimensions, 0.0)); + verticalGradient.fill(0.0, dimensions); + horizontalGradient.fill(0.0, dimensions); + shapeGradient.fill(0.0, dimensions); + jacobianColumnValid.fill(false, dimensions); + + TrustRegionEvaluation bestProbe; + int bestProbeColumn = -1; + double bestProbeDelta = 0.0; + // 差分步长不超过参数范围的 4%,信赖域收缩后同步减小,但保留 0.5% + // 下限,避免步长太小使求解器数值噪声淹没真实灵敏度。 + const double finiteDifferenceStep = qMin( + sensitivityStep, + qMax(5.0e-3, trustRadius * 0.5)); + + for(int column = 0; + column < dimensions && + m_totalEvaluations < maximumEvaluations && + processPauseAndStop(); + ++column) { + // 单边差分优先选择离边界空间更大的方向;首方向求解无效时才反向 + // 补算,因此正常情况下每个参数只消耗一次真实求解。 + double positiveRoom = 1.0 - base.coordinates[column]; + double negativeRoom = base.coordinates[column]; + double preferredSign = positiveRoom >= negativeRoom ? 1.0 : -1.0; + bool columnBuilt = false; + + for(int directionAttempt = 0; + directionAttempt < 2 && + !columnBuilt && + m_totalEvaluations < maximumEvaluations; + ++directionAttempt) { + double direction = directionAttempt == 0 + ? preferredSign : -preferredSign; + double availableRoom = direction > 0.0 + ? positiveRoom : negativeRoom; + double deltaMagnitude = qMin( + finiteDifferenceStep, availableRoom); + if(deltaMagnitude < minimumCoordinateStep) { + continue; + } + + TrustRegionEvaluation probe; + probe.coordinates = base.coordinates; + probe.coordinates[column] += direction * deltaMagnitude; + probe.parameters = parametersFromCoordinates(probe.coordinates); + probe.valid = evaluateTrustRegionPoint( + probe.parameters, + &probe.fitness, + &probe.breakdown, + &probe.curve, + &probe.elapsedMs); + + QString decision = probe.valid + ? "sensitivity_valid" + : (directionAttempt == 0 + ? "sensitivity_retry_opposite" + : "sensitivity_invalid"); + writeTraceRow(m_currentIteration, + column, + "trust_region_sensitivity", + probe.parameters, + probe.fitness, + probe.valid, + probe.elapsedMs, + std::numeric_limits::quiet_NaN(), + decision, + base.parameters, + base.fitness, + probe.valid ? &probe.breakdown : nullptr); + + if(!probe.valid) { + restoreEvaluationState(base); + continue; + } + + double delta = probe.coordinates[column] - + base.coordinates[column]; + if(qAbs(delta) < minimumCoordinateStep || + probe.breakdown.residualVector.size() != residualCount) { + restoreEvaluationState(base); + continue; + } + + // 第 column 列是固定残差向量相对内部参数坐标的有限差分: + // J[:,column] = (r_probe-r_base)/delta。 + for(int row = 0; row < residualCount; ++row) { + jacobian[row][column] = + (probe.breakdown.residualVector[row] - + base.breakdown.residualVector[row]) / delta; + } + + // 有符号诊断量只有在基点和试算点都可靠时才能计算方向梯度; + // shapeLoss 无方向可靠性标志,始终记录其局部变化率。 + if(base.breakdown.verticalReliable && + probe.breakdown.verticalReliable && + !base.breakdown.registrationAmbiguous && + !probe.breakdown.registrationAmbiguous) { + verticalGradient[column] = + (probe.breakdown.verticalCommonBias - + base.breakdown.verticalCommonBias) / delta; + } + if(base.breakdown.horizontalReliable && + probe.breakdown.horizontalReliable && + !base.breakdown.registrationAmbiguous && + !probe.breakdown.registrationAmbiguous) { + horizontalGradient[column] = + (probe.breakdown.horizontalPhysicalShift - + base.breakdown.horizontalPhysicalShift) / delta; + } + shapeGradient[column] = + (probe.breakdown.shapeLoss - + base.breakdown.shapeLoss) / delta; + jacobianColumnValid[column] = true; + columnBuilt = true; + + if(probe.fitness < base.fitness && + (!bestProbe.valid || + probe.fitness < bestProbe.fitness)) { + bestProbe = probe; + bestProbeColumn = column; + bestProbeDelta = delta; + } + restoreEvaluationState(base); + } + } + + int validColumnCount = 0; + for(int i = 0; i < jacobianColumnValid.size(); ++i) { + if(jacobianColumnValid[i]) { + ++validColumnCount; + } + } + if(validColumnCount == 0 || m_shouldStop) { + restoreEvaluationState(base); + return false; + } + + // 灵敏度试算本身若找到更优真实解也应保留。所有列先基于同一个 base + // 建完,再用该已知割线把 Jacobian 平移到新工作点,避免边算边移动基点。 + if(bestProbe.valid && bestProbeColumn >= 0) { + QVector acceptedStep(dimensions, 0.0); + acceptedStep[bestProbeColumn] = bestProbeDelta; + updateTrustRegionJacobian( + &jacobian, + base.breakdown.residualVector, + bestProbe.breakdown.residualVector, + acceptedStep); + if(base.breakdown.verticalReliable && + bestProbe.breakdown.verticalReliable) { + updateTrustRegionScalarGradient( + &verticalGradient, + base.breakdown.verticalCommonBias, + bestProbe.breakdown.verticalCommonBias, + acceptedStep); + } + if(base.breakdown.horizontalReliable && + bestProbe.breakdown.horizontalReliable) { + updateTrustRegionScalarGradient( + &horizontalGradient, + base.breakdown.horizontalPhysicalShift, + bestProbe.breakdown.horizontalPhysicalShift, + acceptedStep); + } + updateTrustRegionScalarGradient( + &shapeGradient, + base.breakdown.shapeLoss, + bestProbe.breakdown.shapeLoss, + acceptedStep); + + current = bestProbe; + publishAcceptedPoint(current); + restoreEvaluationState(current); + m_convergenceHistory.append(current.fitness); + writeTraceRow(m_currentIteration, + bestProbeColumn, + "trust_region_sensitivity_accept", + current.parameters, + current.fitness, + true, + 0, + std::numeric_limits::quiet_NaN(), + "accepted_cached_probe", + current.parameters, + current.fitness, + ¤t.breakdown); + emit logMessageGenerated( + tr("Sensitivity probe accepted: error reduced to %1") + .arg(current.fitness, 0, 'e', 4)); + } else { + restoreEvaluationState(current); + } + + acceptedSinceRebuild = 0; + movementSinceRebuild = 0.0; + consecutiveRejectedSteps = 0; + rebuildRequested = false; + // 若重建过程中接受了试算点,当前模型已通过割线平移而不是在新点完整 + // 重算;再遇到最小半径停滞时仍允许做一次真正的新点重建。 + modelRebuiltAtMinimumRadius = + trustRadius <= minimumTrustRadius * 1.01 && + !bestProbe.valid; + emit logMessageGenerated( + tr("Sensitivity model rebuilt: %1/%2 parameter columns valid") + .arg(validColumnCount) + .arg(dimensions)); + return true; + }; + + int completedIterations = 0; + for(int iteration = 0; + iteration < m_maxIterations && + m_totalEvaluations < maximumEvaluations && + !m_shouldStop; + ++iteration) { + m_currentIteration = iteration; + completedIterations = iteration + 1; + + if(!processPauseAndStop()) { + break; + } + if(rebuildRequested) { + if(!rebuildSensitivity()) { + stopReason = m_shouldStop + ? PSO_USER_STOPPED + : PSO_LOCAL_OPTIMUM; + break; + } + if(current.fitness < m_targetError) { + stopReason = PSO_TARGET_ACHIEVED; + break; + } + if(m_totalEvaluations >= maximumEvaluations) { + stopReason = PSO_MAX_ITERATIONS; + break; + } + } + + // 先确定当前最突出的可靠诊断误差,用其梯度回答“哪些参数最能改善 + // 当前问题”;实际 LM 方向仍由完整残差梯度和 Jacobian 共同计算。 + int dominantComponent = trustRegionDominantComponent( + current.breakdown, diagnosisThreshold); + const QVector* componentGradient = nullptr; + if(dominantComponent == TRUST_REGION_VERTICAL_COMPONENT) { + componentGradient = &verticalGradient; + } else if(dominantComponent == TRUST_REGION_HORIZONTAL_COMPONENT) { + componentGradient = &horizontalGradient; + } else if(dominantComponent == TRUST_REGION_SHAPE_COMPONENT) { + componentGradient = &shapeGradient; + } + + // 主目标采用 0.5*||r||^2,其对参数的梯度为 J^T*r。这里不再叠加 + // vertical/horizontal/shape,保证诊断分量不会改变真实接受目标。 + QVector totalGradient(dimensions, 0.0); + for(int column = 0; column < dimensions; ++column) { + if(!jacobianColumnValid[column]) { + continue; + } + for(int row = 0; row < jacobian.size(); ++row) { + totalGradient[column] += + jacobian[row][column] * + current.breakdown.residualVector[row]; + } + } + + // 每轮最多联合调整三个灵敏参数。按当前诊断梯度绝对值由大到小选取, + // 并剔除 Jacobian 响应过度共线的列,降低弱可辨识参数互相补偿的风险。 + QVector selectedColumns; + QVector alreadyConsidered(dimensions, false); + for(int selection = 0; selection < qMin(3, dimensions); ++selection) { + int bestColumn = -1; + double bestScore = 0.0; + for(int column = 0; column < dimensions; ++column) { + if(alreadyConsidered[column] || + !jacobianColumnValid[column]) { + continue; + } + + double score = componentGradient + ? qAbs((*componentGradient)[column]) + : qAbs(totalGradient[column]); + if(!isFiniteNumber(score) || score <= bestScore) { + continue; + } + + bool excessivelyCorrelated = false; + for(int selectedIndex = 0; + selectedIndex < selectedColumns.size(); + ++selectedIndex) { + if(trustRegionJacobianColumnCorrelation( + jacobian, + column, + selectedColumns[selectedIndex]) > + columnCorrelationLimit) { + excessivelyCorrelated = true; + break; + } + } + if(!excessivelyCorrelated) { + bestColumn = column; + bestScore = score; + } + } + if(bestColumn < 0 || bestScore <= 1.0e-12) { + break; + } + selectedColumns.append(bestColumn); + alreadyConsidered[bestColumn] = true; + } + + // 诊断梯度接近零时,说明该分量在当前局部无法可靠选参,退回完整残差 + // 梯度,但接受标准仍然只有真实 total,诊断值不会重复计入目标函数。 + if(selectedColumns.isEmpty() && componentGradient) { + dominantComponent = TRUST_REGION_TOTAL_COMPONENT; + componentGradient = nullptr; + alreadyConsidered.fill(false, dimensions); + for(int selection = 0; + selection < qMin(3, dimensions); + ++selection) { + int bestColumn = -1; + double bestScore = 0.0; + for(int column = 0; column < dimensions; ++column) { + if(alreadyConsidered[column] || + !jacobianColumnValid[column]) { + continue; + } + double score = qAbs(totalGradient[column]); + if(score <= bestScore) { + continue; + } + bool excessivelyCorrelated = false; + for(int selectedIndex = 0; + selectedIndex < selectedColumns.size(); + ++selectedIndex) { + if(trustRegionJacobianColumnCorrelation( + jacobian, + column, + selectedColumns[selectedIndex]) > + columnCorrelationLimit) { + excessivelyCorrelated = true; + break; + } + } + if(!excessivelyCorrelated) { + bestColumn = column; + bestScore = score; + } + } + if(bestColumn < 0 || bestScore <= 1.0e-12) { + break; + } + selectedColumns.append(bestColumn); + alreadyConsidered[bestColumn] = true; + } + } + + // 当前局部没有可用方向时先缩小半径并重建灵敏度;只有已经在最小 + // 半径完整重建后仍无方向,才把它判定为局部最优。 + if(selectedColumns.isEmpty()) { + if(trustRadius <= minimumTrustRadius * 1.01 && + modelRebuiltAtMinimumRadius) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + damping = qMin(1.0e8, damping * 4.0); + rebuildRequested = true; + continue; + } + + // 在选中参数子空间构造 LM 正规方程: + // (J^T*J + damping*diag(J^T*J))*step = -J^T*r。 + // 对角缩放使不同参数列的灵敏度量级差异不会直接改变阻尼强弱。 + const int selectedCount = selectedColumns.size(); + QVector > normalMatrix( + selectedCount, QVector(selectedCount, 0.0)); + QVector rightHandSide(selectedCount, 0.0); + for(int left = 0; left < selectedCount; ++left) { + int leftColumn = selectedColumns[left]; + rightHandSide[left] = -totalGradient[leftColumn]; + for(int right = 0; right < selectedCount; ++right) { + int rightColumn = selectedColumns[right]; + for(int row = 0; row < jacobian.size(); ++row) { + normalMatrix[left][right] += + jacobian[row][leftColumn] * + jacobian[row][rightColumn]; + } + } + double diagonalScale = qMax( + 1.0e-10, normalMatrix[left][left]); + normalMatrix[left][left] += damping * diagonalScale; + } + + QVector selectedStep; + bool solved = solveTrustRegionLinearSystem( + normalMatrix, rightHandSide, &selectedStep); + QVector coordinateStep(dimensions, 0.0); + if(solved) { + for(int i = 0; i < selectedCount; ++i) { + coordinateStep[selectedColumns[i]] = selectedStep[i]; + } + } + + double stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); + if(!solved || !isFiniteNumber(stepNorm) || + stepNorm < minimumCoordinateStep) { + // 正规方程退化时使用投影最速下降方向,仍只移动本轮已选择的参数。 + coordinateStep.fill(0.0, dimensions); + double gradientNormSquared = 0.0; + for(int i = 0; i < selectedCount; ++i) { + int column = selectedColumns[i]; + double stepDirection = -totalGradient[column]; + if((current.coordinates[column] <= minimumCoordinateStep && + stepDirection < 0.0) || + (current.coordinates[column] >= + 1.0 - minimumCoordinateStep && + stepDirection > 0.0)) { + stepDirection = 0.0; + } + coordinateStep[column] = stepDirection; + gradientNormSquared += stepDirection * stepDirection; + } + double gradientNorm = qSqrt(gradientNormSquared); + if(gradientNorm > minimumCoordinateStep) { + double scale = trustRadius / gradientNorm; + for(int i = 0; i < selectedCount; ++i) { + int column = selectedColumns[i]; + coordinateStep[column] *= scale; + } + } + stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); + } + + // LM 解只给出局部模型建议方向;若超出当前信赖半径,保持方向不变并 + // 等比例截短,避免一次试算离开 Jacobian 有效的局部区域。 + if(stepNorm > trustRadius && stepNorm > 0.0) { + double scale = trustRadius / stepNorm; + for(int i = 0; i < coordinateStep.size(); ++i) { + coordinateStep[i] *= scale; + } + } + + // 将 LM 步长投影到用户给定的参数范围,实际用于预测下降的也是投影后步长。 + QVector candidateCoordinates = current.coordinates; + for(int i = 0; i < dimensions; ++i) { + candidateCoordinates[i] = qBound( + 0.0, + current.coordinates[i] + coordinateStep[i], + 1.0); + coordinateStep[i] = candidateCoordinates[i] - + current.coordinates[i]; + } + stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); + + // 用线性模型 r_new ~= r_current + J*step 预测稳健残差,再用平方能量 + // 的下降量与真实候选下降量比较,作为调整阻尼和半径的依据。 + QVector predictedResidual = + current.breakdown.residualVector; + for(int row = 0; row < jacobian.size(); ++row) { + for(int column = 0; column < dimensions; ++column) { + predictedResidual[row] += + jacobian[row][column] * coordinateStep[column]; + } + } + double predictedReduction = 0.5 * + (trustRegionSquaredNorm(current.breakdown.residualVector) - + trustRegionSquaredNorm(predictedResidual)); + + // 无实际移动或模型预测不下降时没有必要调用昂贵求解器。将它按一次 + // 拒绝处理,并在连续发生后重建灵敏度,防止继续沿失效模型试算。 + if(stepNorm < minimumCoordinateStep || + !isFiniteNumber(predictedReduction) || + predictedReduction <= 1.0e-14) { + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + damping = qMin(1.0e8, damping * 4.0); + ++consecutiveRejectedSteps; + if(consecutiveRejectedSteps >= 2) { + if(trustRadius <= minimumTrustRadius * 1.01 && + modelRebuiltAtMinimumRadius) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } + rebuildRequested = true; + } + continue; + } + + TrustRegionEvaluation candidate; + candidate.coordinates = candidateCoordinates; + candidate.parameters = parametersFromCoordinates(candidate.coordinates); + candidate.valid = evaluateTrustRegionPoint( + candidate.parameters, + &candidate.fitness, + &candidate.breakdown, + &candidate.curve, + &candidate.elapsedMs); + + if(!candidate.valid) { + // 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和 + // 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。 + ++consecutiveSolverFailures; + ++consecutiveRejectedSteps; + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + damping = qMin(1.0e8, damping * 4.0); + writeTraceRow(m_currentIteration, + -1, + "trust_region_candidate", + candidate.parameters, + candidate.fitness, + false, + candidate.elapsedMs, + std::numeric_limits::quiet_NaN(), + "solver_invalid", + current.parameters, + current.fitness, + nullptr); + restoreEvaluationState(current); + if(consecutiveRejectedSteps >= 2) { + rebuildRequested = true; + } + if(consecutiveSolverFailures >= m_maxConsecutiveFailures) { + stopReason = PSO_CONSECUTIVE_FAILURES; + break; + } + continue; + } + + consecutiveSolverFailures = 0; + // 有效候选即使最终被拒绝,也提供了一条真实割线,可用于修正下一轮 + // 局部模型;是否成为新工作点仍只由下面的 total 严格比较决定。 + const AutoFitObjectiveBreakdown oldBreakdown = current.breakdown; + updateTrustRegionJacobian( + &jacobian, + oldBreakdown.residualVector, + candidate.breakdown.residualVector, + coordinateStep); + if(oldBreakdown.verticalReliable && + candidate.breakdown.verticalReliable && + !oldBreakdown.registrationAmbiguous && + !candidate.breakdown.registrationAmbiguous) { + updateTrustRegionScalarGradient( + &verticalGradient, + oldBreakdown.verticalCommonBias, + candidate.breakdown.verticalCommonBias, + coordinateStep); + } + if(oldBreakdown.horizontalReliable && + candidate.breakdown.horizontalReliable && + !oldBreakdown.registrationAmbiguous && + !candidate.breakdown.registrationAmbiguous) { + updateTrustRegionScalarGradient( + &horizontalGradient, + oldBreakdown.horizontalPhysicalShift, + candidate.breakdown.horizontalPhysicalShift, + coordinateStep); + } + updateTrustRegionScalarGradient( + &shapeGradient, + oldBreakdown.shapeLoss, + candidate.breakdown.shapeLoss, + coordinateStep); + + // reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确; + // 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。 + double actualReduction = 0.5 * + (current.fitness * current.fitness - + candidate.fitness * candidate.fitness); + double reductionRatio = actualReduction / predictedReduction; + bool accepted = candidate.fitness < current.fitness; + QString componentName = trustRegionComponentName(dominantComponent); + + if(accepted) { + // 真实总误差下降后才正式替换 current,并同步发布参数、曲线和诊断。 + // 模型预测可靠时减小阻尼并可扩大半径,预测较差时保守收缩。 + current = candidate; + publishAcceptedPoint(current); + restoreEvaluationState(current); + ++acceptedSinceRebuild; + movementSinceRebuild += stepNorm; + consecutiveRejectedSteps = 0; + m_convergenceHistory.append(current.fitness); + + if(reductionRatio > 0.75) { + damping = qMax(1.0e-8, damping * 0.5); + if(stepNorm >= trustRadius * 0.8) { + trustRadius = qMin( + maximumTrustRadius, trustRadius * 1.6); + } + } else if(reductionRatio > 0.25) { + damping = qMax(1.0e-8, damping * 0.8); + } else { + damping = qMin(1.0e8, damping * 2.0); + trustRadius = qMax( + minimumTrustRadius, trustRadius * 0.75); + } + + if(acceptedSinceRebuild >= 6 || + movementSinceRebuild >= 0.30) { + rebuildRequested = true; + } + modelRebuiltAtMinimumRadius = false; + } else { + // 拒绝时 candidate 只保留在 trace 中,DataManager 和内存状态都恢复 + // 到 current。连续拒绝说明割线模型可能失真,因此请求重新试算灵敏度。 + ++consecutiveRejectedSteps; + damping = qMin(1.0e8, damping * 4.0); + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + restoreEvaluationState(current); + if(consecutiveRejectedSteps >= 2) { + rebuildRequested = true; + } + } + + writeTraceRow(m_currentIteration, + -1, + "trust_region_candidate", + candidate.parameters, + candidate.fitness, + true, + candidate.elapsedMs, + std::numeric_limits::quiet_NaN(), + accepted + ? QString("accepted_%1").arg(componentName) + : QString("rejected_%1").arg(componentName), + current.parameters, + current.fitness, + &candidate.breakdown); + + emit logMessageGenerated( + tr("Iteration %1: focus=%2, parameters=%3, error=%4, result=%5") + .arg(iteration + 1) + .arg(componentName) + .arg(selectedColumns.size()) + .arg(candidate.fitness, 0, 'e', 4) + .arg(accepted ? tr("accepted") : tr("rejected"))); + emit progressUpdated(iteration + 1, m_globalBestFitness); + + if(current.fitness < m_targetError) { + stopReason = PSO_TARGET_ACHIEVED; + break; + } + if(trustRadius <= minimumTrustRadius * 1.01 && + consecutiveRejectedSteps >= 2) { + if(modelRebuiltAtMinimumRadius) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } + rebuildRequested = true; + } + } + + if(completedIterations > 0) { + m_currentIteration = completedIterations - 1; + } + restoreEvaluationState(current); + + if(m_shouldStop) { + return PSO_USER_STOPPED; + } + if(current.fitness < m_targetError) { + return PSO_TARGET_ACHIEVED; + } + if(stopReason == PSO_CONSECUTIVE_FAILURES || + stopReason == PSO_LOCAL_OPTIMUM || + stopReason == PSO_OPTIMIZATION_FAILED) { + return stopReason; + } + return PSO_MAX_ITERATIONS; } void nmCalculationAutoFitPSO::updateParticle(int particleIndex) @@ -3647,6 +4981,7 @@ void nmCalculationAutoFitPSO::updateParticle(int particleIndex) // 直接复用真实误差和曲线,避免一次重复 DLL 调用且不改变 PSO 数学状态。 particle.fitness = m_userInitialFitness; particle.currentLogLogData = m_userInitialLogLogData; + particle.currentObjectiveBreakdown = m_userInitialObjectiveBreakdown; particle.lastEvaluationElapsedMs = 0; particle.evaluatedThisIteration = true; particle.lastEvaluationSuccess = true; @@ -3657,6 +4992,7 @@ void nmCalculationAutoFitPSO::updateParticle(int particleIndex) evalTimer.start(); particle.fitness = evaluateFitness(particle.position); particle.currentLogLogData = m_lastEvaluatedLogLogData; + particle.currentObjectiveBreakdown = m_lastObjectiveBreakdown; particle.lastEvaluationElapsedMs = evalTimer.elapsed(); particle.evaluatedThisIteration = true; particle.lastEvaluationSuccess = (particle.fitness < 1e9); @@ -3698,6 +5034,7 @@ void nmCalculationAutoFitPSO::updateParticle(int particleIndex) particle.bestFitness = particle.fitness; particle.bestPosition = particle.position; particle.bestLogLogData = particle.currentLogLogData; + particle.bestObjectiveBreakdown = particle.currentObjectiveBreakdown; if(!preserveSurrogateGuide) { particle.guideBestPosition = particle.position; @@ -3746,6 +5083,7 @@ void nmCalculationAutoFitPSO::updateGlobalBest() m_globalBestFitness = particle.bestFitness; m_globalBestPosition = particle.bestPosition; m_globalBestLogLogData = particle.bestLogLogData; + m_globalBestObjectiveBreakdown = particle.bestObjectiveBreakdown; globalBestUpdated = true; emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness); } @@ -3765,6 +5103,7 @@ void nmCalculationAutoFitPSO::updateGlobalBest() m_globalBestFitness = m_userInitialFitness; m_globalBestPosition = m_userInitialSolution; m_globalBestLogLogData = m_userInitialLogLogData; + m_globalBestObjectiveBreakdown = m_userInitialObjectiveBreakdown; emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness); } } @@ -3841,6 +5180,7 @@ void nmCalculationAutoFitPSO::updateVelocityAndPosition() int paramIndex = m_enabledParamIndices[j]; double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex]; double maxVel = range * VELOCITY_LIMIT_FACTOR; + particle.velocity[j] = qMax(-maxVel, qMin(maxVel, particle.velocity[j])); // 更新位置 @@ -4650,10 +5990,8 @@ void nmCalculationAutoFitPSO::saveOptimizationResult() void nmCalculationAutoFitPSO::validateAndProtectFinalResult() { - // 最终精英保护。 - // PSO 是随机启发式算法,某些工况下可能没有找到比用户初始模型更好的结果。 - // 这里用初始解误差与最终全局最优误差做比较,如果改进不足,就恢复初始解, - // 避免“自动拟合”把已有模型调坏。 + // 最终精英保护只阻止无效结果或真正变差的结果。任何真实误差下降都应保留, + // 不能再用固定百分比门槛把已经找到的更优解恢复成初始值。 if(!m_hasValidUserSolution) { emit logMessageGenerated(tr("No initial solution for elite protection")); return; @@ -4668,28 +6006,36 @@ void nmCalculationAutoFitPSO::validateAndProtectFinalResult() emit logMessageGenerated(tr("Comparing results: Initial=%1, Final=%2") .arg(initialFitness, 0, 'e', 4).arg(finalFitness, 0, 'e', 4)); - // 计算改进程度 - double improvement = initialFitness - finalFitness; - double relativeImprovement = improvement / qMax(1e-10, qAbs(initialFitness)); - - emit logMessageGenerated(tr("Improvement: %1 (%2%)") - .arg(improvement, 0, 'e', 4).arg(relativeImprovement * 100, 0, 'f', 2)); + bool finalValid = isFiniteNumber(finalFitness) && + finalFitness < 1.0e9 && + m_globalBestPosition.size() == + m_userInitialSolution.size() && + !m_globalBestLogLogData.isEmpty() && + m_globalBestObjectiveBreakdown.valid; + if(finalValid) { + double improvement = initialFitness - finalFitness; + double relativeImprovement = + improvement / qMax(1.0e-10, qAbs(initialFitness)); + emit logMessageGenerated(tr("Improvement: %1 (%2%)") + .arg(improvement, 0, 'e', 4) + .arg(relativeImprovement * 100, 0, 'f', 2)); + } - if(relativeImprovement < m_improvementThreshold) { - emit logMessageGenerated(tr("Elite protection triggered: insufficient improvement")); - emit logMessageGenerated(tr("Threshold: %1%, Actual: %2%") - .arg(m_improvementThreshold * 100, 0, 'f', 2) - .arg(relativeImprovement * 100, 0, 'f', 4)); + if(!finalValid || finalFitness > initialFitness) { + emit logMessageGenerated( + tr("Elite protection triggered: final result is invalid or worse than initial")); emit logMessageGenerated(tr("Restoring initial solution as final result")); m_globalBestFitness = initialFitness; m_globalBestPosition = m_userInitialSolution; m_globalBestLogLogData = m_userInitialLogLogData; + m_globalBestObjectiveBreakdown = m_userInitialObjectiveBreakdown; emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness); emit logMessageGenerated(tr("Initial solution restored successfully")); } else { - emit logMessageGenerated(tr("Final result validated - significant improvement achieved")); + emit logMessageGenerated( + tr("Final result validated - solution is not worse than initial")); } } @@ -4997,17 +6343,16 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( const QVector >& target, const QVector >& result) const { - // 这里只负责“曲线比较和误差诊断”,不根据诊断结果直接修改任何拟合参数。 - // 调用方可以读取 m_lastObjectiveBreakdown 做诊断或展示;本函数本身不修改参数。 - // 在统一的对数时间网格上计算压力和导数残差,并拆分为上下、左右、形状误差。 + // 主目标只比较固定网格上的压力和导数残差;上下、左右和形状只负责诊断 + // 误差来源和选择参数,避免同一残差在 total 中被重复计算。整个计算过程均 + // 位于 log(time)-log(value) 坐标,因此得到的是相对尺度偏差而非原始压力量纲。 const double invalidLoss = 1.0e10; - const double valueFloor = 1.0e-12; // 导数接近零时的对数下限,避免 log(0)。 - const double huberDelta = qLn(1.2); // 约对应 20% 的相对偏差拐点。 - const double minimumCoverage = 0.95; // 点数比例和连续跨度比例都至少接近 95%。 - const int numPoints = 50; // 固定网格使不同候选的损失具有可比性。 - // 目标函数的主排序项为 0.5*pressureLoss + 0.5*derivativeLoss; - // coveragePenalty 只在接近覆盖边界时提供连续惩罚,上下、左右和形状分量 - // 会写入 m_lastObjectiveBreakdown,供后续按误差类型选择参数。 + const double valueFloor = 1.0e-12; + // Huber 转折点 qLn(1.2) 对应约 20% 的倍率偏差;小偏差保持平方惩罚, + // 更大的局部尖峰改为近似线性惩罚,避免单点支配整条曲线。 + const double huberDelta = qLn(1.2); + const double minimumCoverage = 0.95; + const int numPoints = 50; m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); if(!validateLogLogData(target) || !validateLogLogData(result)) { @@ -5015,9 +6360,8 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } try { - // 清洗曲线并拆成压力、导数两条曲线。导数可以为负,所以统一使用绝对值 - // 进入双对数空间;时间和压力必须为正,否则无法进行对数插值。这里的清洗 - // 只丢弃无法比较的采样点,不改变原始曲线或求解器输出。 + // 无法比较的采样行先跳过;有限但非正的导数无法进入双对数空间, + // 当前数据又没有逐点有效掩码,因此遇到这种导数时判本次评价无效。 auto prepareCurve = [valueFloor](const QVector >& data, QVector* pressure, QVector* derivative) -> bool { @@ -5028,37 +6372,41 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } for(int i = 0; i < data[0].size(); ++i) { - if(!isFiniteNumber(data[0][i]) || !isFiniteNumber(data[1][i]) || - !isFiniteNumber(data[2][i]) || data[0][i] <= 0.0 || + if(!isFiniteNumber(data[0][i]) || + !isFiniteNumber(data[1][i]) || + !isFiniteNumber(data[2][i]) || + data[0][i] <= 0.0 || data[1][i] <= 0.0) { continue; } + if(data[2][i] <= 0.0) { + return false; + } pressure->append(QPointF(data[0][i], data[1][i])); - derivative->append(QPointF(data[0][i], - qMax(qAbs(data[2][i]), valueFloor))); + derivative->append( + QPointF(data[0][i], qMax(data[2][i], valueFloor))); } - // 插值要求时间严格递增。重复时间点保留排序后的最后一个值, - // 避免重复横坐标导致对数插值分母为零。压力和导数分别去重, - // 这样即使某条曲线存在重复时间点,也不会污染另一条曲线的插值。 + // 求解器输出可能不是严格升序,且同一时刻可能出现重复记录。 + // 插值前统一排序并让后出现的记录覆盖同时间旧值,保证横坐标严格递增。 auto sortAndUnique = [](QVector* curve) { - std::stable_sort(curve->begin(), curve->end(), - [](const QPointF& left, const QPointF& right) { - return left.x() < right.x(); - }); + std::stable_sort( + curve->begin(), curve->end(), + [](const QPointF& left, const QPointF& right) { + return left.x() < right.x(); + }); QVector unique; unique.reserve(curve->size()); - for(int i = 0; i < curve->size(); ++i) { - if(unique.isEmpty() || curve->at(i).x() > unique.last().x()) { + if(unique.isEmpty() || + curve->at(i).x() > unique.last().x()) { unique.append(curve->at(i)); } else { unique[unique.size() - 1] = curve->at(i); } } - *curve = unique; }; @@ -5071,44 +6419,77 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( QVector targetDerivative; QVector resultPressure; QVector resultDerivative; - if(!prepareCurve(target, &targetPressure, &targetDerivative) || !prepareCurve(result, &resultPressure, &resultDerivative)) { return invalidLoss; } - // 在 log(time)-log(value) 空间做线性插值,而不是在线性坐标直接插值。 - // 这样可以保持双对数曲线的时间尺度和数量级特征;返回值是 - // log(abs(value)),后续残差因此可以直接解释为相对幅值误差。 - auto interpolateLogValue = [valueFloor](const QVector& curve, - double x, - double* value) -> bool { - if(!value || curve.size() < 2 || x < curve.first().x() || - x > curve.last().x() || x <= 0.0) { + // 在双对数坐标中插值。二分定位用于后面的多次水平配准试算。 + auto interpolateLogValue = [valueFloor]( + const QVector& curve, + double x, + double* value) -> bool { + if(!value || curve.size() < 2 || x <= 0.0 || + x < curve.first().x() || x > curve.last().x()) { return false; } - int right = 1; - - while(right < curve.size() && curve[right].x() < x) { - ++right; + int low = 0; + int high = curve.size() - 1; + while(low < high) { + int middle = low + (high - low) / 2; + if(curve[middle].x() < x) { + low = middle + 1; + } else { + high = middle; + } } - right = qMin(right, curve.size() - 1); - int left = qMax(0, right - 1); + int right = qBound(1, low, curve.size() - 1); + int left = right - 1; double leftLogX = qLn(curve[left].x()); double rightLogX = qLn(curve[right].x()); double denominator = rightLogX - leftLogX; - double leftLogY = qLn(qMax(qAbs(curve[left].y()), valueFloor)); - double rightLogY = qLn(qMax(qAbs(curve[right].y()), valueFloor)); + double leftLogY = + qLn(qMax(qAbs(curve[left].y()), valueFloor)); + double rightLogY = + qLn(qMax(qAbs(curve[right].y()), valueFloor)); if(qAbs(denominator) <= 1.0e-12) { *value = leftLogY; } else { double ratio = (qLn(x) - leftLogX) / denominator; - *value = leftLogY + ratio * (rightLogY - leftLogY); + *value = leftLogY + + ratio * (rightLogY - leftLogY); } + return isFiniteNumber(*value); + }; + // 覆盖率通过后若只缺少首尾少量点,用模拟曲线自身的端点斜率作短距离 + // 双对数外推。该外推只用于主损失的固定网格,不参与水平配准搜索。 + auto extrapolateEndpointLogValue = [valueFloor]( + const QVector& curve, + double x, + double* value) -> bool { + if(!value || curve.size() < 2 || x <= 0.0) { + return false; + } + int left = x < curve.first().x() + ? 0 + : curve.size() - 2; + int right = left + 1; + double leftLogX = qLn(curve[left].x()); + double rightLogX = qLn(curve[right].x()); + double denominator = rightLogX - leftLogX; + if(qAbs(denominator) <= 1.0e-12) { + return false; + } + double leftLogY = + qLn(qMax(qAbs(curve[left].y()), valueFloor)); + double rightLogY = + qLn(qMax(qAbs(curve[right].y()), valueFloor)); + double ratio = (qLn(x) - leftLogX) / denominator; + *value = leftLogY + ratio * (rightLogY - leftLogY); return isFiniteNumber(*value); }; @@ -5116,14 +6497,11 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( const double targetMaxX = targetPressure.last().x(); const double resultMinX = resultPressure.first().x(); const double resultMaxX = resultPressure.last().x(); - if(targetMinX <= 0.0 || targetMaxX <= targetMinX || resultMinX <= 0.0 || resultMaxX <= resultMinX) { return invalidLoss; } - // 目标曲线的完整时间范围作为统一比较区间。若候选曲线覆盖不足, - // 后面的 coverage 检查会拒绝它,防止候选通过缩短时间范围来降低误差。 QVector commonX(numPoints); QVector commonLogX(numPoints); QVector targetLogPressure(numPoints); @@ -5131,34 +6509,34 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( const double targetLogMinX = qLn(targetMinX); const double targetLogMaxX = qLn(targetMaxX); + // 固定使用目标曲线的完整 log-time 网格,候选之间不会因采样点不同而失去可比性。 for(int i = 0; i < numPoints; ++i) { double logX = targetLogMinX + static_cast(i) * - (targetLogMaxX - targetLogMinX) / (numPoints - 1); + (targetLogMaxX - targetLogMinX) / + (numPoints - 1); commonLogX[i] = logX; commonX[i] = qExp(logX); - if(!interpolateLogValue(targetPressure, commonX[i], - &targetLogPressure[i]) || - !interpolateLogValue(targetDerivative, commonX[i], - &targetLogDerivative[i])) { + if(!interpolateLogValue( + targetPressure, commonX[i], + &targetLogPressure[i]) || + !interpolateLogValue( + targetDerivative, commonX[i], + &targetLogDerivative[i])) { return invalidLoss; } } - // 残差采用“模拟减目标”,因此正值表示模拟曲线在对数幅值上高于目标, - // 负值表示模拟曲线偏低。NaN 表示该网格点不在模拟曲线支持范围内, - // 后续统计会自动跳过,但覆盖率检查仍会限制候选不能靠缺失数据降低损失。 - QVector pressureResidual(numPoints, - std::numeric_limits::quiet_NaN()); - QVector derivativeResidual(numPoints, - std::numeric_limits::quiet_NaN()); - QVector pressureSlope(numPoints, 0.0); - QVector derivativeSlope(numPoints, 0.0); + QVector pressureResidual( + numPoints, std::numeric_limits::quiet_NaN()); + QVector derivativeResidual( + numPoints, std::numeric_limits::quiet_NaN()); int firstSupported = -1; int lastSupported = -1; int supportedCount = 0; + // 残差定义为“模拟减目标”:正值表示模拟曲线偏高,负值表示偏低。 for(int i = 0; i < numPoints; ++i) { if(commonX[i] < resultMinX || commonX[i] > resultMaxX) { continue; @@ -5166,87 +6544,95 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( double resultLogPressure = 0.0; double resultLogDerivative = 0.0; - - if(!interpolateLogValue(resultPressure, commonX[i], - &resultLogPressure) || - !interpolateLogValue(resultDerivative, commonX[i], - &resultLogDerivative)) { - continue; + if(!interpolateLogValue( + resultPressure, commonX[i], + &resultLogPressure) || + !interpolateLogValue( + resultDerivative, commonX[i], + &resultLogDerivative)) { + // 已位于结果时间范围内却无法插值说明数据存在内部断点,不能补线。 + return invalidLoss; } - pressureResidual[i] = resultLogPressure - targetLogPressure[i]; - derivativeResidual[i] = resultLogDerivative - targetLogDerivative[i]; + pressureResidual[i] = + resultLogPressure - targetLogPressure[i]; + derivativeResidual[i] = + resultLogDerivative - targetLogDerivative[i]; ++supportedCount; - if(firstSupported < 0) { firstSupported = i; } - lastSupported = i; } - // 同时使用点覆盖率和连续时间跨度覆盖率,避免只覆盖少数离散点也被判定为完整。 + // 覆盖率同时约束“有效点数量”和“连续时间跨度”。取两者较小值可避免 + // 点数很多但只集中在局部时段的候选被误认为覆盖充分。 AutoFitObjectiveBreakdown breakdown; breakdown.coverage = supportedCount > 0 - ? static_cast(supportedCount) / numPoints + ? static_cast(supportedCount) / + numPoints : 0.0; - if(firstSupported >= 0 && lastSupported >= firstSupported) { - double span = qMax(1.0e-12, targetLogMaxX - targetLogMinX); - double coveredSpan = commonLogX[lastSupported] - - commonLogX[firstSupported]; - breakdown.coverage = qMin(breakdown.coverage, - qMax(0.0, coveredSpan / span)); + double targetSpan = + qMax(1.0e-12, targetLogMaxX - targetLogMinX); + double coveredSpan = + commonLogX[lastSupported] - commonLogX[firstSupported]; + breakdown.coverage = qMin( + breakdown.coverage, + qMax(0.0, coveredSpan / targetSpan)); } - // 覆盖率越接近 1,惩罚越小;覆盖不足 minimumCoverage 时直接返回无效损失。 - double coverageGap = qMax(0.0, 1.0 - breakdown.coverage); - breakdown.coveragePenalty = - qPow(coverageGap / (1.0 - minimumCoverage), 2.0); - + // 覆盖率只判断候选是否有效,不再加入固定惩罚,避免所有误差被整体抬高。 if(breakdown.coverage < minimumCoverage) { breakdown.total = invalidLoss; m_lastObjectiveBreakdown = breakdown; return invalidLoss; } - // 目标曲线斜率用于把“残差随时间的系统性变化”解释为左右平移。 - // 斜率用对数坐标计算,与前面的插值空间保持一致;平坦区斜率接近零, - // 不会凭空制造水平偏移量。 + // 通过门槛后最多只缺少首尾少量目标点。按模拟曲线端点趋势补齐后, + // 每个候选仍在固定 50 点上计算 Huber 均值,不能靠少算难拟合端点获益。 for(int i = 0; i < numPoints; ++i) { - int left = i == 0 ? 0 : i - 1; - int right = i == numPoints - 1 ? numPoints - 1 : i + 1; - double denominator = commonLogX[right] - commonLogX[left]; - - if(qAbs(denominator) > 1.0e-12) { - pressureSlope[i] = - (targetLogPressure[right] - targetLogPressure[left]) / - denominator; - derivativeSlope[i] = - (targetLogDerivative[right] - targetLogDerivative[left]) / - denominator; - } - } - - // Huber RMS 在小残差区域保持平方损失,在异常点区域转为线性增长, - // 避免少量求解器异常点完全主导候选排序。这里没有除以目标值, - // 因为残差已经是 log(value) 差值,本身就是相对误差的表达;返回值是 - // Huber rho 均值的平方根,保持与 RMS 类似的尺度。 - auto huberRms = [huberDelta](const QVector& values, - int begin, - int end) -> double { + if(isFiniteNumber(pressureResidual[i]) && + isFiniteNumber(derivativeResidual[i])) { + continue; + } + double resultLogPressure = 0.0; + double resultLogDerivative = 0.0; + if(!extrapolateEndpointLogValue( + resultPressure, commonX[i], + &resultLogPressure) || + !extrapolateEndpointLogValue( + resultDerivative, commonX[i], + &resultLogDerivative)) { + return invalidLoss; + } + pressureResidual[i] = + resultLogPressure - targetLogPressure[i]; + derivativeResidual[i] = + resultLogDerivative - targetLogDerivative[i]; + } + + // Huber RMS 在小残差处保持平方损失,在异常点处转为线性增长。 + auto huberRmsAround = [huberDelta]( + const QVector& values, + int begin, + int end, + double center) -> double { double sum = 0.0; int count = 0; + int validBegin = qMax(0, begin); + int validEnd = + qMin(end, static_cast(values.size())); - for(int i = qMax(0, begin); - i < qMin(end, static_cast(values.size())); ++i) { + for(int i = validBegin; i < validEnd; ++i) { if(!isFiniteNumber(values[i])) { continue; } - double absoluteValue = qAbs(values[i]); + double difference = values[i] - center; + double absoluteValue = qAbs(difference); double rho = absoluteValue <= huberDelta - ? values[i] * values[i] + ? difference * difference : 2.0 * huberDelta * absoluteValue - huberDelta * huberDelta; sum += rho; @@ -5258,41 +6644,61 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( : std::numeric_limits::quiet_NaN(); }; - // 用 Huber 加权迭代估计残差中心,作为整体上下偏移。相比普通平均值, - // 它对局部尖峰更稳健,同时保留偏高/偏低的方向信息。迭代只用于诊断, - // 不会把残差“校正”后再写回求解器结果。 - auto huberCenter = [huberDelta](const QVector& values) -> double { + auto huberRms = [&huberRmsAround]( + const QVector& values, + int begin, + int end) -> double { + return huberRmsAround(values, begin, end, 0.0); + }; + + // 将 Huber 能量转换成带符号的等效残差,使向量平方和与稳健损失一致。 + // 非代理 LM 直接对该固定长度向量建立 Jacobian。 + auto huberEquivalentResidual = [huberDelta](double value) -> double { + double absoluteValue = qAbs(value); + double rho = absoluteValue <= huberDelta + ? value * value + : 2.0 * huberDelta * absoluteValue - + huberDelta * huberDelta; + double magnitude = qSqrt(qMax(0.0, rho)); + return value < 0.0 ? -magnitude : magnitude; + }; + + // Huber 加权中心保留上下偏差的符号,并降低局部尖峰的影响。 + auto huberCenterRange = [huberDelta]( + const QVector& values, + int begin, + int end) -> double { + int validBegin = qMax(0, begin); + int validEnd = + qMin(end, static_cast(values.size())); double center = 0.0; int count = 0; - for(int i = 0; i < values.size(); ++i) { + for(int i = validBegin; i < validEnd; ++i) { if(isFiniteNumber(values[i])) { center += values[i]; ++count; } } - if(count == 0) { return std::numeric_limits::quiet_NaN(); } - center /= count; - // 固定最多 8 次迭代,控制每个候选的计算开销并保持结果稳定。 for(int iteration = 0; iteration < 8; ++iteration) { double weightedSum = 0.0; double weightTotal = 0.0; - - for(int i = 0; i < values.size(); ++i) { + for(int i = validBegin; i < validEnd; ++i) { if(!isFiniteNumber(values[i])) { continue; } double distance = qAbs(values[i] - center); - // 距离接近零时直接取权重 1,避免除零并保持中心点不被放大。 - double weight = distance <= huberDelta || distance < 1.0e-12 - ? 1.0 - : huberDelta / distance; + double weight = + distance <= huberDelta || + distance < 1.0e-12 + ? 1.0 + : huberDelta / distance; weightedSum += weight * values[i]; weightTotal += weight; } @@ -5300,133 +6706,549 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( if(weightTotal <= 1.0e-12) { break; } - double nextCenter = weightedSum / weightTotal; if(qAbs(nextCenter - center) <= 1.0e-12) { center = nextCenter; break; } - center = nextCenter; } - return center; }; - // 整体压力/导数误差用于排序;verticalLoss 主要用于解释整体上下偏移。 - breakdown.pressureLoss = huberRms(pressureResidual, 0, numPoints); - breakdown.derivativeLoss = huberRms(derivativeResidual, 0, numPoints); - breakdown.verticalBiasPressure = huberCenter(pressureResidual); - breakdown.verticalBiasDerivative = huberCenter(derivativeResidual); - breakdown.verticalLoss = - 0.5 * (qAbs(breakdown.verticalBiasPressure) + - qAbs(breakdown.verticalBiasDerivative)); - - // 去除上下中心后,把残差投影到目标曲线斜率上估计左右偏移。 - // residual ~= -physicalShift * targetSlope,因此 physicalShift 取回归系数的相反数。 - // 这是局部一阶近似,用于判断方向和大小,不等同于再次优化时间轴。 - double horizontalNumerator = 0.0; - double horizontalDenominator = 0.0; + // 压力和导数合并后只求一个公共中心,表示两条曲线共同的上下位移。 + // 分别去中心会把压力与导数之间真实的相对形状差异一并消除。 + auto huberCommonCenterRange = [&huberCenterRange]( + const QVector& pressureValues, + const QVector& derivativeValues, + int begin, + int end) -> double { + QVector combined; + int validBegin = qMax(0, begin); + int validEnd = qMin( + end, + qMin(static_cast(pressureValues.size()), + static_cast(derivativeValues.size()))); + combined.reserve(2 * qMax(0, validEnd - validBegin)); + + for(int i = validBegin; i < validEnd; ++i) { + if(isFiniteNumber(pressureValues[i])) { + combined.append(pressureValues[i]); + } + if(isFiniteNumber(derivativeValues[i])) { + combined.append(derivativeValues[i]); + } + } + return huberCenterRange(combined, 0, combined.size()); + }; + + // 两个通道按能量等权合并,返回值与单通道 Huber RMS 保持同一量纲。 + auto jointHuberRmsAround = [&huberRmsAround]( + const QVector& pressureValues, + const QVector& derivativeValues, + int begin, + int end, + double center) -> double { + double pressureLoss = huberRmsAround( + pressureValues, begin, end, center); + double derivativeLoss = huberRmsAround( + derivativeValues, begin, end, center); + if(!isFiniteNumber(pressureLoss) || + !isFiniteNumber(derivativeLoss)) { + return std::numeric_limits::quiet_NaN(); + } + return qSqrt(0.5 * + (pressureLoss * pressureLoss + + derivativeLoss * derivativeLoss)); + }; + + // 主目标始终使用未做上下或左右校正的完整曲线误差。 + breakdown.pressureLoss = + huberRms(pressureResidual, 0, numPoints); + breakdown.derivativeLoss = + huberRms(derivativeResidual, 0, numPoints); + // 压力和导数各占一半权重。缩放后 residualVector 的二范数就是 + // sqrt(0.5 * pressureLoss^2 + 0.5 * derivativeLoss^2)。 + const double residualScale = qSqrt(0.5 / numPoints); + breakdown.residualVector.reserve(2 * numPoints); for(int i = 0; i < numPoints; ++i) { - if(!isFiniteNumber(pressureResidual[i]) || - !isFiniteNumber(derivativeResidual[i])) { + breakdown.residualVector.append( + residualScale * + huberEquivalentResidual(pressureResidual[i])); + } + for(int i = 0; i < numPoints; ++i) { + breakdown.residualVector.append( + residualScale * + huberEquivalentResidual(derivativeResidual[i])); + } + + const double logGridStep = + (targetLogMaxX - targetLogMinX) / + (numPoints - 1); + const double resultLogMinX = qLn(resultMinX); + const double resultLogMaxX = qLn(resultMaxX); + // 左右配准只在所有候选位移都共同覆盖的固定区间比较,至少保留 80% + // 目标点;每个 log-time 网格间隔再细分为 8 份,提高位移分辨率。 + const int minimumRegistrationPoints = + (numPoints * 4) / 5; + const int shiftSubdivisions = 8; + int maximumShiftIntervals = 4; + int registrationBegin = 0; + int registrationEnd = numPoints; + double maximumPhysicalShift = + maximumShiftIntervals * logGridStep; + + // 所有位移候选使用同一组目标点。结果范围不足时逐步缩小最大位移, + // 但用于配准的固定公共区间不得少于目标网格的 80%。 + auto updateRegistrationRange = [&](double maximumShift) { + registrationBegin = 0; + registrationEnd = numPoints; + while(registrationBegin < registrationEnd && + commonLogX[registrationBegin] - maximumShift < + resultLogMinX - 1.0e-12) { + ++registrationBegin; + } + while(registrationEnd > registrationBegin && + commonLogX[registrationEnd - 1] + maximumShift > + resultLogMaxX + 1.0e-12) { + --registrationEnd; + } + }; + + updateRegistrationRange(maximumPhysicalShift); + while(maximumShiftIntervals > 0 && + registrationEnd - registrationBegin < + minimumRegistrationPoints) { + --maximumShiftIntervals; + maximumPhysicalShift = + maximumShiftIntervals * logGridStep; + updateRegistrationRange(maximumPhysicalShift); + } + if(registrationEnd - registrationBegin < + minimumRegistrationPoints) { + return invalidLoss; + } + + // physicalShift 为正表示模拟曲线偏右;对齐时在目标时刻右侧读取模拟值。 + auto buildShiftResidual = [&]( + double physicalShift, + int compareBegin, + int compareEnd, + QVector* shiftedPressure, + QVector* shiftedDerivative) -> bool { + if(!shiftedPressure || !shiftedDerivative) { + return false; + } + + shiftedPressure->fill( + std::numeric_limits::quiet_NaN(), + numPoints); + shiftedDerivative->fill( + std::numeric_limits::quiet_NaN(), + numPoints); + + int validBegin = qMax(0, compareBegin); + int validEnd = qMin(numPoints, compareEnd); + for(int i = validBegin; i < validEnd; ++i) { + double shiftedLogX = + commonLogX[i] + physicalShift; + if(shiftedLogX < resultLogMinX - 1.0e-12 || + shiftedLogX > + resultLogMaxX + 1.0e-12) { + return false; + } + + double shiftedX = qExp( + qBound(resultLogMinX, + shiftedLogX, + resultLogMaxX)); + double resultLogPressure = 0.0; + double resultLogDerivative = 0.0; + if(!interpolateLogValue( + resultPressure, shiftedX, + &resultLogPressure) || + !interpolateLogValue( + resultDerivative, shiftedX, + &resultLogDerivative)) { + return false; + } + + (*shiftedPressure)[i] = + resultLogPressure - targetLogPressure[i]; + (*shiftedDerivative)[i] = + resultLogDerivative - targetLogDerivative[i]; + } + return true; + }; + + // 损失相同时优先选择绝对位移更小的候选,避免平坦 profile 在数值噪声 + // 下无故偏向搜索边界。 + auto isBetterProfileValue = []( + double loss, + double shift, + double bestLoss, + double bestShift) -> bool { + const double tolerance = 1.0e-12; + return loss < bestLoss - tolerance || + (qAbs(loss - bestLoss) <= tolerance && + qAbs(shift) < qAbs(bestShift)); + }; + + const int halfShiftStepCount = + maximumShiftIntervals * shiftSubdivisions; + const double physicalShiftStep = + logGridStep / shiftSubdivisions; + double zeroShiftCenteredLoss = + std::numeric_limits::quiet_NaN(); + double bestCenteredLoss = + std::numeric_limits::infinity(); + double bestPhysicalShift = 0.0; + int bestShiftStep = 0; + double bestPressureLoss = + std::numeric_limits::infinity(); + double bestPressureShift = 0.0; + double bestDerivativeLoss = + std::numeric_limits::infinity(); + double bestDerivativeShift = 0.0; + QVector profileLosses( + 2 * halfShiftStepCount + 1, + std::numeric_limits::quiet_NaN()); + QVector profileCommonBiases( + 2 * halfShiftStepCount + 1, + std::numeric_limits::quiet_NaN()); + QVector shiftedPressureResidual; + QVector shiftedDerivativeResidual; + + // 位移和公共上下偏移联合求解,避免“先扣上下还是先扣左右”的顺序依赖。 + for(int shiftStep = -halfShiftStepCount; + shiftStep <= halfShiftStepCount; + ++shiftStep) { + double physicalShift = + shiftStep * physicalShiftStep; + if(!buildShiftResidual( + physicalShift, + registrationBegin, + registrationEnd, + &shiftedPressureResidual, + &shiftedDerivativeResidual)) { continue; } - double pressureCentered = - pressureResidual[i] - breakdown.verticalBiasPressure; - double derivativeCentered = - derivativeResidual[i] - breakdown.verticalBiasDerivative; - horizontalNumerator += pressureSlope[i] * pressureCentered + - derivativeSlope[i] * derivativeCentered; - horizontalDenominator += pressureSlope[i] * pressureSlope[i] + - derivativeSlope[i] * derivativeSlope[i]; + double commonBias = huberCommonCenterRange( + shiftedPressureResidual, + shiftedDerivativeResidual, + registrationBegin, + registrationEnd); + double centeredLoss = jointHuberRmsAround( + shiftedPressureResidual, + shiftedDerivativeResidual, + registrationBegin, + registrationEnd, + commonBias); + double pressureBias = huberCenterRange( + shiftedPressureResidual, + registrationBegin, + registrationEnd); + double derivativeBias = huberCenterRange( + shiftedDerivativeResidual, + registrationBegin, + registrationEnd); + double pressureLoss = huberRmsAround( + shiftedPressureResidual, + registrationBegin, + registrationEnd, + pressureBias); + double derivativeLoss = huberRmsAround( + shiftedDerivativeResidual, + registrationBegin, + registrationEnd, + derivativeBias); + + if(!isFiniteNumber(centeredLoss) || + !isFiniteNumber(pressureLoss) || + !isFiniteNumber(derivativeLoss)) { + continue; + } + int profileIndex = shiftStep + halfShiftStepCount; + profileLosses[profileIndex] = centeredLoss; + profileCommonBiases[profileIndex] = commonBias; + if(shiftStep == 0) { + zeroShiftCenteredLoss = centeredLoss; + } + if(isBetterProfileValue( + centeredLoss, + physicalShift, + bestCenteredLoss, + bestPhysicalShift)) { + bestCenteredLoss = centeredLoss; + bestPhysicalShift = physicalShift; + bestShiftStep = shiftStep; + } + if(isBetterProfileValue( + pressureLoss, + physicalShift, + bestPressureLoss, + bestPressureShift)) { + bestPressureLoss = pressureLoss; + bestPressureShift = physicalShift; + } + if(isBetterProfileValue( + derivativeLoss, + physicalShift, + bestDerivativeLoss, + bestDerivativeShift)) { + bestDerivativeLoss = derivativeLoss; + bestDerivativeShift = physicalShift; + } } - breakdown.horizontalShift = horizontalDenominator > 1.0e-12 - ? horizontalNumerator / - horizontalDenominator - : 0.0; - breakdown.horizontalPhysicalShift = -breakdown.horizontalShift; - breakdown.horizontalLoss = qAbs(breakdown.horizontalShift); + if(!isFiniteNumber(zeroShiftCenteredLoss) || + !isFiniteNumber(bestCenteredLoss) || + !isFiniteNumber(bestPressureLoss) || + !isFiniteNumber(bestDerivativeLoss)) { + return invalidLoss; + } - // 从原始残差中扣除“整体上下 + 等效左右”两部分,剩余项才作为形状误差。 - // 因此 shapeLoss 较大而 vertical/horizontal 较小时,说明主要是曲率、拐点 - // 或导数变化趋势不一致,而不是简单的整体平移。 - QVector shapePressure(numPoints, - std::numeric_limits::quiet_NaN()); - QVector shapeDerivative(numPoints, - std::numeric_limits::quiet_NaN()); + // horizontalGain 是“允许水平位移”相对“固定零位移”减少的稳健能量。 + // 只有改善足够明显且最优点不是边界,才把位移解释为可靠左右偏差。 + double horizontalGain = nestedRmsContribution( + zeroShiftCenteredLoss, bestCenteredLoss); + double horizontalSignalThreshold = + qMax(1.0e-5, zeroShiftCenteredLoss * 0.02); + int bestProfileIndex = bestShiftStep + halfShiftStepCount; + double nearbyProfileLoss = + std::numeric_limits::infinity(); + int leftProfileIndex = + bestProfileIndex - shiftSubdivisions; + int rightProfileIndex = + bestProfileIndex + shiftSubdivisions; + if(leftProfileIndex >= 0 && + leftProfileIndex < profileLosses.size() && + isFiniteNumber(profileLosses[leftProfileIndex])) { + nearbyProfileLoss = qMin( + nearbyProfileLoss, + profileLosses[leftProfileIndex]); + } + if(rightProfileIndex >= 0 && + rightProfileIndex < profileLosses.size() && + isFiniteNumber(profileLosses[rightProfileIndex])) { + nearbyProfileLoss = qMin( + nearbyProfileLoss, + profileLosses[rightProfileIndex]); + } + double profileContrast = isFiniteNumber(nearbyProfileLoss) + ? nestedRmsContribution( + nearbyProfileLoss, + bestCenteredLoss) + : 0.0; + bool flatRegistrationProfile = + profileContrast <= horizontalSignalThreshold; + + // 平台曲线的 profile 也可能很平,但公共 bias 在各个位移下保持稳定, + // 此时仍能可靠判断上下。只有近优位移会明显改变 bias 才说明上下/左右不可辨识。 + double minimumNearOptimalBias = + std::numeric_limits::infinity(); + double maximumNearOptimalBias = + -std::numeric_limits::infinity(); + for(int i = 0; i < profileLosses.size(); ++i) { + if(isFiniteNumber(profileLosses[i]) && + isFiniteNumber(profileCommonBiases[i]) && + profileLosses[i] <= + bestCenteredLoss + horizontalSignalThreshold) { + minimumNearOptimalBias = qMin( + minimumNearOptimalBias, + profileCommonBiases[i]); + maximumNearOptimalBias = qMax( + maximumNearOptimalBias, + profileCommonBiases[i]); + } + } + double nearOptimalBiasSpread = + isFiniteNumber(minimumNearOptimalBias) && + isFiniteNumber(maximumNearOptimalBias) + ? maximumNearOptimalBias - minimumNearOptimalBias + : std::numeric_limits::infinity(); + bool commonBiasStable = nearOptimalBiasSpread <= + qMax(1.0e-4, huberDelta * 0.05); + bool horizontalAtBoundary = + halfShiftStepCount > 0 && + qAbs(bestShiftStep) == halfShiftStepCount; + // 压力和导数通道分别求出的最佳位移若方向相反或相差过大,说明一个 + // 单一水平平移无法解释两条曲线,此时标记配准歧义并禁用左右引导。 + bool pressureShiftDetected = + qAbs(bestPressureShift) >= + 0.5 * physicalShiftStep; + bool derivativeShiftDetected = + qAbs(bestDerivativeShift) >= + 0.5 * physicalShiftStep; + bool channelShiftConflict = + pressureShiftDetected && + derivativeShiftDetected && + (bestPressureShift * bestDerivativeShift < 0.0 || + qAbs(bestPressureShift - bestDerivativeShift) > + 2.0 * logGridStep); + + breakdown.horizontalLoss = horizontalGain; + breakdown.horizontalReliable = + maximumShiftIntervals > 0 && + !horizontalAtBoundary && + !channelShiftConflict && + !flatRegistrationProfile && + horizontalGain > horizontalSignalThreshold && + qAbs(bestPhysicalShift) >= + 0.5 * physicalShiftStep; + breakdown.registrationAmbiguous = + channelShiftConflict || + (qAbs(bestPhysicalShift) >= + 0.5 * physicalShiftStep && + !breakdown.horizontalReliable) || + (flatRegistrationProfile && !commonBiasStable); + breakdown.horizontalPhysicalShift = + breakdown.horizontalReliable + ? bestPhysicalShift + : 0.0; + + // 可信水平位移确定后,在该位移实际覆盖的最大区间重新计算上下和形状。 + int diagnosticBegin = 0; + int diagnosticEnd = numPoints; + while(diagnosticBegin < diagnosticEnd && + commonLogX[diagnosticBegin] + + breakdown.horizontalPhysicalShift < + resultLogMinX - 1.0e-12) { + ++diagnosticBegin; + } + while(diagnosticEnd > diagnosticBegin && + commonLogX[diagnosticEnd - 1] + + breakdown.horizontalPhysicalShift > + resultLogMaxX + 1.0e-12) { + --diagnosticEnd; + } + if(diagnosticEnd - diagnosticBegin < + minimumRegistrationPoints || + !buildShiftResidual( + breakdown.horizontalPhysicalShift, + diagnosticBegin, + diagnosticEnd, + &shiftedPressureResidual, + &shiftedDerivativeResidual)) { + return invalidLoss; + } + + double commonBias = huberCommonCenterRange( + shiftedPressureResidual, + shiftedDerivativeResidual, + diagnosticBegin, + diagnosticEnd); + double rawAlignedLoss = jointHuberRmsAround( + shiftedPressureResidual, + shiftedDerivativeResidual, + diagnosticBegin, + diagnosticEnd, + 0.0); + double centeredAlignedLoss = jointHuberRmsAround( + shiftedPressureResidual, + shiftedDerivativeResidual, + diagnosticBegin, + diagnosticEnd, + commonBias); + if(!isFiniteNumber(commonBias) || + !isFiniteNumber(rawAlignedLoss) || + !isFiniteNumber(centeredAlignedLoss)) { + return invalidLoss; + } + + // 原始对齐误差减去公共中心后的能量差定义为上下误差贡献。只有它相对 + // 当前对齐误差足够明显,且配准无歧义时,公共 bias 才可用于有符号选参。 + breakdown.verticalCommonBias = commonBias; + breakdown.verticalLoss = nestedRmsContribution( + rawAlignedLoss, centeredAlignedLoss); + breakdown.verticalReliable = + !breakdown.registrationAmbiguous && + breakdown.verticalLoss > + qMax(1.0e-5, rawAlignedLoss * 0.02); + + QVector shapePressure( + numPoints, std::numeric_limits::quiet_NaN()); + QVector shapeDerivative( + numPoints, std::numeric_limits::quiet_NaN()); + for(int i = diagnosticBegin; i < diagnosticEnd; ++i) { + if(isFiniteNumber(shiftedPressureResidual[i])) { + shapePressure[i] = + shiftedPressureResidual[i] - commonBias; + } + if(isFiniteNumber(shiftedDerivativeResidual[i])) { + shapeDerivative[i] = + shiftedDerivativeResidual[i] - commonBias; + } + } + + // shapeLoss 是去除可信左右位移和稳健公共中心后的剩余误差。 + double shapePressureLoss = huberRms( + shapePressure, diagnosticBegin, diagnosticEnd); + double shapeDerivativeLoss = huberRms( + shapeDerivative, diagnosticBegin, diagnosticEnd); + if(!isFiniteNumber(shapePressureLoss) || + !isFiniteNumber(shapeDerivativeLoss)) { + return invalidLoss; + } + breakdown.shapeLoss = qSqrt( + 0.5 * + (shapePressureLoss * shapePressureLoss + + shapeDerivativeLoss * shapeDerivativeLoss)); + + // 现阶段不识别或单独调度晚期流动段;保留字段只为了维持现有 trace 列。 + breakdown.lateDerivativeSlopeBias = 0.0; + breakdown.lateDerivativeTrendLoss = 0.0; + breakdown.lateDerivativeTrendReliable = false; - for(int i = 0; i < numPoints; ++i) { - if(isFiniteNumber(pressureResidual[i])) { - shapePressure[i] = pressureResidual[i] - - breakdown.verticalBiasPressure - - breakdown.horizontalShift * pressureSlope[i]; - } - - if(isFiniteNumber(derivativeResidual[i])) { - shapeDerivative[i] = derivativeResidual[i] - - breakdown.verticalBiasDerivative - - breakdown.horizontalShift * - derivativeSlope[i]; - } - } - - breakdown.shapeLoss = - 0.5 * (huberRms(shapePressure, 0, numPoints) + - huberRms(shapeDerivative, 0, numPoints)); - - // 将对数时间网格分成早、中、晚三段,用于定位误差集中出现的阶段。 - // 网格本身按 log(time) 均匀分布,所以三段对应的是时间数量级,而非原始 - // 线性时间长度,适合双对数试井曲线的早期/中期/晚期判读。 - const int segment1 = numPoints / 3; - const int segment2 = (2 * numPoints) / 3; - breakdown.pressureEarlyLoss = huberRms(pressureResidual, 0, segment1); - breakdown.pressureMiddleLoss = - huberRms(pressureResidual, segment1, segment2); - breakdown.pressureLateLoss = - huberRms(pressureResidual, segment2, numPoints); - breakdown.derivativeEarlyLoss = - huberRms(derivativeResidual, 0, segment1); - breakdown.derivativeMiddleLoss = - huberRms(derivativeResidual, segment1, segment2); - breakdown.derivativeLateLoss = - huberRms(derivativeResidual, segment2, numPoints); - - // 函数入口已将 m_lastObjectiveBreakdown 重置为无效状态,因此这里直接返回 - // invalidLoss 时不会把上一候选的误差分解误报给调用方。 - // 导数、压力或形状无法形成有效统计时,整个候选都视为无效,避免 NaN - // 进入粒子排序。 if(!isFiniteNumber(breakdown.pressureLoss) || !isFiniteNumber(breakdown.derivativeLoss) || + !isFiniteNumber(breakdown.verticalCommonBias) || + !isFiniteNumber(breakdown.verticalLoss) || + !isFiniteNumber(breakdown.horizontalPhysicalShift) || + !isFiniteNumber(breakdown.horizontalLoss) || !isFiniteNumber(breakdown.shapeLoss) || - !isFiniteNumber(breakdown.verticalLoss)) { + breakdown.residualVector.size() != 2 * numPoints) { return invalidLoss; } - // 当前总损失作为 fitness 用于粒子比较、收敛/停止判断;上下、左右、形状和 - // 分段分量先作为诊断输出,不在本次改动中直接参与参数更新。 - breakdown.total = 0.5 * breakdown.pressureLoss + - 0.5 * breakdown.derivativeLoss + - 0.1 * breakdown.coveragePenalty; - breakdown.valid = isFiniteNumber(breakdown.total) && - breakdown.total >= 0.0; + if(isSurrogateScreeningEnabled()) { + // 代理模型仍按原目标比较,不能用未参与训练的新损失改变候选排序。 + breakdown.total = + 0.5 * breakdown.pressureLoss + + 0.5 * breakdown.derivativeLoss; + } else { + // 非代理总目标等于固定 100 维 Huber 等效残差的二范数;压力和 + // 导数各占一半能量。上下、左右和形状分量不参与候选排序与接受。 + breakdown.total = qSqrt( + 0.5 * breakdown.pressureLoss * breakdown.pressureLoss + + 0.5 * breakdown.derivativeLoss * breakdown.derivativeLoss); + } + breakdown.valid = + isFiniteNumber(breakdown.total) && + breakdown.total >= 0.0; m_lastObjectiveBreakdown = breakdown; - DEBUG_OUT(QString("LogLog objective: pressure=%1, derivative=%2, vertical=%3, horizontal=%4, shape=%5, coverage=%6, total=%7") - .arg(breakdown.pressureLoss, 0, 'e', 4) - .arg(breakdown.derivativeLoss, 0, 'e', 4) - .arg(breakdown.verticalLoss, 0, 'e', 4) - .arg(breakdown.horizontalLoss, 0, 'e', 4) - .arg(breakdown.shapeLoss, 0, 'e', 4) - .arg(breakdown.coverage, 0, 'f', 4) - .arg(breakdown.total, 0, 'e', 4)); - - return breakdown.valid ? qMin(1.0e9, breakdown.total) : invalidLoss; + DEBUG_OUT( + QString("LogLog objective: pressure=%1, derivative=%2, vertical=%3, horizontal=%4, shape=%5, ambiguous=%6, shift=%7, coverage=%8, total=%9") + .arg(breakdown.pressureLoss, 0, 'e', 4) + .arg(breakdown.derivativeLoss, 0, 'e', 4) + .arg(breakdown.verticalLoss, 0, 'e', 4) + .arg(breakdown.horizontalLoss, 0, 'e', 4) + .arg(breakdown.shapeLoss, 0, 'e', 4) + .arg(breakdown.registrationAmbiguous) + .arg(breakdown.horizontalPhysicalShift, 0, 'e', 4) + .arg(breakdown.coverage, 0, 'f', 4) + .arg(breakdown.total, 0, 'e', 4)); + + return breakdown.valid + ? qMin(1.0e9, breakdown.total) + : invalidLoss; } catch(const std::exception& e) { - DEBUG_OUT(QString("Exception in LogLog error calculation: %1").arg(e.what())); + DEBUG_OUT( + QString("Exception in LogLog error calculation: %1") + .arg(e.what())); return invalidLoss; } catch(...) { DEBUG_OUT("Unknown exception in LogLog error calculation"); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index f0f243e..2a527ee 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -35,6 +35,14 @@ bool nmAutoFitUiIsFinite(double value) #endif } +// 物理边界可能由多个浮点配置量计算得到,界面十进制文本再转回 double 后会有 +// 末位舍入差。这里只放宽约 8 个机器精度,不放宽实际物理范围。 +bool nmAutoFitUiNearlyEqual(double left, double right) +{ + const double scale = qMax(std::fabs(left), std::fabs(right)); + return std::fabs(left - right) <= DBL_EPSILON * 8.0 * scale; +} + // 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。 bool nmAutoFitReadPhysicalRange(const char* parameterName, double fallbackMin, double fallbackMax, double& minValue, double& maxValue) @@ -337,9 +345,10 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, m_updatingParameterRanges = wasUpdatingRanges; } -// 根据初值生成首次或拟合后的建议范围,并始终限制在物理边界内。 +// 根据当前初值生成建议搜索范围,并始终截断在系统物理边界内。skin 使用 +// 加减固定宽度,其余正值参数使用倍率范围;该规则在首次加载和拟合完成后复用。 void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, - double centerValue, bool afterFit) + double centerValue) { if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8 || !nmAutoFitUiIsFinite(centerValue)) { @@ -371,12 +380,12 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double newMin = physicalMin; double newMax = physicalMax; if(parameterIndex == 1) { - const double skinHalfRange = afterFit ? 1.0 : 10.0; + const double skinHalfRange = 10.0; newMin = qMax(physicalMin, reference - skinHalfRange); newMax = qMin(physicalMax, reference + skinHalfRange); } else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) { - const double lowerFactor = afterFit ? 0.5 : 0.1; - const double upperFactor = afterFit ? 2.0 : 10.0; + const double lowerFactor = 0.1; + const double upperFactor = 10.0; newMin = qMax(physicalMin, reference * lowerFactor); newMax = qMin(physicalMax, reference * upperFactor); } else if(parameterIndex == 7) { @@ -409,7 +418,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges() bool initialOk = false; const double initialValue = initialItem->text().toDouble(&initialOk); if(initialOk && nmAutoFitUiIsFinite(initialValue)) { - updateRangeForParameter(parameterIndex, initialValue, false); + updateRangeForParameter(parameterIndex, initialValue); } else { // 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。 double physicalMin = 0.0; @@ -462,7 +471,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges() bool initialOk = false; const double initialValue = initialItem->text().toDouble(&initialOk); if(initialOk && nmAutoFitUiIsFinite(initialValue)) { - updateRangeForParameter(parameterIndex, initialValue, false); + updateRangeForParameter(parameterIndex, initialValue); } else if(physicalMax >= physicalMin) { setParameterRange(parameterIndex, physicalMin, physicalMax); } @@ -529,9 +538,12 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par return false; } - if(minValue < physicalMin || minValue > physicalMax - || initialValue < physicalMin || initialValue > physicalMax - || maxValue < physicalMin || maxValue > physicalMax) { + if((minValue < physicalMin && !nmAutoFitUiNearlyEqual(minValue, physicalMin)) + || (minValue > physicalMax && !nmAutoFitUiNearlyEqual(minValue, physicalMax)) + || (initialValue < physicalMin && !nmAutoFitUiNearlyEqual(initialValue, physicalMin)) + || (initialValue > physicalMax && !nmAutoFitUiNearlyEqual(initialValue, physicalMax)) + || (maxValue < physicalMin && !nmAutoFitUiNearlyEqual(maxValue, physicalMin)) + || (maxValue > physicalMax && !nmAutoFitUiNearlyEqual(maxValue, physicalMax))) { errorMessage = tr("The values of %1 exceed the physical range [%2, %3].") .arg(tr(parameterNames[currentParameterIndex])) .arg(QString::number(physicalMin, 'g', 10)) @@ -1195,8 +1207,8 @@ void nmWxAutomaticFitting::onWellSelected(int index) m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue)); if(m_autoParameterRanges) { - updateRangeForParameter(1, skinValue, false); - updateRangeForParameter(2, wellboreStorageValue, false); + updateRangeForParameter(1, skinValue); + updateRangeForParameter(2, wellboreStorageValue); } } } @@ -1531,9 +1543,9 @@ void nmWxAutomaticFitting::updateBestParametersToTable() // 更新初始值 m_parameterTable->item(paramIndex, 3)->setText(QString::number(bestValue, 'g', 4)); - // 只有自动范围模式才根据拟合结果收窄下一轮搜索区间;手工范围由用户保留。 + // 自动范围模式下,以拟合结果为中心复用首次建范围的规则;手工范围由用户保留。 if(m_autoParameterRanges) { - updateRangeForParameter(paramIndex, bestValue, true); + updateRangeForParameter(paramIndex, bestValue); } } From e8563506b9eae901aad504db4caddf6bf5065a0d Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 10:26:56 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8E=8B=E8=A3=82?= =?UTF-8?q?=E4=BA=95=E5=AF=BC=E6=B5=81=E8=83=BD=E5=8A=9B=20Dfc=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=8B=9F=E5=90=88=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 93646 -> 93895 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 8 + .../nmCalculation/nmCalculationAutoFitPSO.h | 8 +- Include/nmNum/nmData/nmDataAutomaticFitting.h | 13 ++ Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 1 + .../nmCalculation/nmCalculationAutoFitPSO.cpp | 166 +++++++++++++++--- Src/nmNum/nmData/nmDataAutomaticFitting.cpp | 27 +++ Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 122 +++++++++++-- 8 files changed, 299 insertions(+), 46 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 19bf9a383803b135f0206e61491f581566c84ea4..14a0e5f0d7b69500fcb31946cf2f7bbf3d0a7285 100644 GIT binary patch delta 6293 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 delta 6075 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 diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 465cceb..e2bafea 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -3486,6 +3486,14 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We On 开启 + + Fracture conductivity + 裂缝导流能力 + + + The minimum value of %1 must be greater than zero for automatic fitting. + 自动拟合时,%1 的最小值和初始值必须大于零。 + nmWxAutomaticfitting diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index b3e201a..0bf415b 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -374,10 +374,10 @@ private: // 3 phi 孔隙度;4 h 储层厚度;5 Ct 综合压缩系数; // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度。 // m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。 - QVector m_parameterSelected; // 完整 8 个参数是否被用户勾选参与拟合。 - QVector m_parameterLower; // 完整 8 个参数的搜索下界。 - QVector m_parameterUpper; // 完整 8 个参数的搜索上界。 - QVector m_enabledParamIndices; // 被勾选参数在完整 8 维体系中的索引。 + QVector m_parameterSelected; // 完整 9 个参数是否被用户勾选参与拟合。 + QVector m_parameterLower; // 完整 9 个参数的搜索下界。 + QVector m_parameterUpper; // 完整 9 个参数的搜索上界。 + QVector m_enabledParamIndices; // 被勾选参数在完整 9 维体系中的索引。 QVector > m_targetLogLogData; // 目标井 history log-log 曲线:time/pressure/derivative。 QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。 diff --git a/Include/nmNum/nmData/nmDataAutomaticFitting.h b/Include/nmNum/nmData/nmDataAutomaticFitting.h index b2da223..18bc851 100644 --- a/Include/nmNum/nmData/nmDataAutomaticFitting.h +++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h @@ -75,6 +75,13 @@ public: nmDataAttribute& getSwiMin(); void setSwiMin(const nmDataAttribute& swiMin); + // Getter and Setter for fractureConductivityMax + nmDataAttribute& getFractureConductivityMax(); + void setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax); + // Getter and Setter for fractureConductivityMin + nmDataAttribute& getFractureConductivityMin(); + void setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin); + // Getter and Setter for iteration count nmDataAttribute& getIterationCount(); void setIterationCount(const nmDataAttribute& iterationCount); @@ -114,6 +121,9 @@ public: bool getSwiSelected() const; void setSwiSelected(bool selected); + bool getFractureConductivitySelected() const; + void setFractureConductivitySelected(bool selected); + private: // 参数最大值 nmDataAttribute m_permeabilityMax; @@ -124,6 +134,7 @@ private: nmDataAttribute m_ctMax; nmDataAttribute m_cfMax; nmDataAttribute m_swiMax; + nmDataAttribute m_fractureConductivityMax; // 参数最小值 nmDataAttribute m_permeabilityMin; @@ -134,6 +145,7 @@ private: nmDataAttribute m_ctMin; nmDataAttribute m_cfMin; nmDataAttribute m_swiMin; + nmDataAttribute m_fractureConductivityMin; // 迭代参数 nmDataAttribute m_iterationCount; // 迭代步数 @@ -150,6 +162,7 @@ private: bool m_ctSelected; // 是否选择综合压缩系数进行拟合 bool m_cfSelected; // 是否选择岩石压缩系数进行拟合 bool m_swiSelected; // 是否选择初始含水饱和度进行拟合 + bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合 }; #endif // NMDATAAUTOMATICFITTING_H diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 5b3848c..ada66d0 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -91,6 +91,7 @@ private: QCheckBox* m_ctCheckBox; // 综合压缩系数 QCheckBox* m_cfCheckBox; // 岩石压缩系数 QCheckBox* m_swiCheckBox; // 初始含水饱和度 + QCheckBox* m_dfcCheckBox; // 裂缝导流能力 // 按钮 QPushButton* m_reverseBtn; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index a4eb476..2c75f29 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -432,7 +432,7 @@ static QString findExecutableInPath(const QString& executableName) static QStringList traceParameterNames() { // trace 和 trace meta 使用的完整参数名顺序。 - // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-7 索引一致。 + // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-8 索引一致。 QStringList names; names << "k" << "skin" @@ -441,7 +441,8 @@ static QStringList traceParameterNames() << "h" << "Ct" << "Cf" - << "Swi"; + << "Swi" + << "Dfc"; return names; } @@ -1011,7 +1012,8 @@ void nmCalculationAutoFitPSO::closeTraceFile() void nmCalculationAutoFitPSO::writeTraceHeader() { // trace CSV 字段说明: - // - 当前粒子参数只记录代理模型关心的 k/skin/wellboreC/phi/h/Ct/Cf; + // - 代理模式保持原 k/skin/wellboreC/phi/h/Ct/Cf 契约; + // - 非代理模式额外记录 Swi/Dfc,便于复盘信赖域对两项参数的调整; // - solver_objective 是真实求解器误差; // - surrogate_objective 是 Python 代理评分; // - screening_decision 说明该粒子为什么跑/不跑真实求解器; @@ -1032,8 +1034,12 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "phi" << "h" << "Ct" - << "Cf" - << "solver_objective" + << "Cf"; + if(!isSurrogateScreeningEnabled()) { + cols << "Swi" + << "Dfc"; + } + cols << "solver_objective" << "solver_success" << "elapsed_ms" << "surrogate_objective" @@ -1045,16 +1051,24 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "pbest_phi" << "pbest_h" << "pbest_Ct" - << "pbest_Cf" - << "gbest_objective" + << "pbest_Cf"; + if(!isSurrogateScreeningEnabled()) { + cols << "pbest_Swi" + << "pbest_Dfc"; + } + cols << "gbest_objective" << "gbest_k" << "gbest_skin" << "gbest_wellboreC" << "gbest_phi" << "gbest_h" << "gbest_Ct" - << "gbest_Cf" - << "enabled_param_indices" + << "gbest_Cf"; + if(!isSurrogateScreeningEnabled()) { + cols << "gbest_Swi" + << "gbest_Dfc"; + } + cols << "enabled_param_indices" << "pressure_loss" << "derivative_loss"; if(!isSurrogateScreeningEnabled()) { @@ -1118,7 +1132,7 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile() // 非代理 v5 增加带符号诊断列;代理 PSO 保留原 v3 字段和目标,避免改变 // 已有模型的训练和回放契约。 out << " \"schema_version\": " - << (isSurrogateScreeningEnabled() ? 3 : 5) << ",\n"; + << (isSurrogateScreeningEnabled() ? 3 : 6) << ",\n"; out << " \"trace_type\": " << jsonEscape(isSurrogateScreeningEnabled() ? "pso_baseline_replay_meta" @@ -1179,10 +1193,10 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile() QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector& selectedParameters) const { - // 将粒子内部使用的“启用参数向量”还原成完整 8 维参数向量。 + // 将粒子内部使用的“启用参数向量”还原成完整 9 维参数向量。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 // trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。 - QVector fullParams(8, 0.0); + QVector fullParams(9, 0.0); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1198,8 +1212,26 @@ QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName); if(pTargetWell) { - fullParams[1] = pTargetWell->getPerforation(0)->getSkin().getValue().toDouble(); + nmDataPerforation* perforation = pTargetWell->getPerforation(0); + if(perforation) { + fullParams[1] = perforation->getSkin().getValue().toDouble(); + } fullParams[2] = pTargetWell->getWellboreStorage().getValue().toDouble(); + + // Dfc 只存在于两类压裂井,普通井在完整向量中保持为 0。 + if(pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + fullParams[8] = fracturedWell->getDfc().getValue().toDouble(); + } + } else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + fullParams[8] = fracturedWell->getDfc().getValue().toDouble(); + } + } } } @@ -1255,8 +1287,12 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(currentParams, 3) << traceParamAt(currentParams, 4) << traceParamAt(currentParams, 5) - << traceParamAt(currentParams, 6) - << traceNumber(solverObjective) + << traceParamAt(currentParams, 6); + if(!isSurrogateScreeningEnabled()) { + cols << traceParamAt(currentParams, 7) + << traceParamAt(currentParams, 8); + } + cols << traceNumber(solverObjective) << QString::number(solverSuccess ? 1 : 0) << QString::number(elapsedMs) << traceNumber(surrogateObjective) @@ -1268,16 +1304,24 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(pbestParams, 3) << traceParamAt(pbestParams, 4) << traceParamAt(pbestParams, 5) - << traceParamAt(pbestParams, 6) - << traceNumber(m_globalBestFitness) + << traceParamAt(pbestParams, 6); + if(!isSurrogateScreeningEnabled()) { + cols << traceParamAt(pbestParams, 7) + << traceParamAt(pbestParams, 8); + } + cols << traceNumber(m_globalBestFitness) << traceParamAt(gbestParams, 0) << traceParamAt(gbestParams, 1) << traceParamAt(gbestParams, 2) << traceParamAt(gbestParams, 3) << traceParamAt(gbestParams, 4) << traceParamAt(gbestParams, 5) - << traceParamAt(gbestParams, 6) - << csvEscape(enabledIndices.join(";")); + << traceParamAt(gbestParams, 6); + if(!isSurrogateScreeningEnabled()) { + cols << traceParamAt(gbestParams, 7) + << traceParamAt(gbestParams, 8); + } + cols << csvEscape(enabledIndices.join(";")); if(objectiveBreakdown && objectiveBreakdown->valid) { cols << traceNumber(objectiveBreakdown->pressureLoss) @@ -2873,14 +2917,14 @@ void nmCalculationAutoFitPSO::loadParameterBounds() // 读取用户勾选的拟合参数及上下界。 // // 这里构建三个核心数组: - // - m_parameterSelected[8]:完整参数体系中每个参数是否参与拟合; - // - m_parameterLower/Upper[8]:完整参数体系的搜索上下界; + // - m_parameterSelected[9]:完整参数体系中每个参数是否参与拟合; + // - m_parameterLower/Upper[9]:完整参数体系的搜索上下界; // - m_enabledParamIndices:把粒子内部紧凑向量映射回完整参数索引。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); // 获取参数选择状态 - m_parameterSelected.resize(8); + m_parameterSelected.resize(9); m_parameterSelected[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[1] = fittingData.getSkinSelected(); m_parameterSelected[2] = fittingData.getWellboreStorageSelected(); @@ -2889,10 +2933,11 @@ void nmCalculationAutoFitPSO::loadParameterBounds() m_parameterSelected[5] = fittingData.getCtSelected(); m_parameterSelected[6] = fittingData.getCfSelected(); m_parameterSelected[7] = fittingData.getSwiSelected(); + m_parameterSelected[8] = fittingData.getFractureConductivitySelected(); // 获取参数边界 - m_parameterLower.resize(8); - m_parameterUpper.resize(8); + m_parameterLower.resize(9); + m_parameterUpper.resize(9); m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble(); m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble(); @@ -2918,6 +2963,9 @@ void nmCalculationAutoFitPSO::loadParameterBounds() m_parameterLower[7] = fittingData.getSwiMin().getValue().toDouble(); m_parameterUpper[7] = fittingData.getSwiMax().getValue().toDouble(); + m_parameterLower[8] = fittingData.getFractureConductivityMin().getValue().toDouble(); + m_parameterUpper[8] = fittingData.getFractureConductivityMax().getValue().toDouble(); + // 更新启用参数索引 m_enabledParamIndices.clear(); @@ -3425,6 +3473,15 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(tr("Applying optimized parameters to model...")); applyParametersToDataManager(m_globalBestPosition); + // 即使用户此时停止、不再执行最终完整计算,也要把 PEBI 缓存恢复为 + // 最终已接受的 Dfc,避免缓存仍停留在最后一个被拒绝的候选值。 + if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) { + nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance(); + if(!pebiGrid || !pebiGrid->generateOutputPara()) { + throw std::runtime_error("Failed to refresh final fracture conductivity"); + } + } + if(m_shouldStop) { // 手动停止优先保持快速返回,仅写回已确认的最优参数。 emit logMessageGenerated(tr("Final full-field calculation skipped after user stop")); @@ -3638,6 +3695,22 @@ void nmCalculationAutoFitPSO::extractUserInitialValues() case 7: // 初始含水饱和度 initialValue = reservoirData.getSwi().getValue().toDouble(); break; + + case 8: // 裂缝导流能力 + if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getDfc().getValue().toDouble(); + } + } else if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getDfc().getValue().toDouble(); + } + } + break; } m_initialValues.append(initialValue); @@ -5461,6 +5534,17 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter return 1e10; } + // Dfc 位于 PEBI 裂缝数组 crack[5],不是每次求解都会重新组装的 Base/CS 参数。 + // 因此只有勾选 Dfc 时才刷新一次网格输出参数,保证本次真实试算使用刚写入井对象的值。 + if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) { + nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance(); + if(!pebiGrid || !pebiGrid->generateOutputPara()) { + DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture conductivity") + .arg(funcName).arg(callCount)); + return 1e10; + } + } + // 4. 运行求解器。真实求解器偶发失败时允许重试,避免一次 DLL 调用异常 // 直接让整个粒子评价失败。 QVector> solverResult; @@ -5623,7 +5707,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector& p nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - // paramIndex 是粒子 position 中的索引;i 是完整 8 个参数体系中的索引。 + // paramIndex 是粒子 position 中的索引;i 是完整 9 个参数体系中的索引。 // 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。 int paramIndex = 0; @@ -5669,7 +5753,8 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame { // 更新目标井上的拟合参数。目前井级可拟合参数主要是: // - skin:写入第一个 perforation; - // - wellboreC:写入井筒储集系数。 + // - wellboreC:写入井筒储集系数; + // - Dfc:只写入垂直压裂井或多段压裂水平井的裂缝导流能力。 // 如果目标井不存在或没有射孔数据,这里只记录 debug,不抛异常。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -5705,6 +5790,27 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame pWell->setWellboreStorage(wellboreAttr); } break; + + case 8: { // 裂缝导流能力 + if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(pWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(value); + fracturedWell->setDfc(dfc); + } + } else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(value); + fracturedWell->setDfc(dfc); + } + } + } + break; } paramIndex++; @@ -6139,6 +6245,14 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector& paramete return false; } + break; + + case 8: // 裂缝导流能力:0 表示无限导流,不能作为连续拟合搜索点 + if(value <= 1e-10) { + DEBUG_OUT(QString("Rejecting non-positive fracture conductivity: %1").arg(value)); + return false; + } + break; } } diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp index 4d3b6d8..a7a3462 100644 --- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp +++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp @@ -11,6 +11,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_ctSelected = true; // 默认选中 m_cfSelected = false; // 默认不选中 m_swiSelected = false; // 默认不选中 + m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中 // 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。 m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy"); @@ -21,6 +22,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_ctMax = nmDataAttribute("Ct Max", QVariant(), ""); m_cfMax = nmDataAttribute("Cf Max", QVariant(), ""); m_swiMax = nmDataAttribute("Swi Max", QVariant(), ""); + m_fractureConductivityMax = nmDataAttribute("Fracture Conductivity Max", QVariant(), "md.m"); m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy"); m_skinMin = nmDataAttribute("Skin Min", QVariant(), ""); @@ -30,6 +32,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_ctMin = nmDataAttribute("Ct Min", QVariant(), ""); m_cfMin = nmDataAttribute("Cf Min", QVariant(), ""); m_swiMin = nmDataAttribute("Swi Min", QVariant(), ""); + m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "md.m"); // 初始化迭代参数 m_iterationCount = nmDataAttribute("Iteration Count", 20, ""); @@ -59,6 +62,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_ctSelected = other.m_ctSelected; m_cfSelected = other.m_cfSelected; m_swiSelected = other.m_swiSelected; + m_fractureConductivitySelected = other.m_fractureConductivitySelected; // 复制参数最大值 m_permeabilityMax = other.m_permeabilityMax; @@ -69,6 +73,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_ctMax = other.m_ctMax; m_cfMax = other.m_cfMax; m_swiMax = other.m_swiMax; + m_fractureConductivityMax = other.m_fractureConductivityMax; // 复制参数最小值 m_permeabilityMin = other.m_permeabilityMin; @@ -79,6 +84,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_ctMin = other.m_ctMin; m_cfMin = other.m_cfMin; m_swiMin = other.m_swiMin; + m_fractureConductivityMin = other.m_fractureConductivityMin; // 复制迭代参数 m_iterationCount = other.m_iterationCount; @@ -102,6 +108,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CtSelected", m_ctSelected, allocator); fittingObject.AddMember("CfSelected", m_cfSelected, allocator); fittingObject.AddMember("SwiSelected", m_swiSelected, allocator); + fittingObject.AddMember("FractureConductivitySelected", m_fractureConductivitySelected, allocator); // 序列化参数最大值 fittingObject.AddMember("PermeabilityMax", m_permeabilityMax.ToJsonValue(allocator), allocator); @@ -112,6 +119,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CtMax", m_ctMax.ToJsonValue(allocator), allocator); fittingObject.AddMember("CfMax", m_cfMax.ToJsonValue(allocator), allocator); fittingObject.AddMember("SwiMax", m_swiMax.ToJsonValue(allocator), allocator); + fittingObject.AddMember("FractureConductivityMax", m_fractureConductivityMax.ToJsonValue(allocator), allocator); // 序列化参数最小值 fittingObject.AddMember("PermeabilityMin", m_permeabilityMin.ToJsonValue(allocator), allocator); @@ -122,6 +130,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CtMin", m_ctMin.ToJsonValue(allocator), allocator); fittingObject.AddMember("CfMin", m_cfMin.ToJsonValue(allocator), allocator); fittingObject.AddMember("SwiMin", m_swiMin.ToJsonValue(allocator), allocator); + fittingObject.AddMember("FractureConductivityMin", m_fractureConductivityMin.ToJsonValue(allocator), allocator); // 序列化迭代参数 fittingObject.AddMember("IterationCount", m_iterationCount.ToJsonValue(allocator), allocator); @@ -159,6 +168,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("SwiSelected") && jsonValue["SwiSelected"].IsBool()) { m_swiSelected = jsonValue["SwiSelected"].GetBool(); } + if (jsonValue.HasMember("FractureConductivitySelected") && jsonValue["FractureConductivitySelected"].IsBool()) { + m_fractureConductivitySelected = jsonValue["FractureConductivitySelected"].GetBool(); + } // 反序列化参数最大值 if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) { @@ -185,6 +197,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("SwiMax") && jsonValue["SwiMax"].IsObject()) { m_swiMax.FromJsonValue(jsonValue["SwiMax"]); } + if (jsonValue.HasMember("FractureConductivityMax") && jsonValue["FractureConductivityMax"].IsObject()) { + m_fractureConductivityMax.FromJsonValue(jsonValue["FractureConductivityMax"]); + } // 反序列化参数最小值 if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) { @@ -211,6 +226,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("SwiMin") && jsonValue["SwiMin"].IsObject()) { m_swiMin.FromJsonValue(jsonValue["SwiMin"]); } + if (jsonValue.HasMember("FractureConductivityMin") && jsonValue["FractureConductivityMin"].IsObject()) { + m_fractureConductivityMin.FromJsonValue(jsonValue["FractureConductivityMin"]); + } // 反序列化迭代参数 if (jsonValue.HasMember("IterationCount") && jsonValue["IterationCount"].IsObject()) { @@ -253,6 +271,9 @@ void nmDataAutomaticFitting::setCfSelected(bool selected) { m_cfSelected = selec bool nmDataAutomaticFitting::getSwiSelected() const { return m_swiSelected; } void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = selected; } +bool nmDataAutomaticFitting::getFractureConductivitySelected() const { return m_fractureConductivitySelected; } +void nmDataAutomaticFitting::setFractureConductivitySelected(bool selected) { m_fractureConductivitySelected = selected; } + // Getter and Setter implementations for Max values nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMax() { return m_permeabilityMax; } @@ -279,6 +300,9 @@ void nmDataAutomaticFitting::setCfMax(const nmDataAttribute& cfMax) { m_cfMax = nmDataAttribute& nmDataAutomaticFitting::getSwiMax() { return m_swiMax; } void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax = swiMax; } +nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMax() { return m_fractureConductivityMax; } +void nmDataAutomaticFitting::setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax) { m_fractureConductivityMax = fractureConductivityMax; } + // Getter and Setter implementations for Min values nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMin() { return m_permeabilityMin; } void nmDataAutomaticFitting::setPermeabilityMin(const nmDataAttribute& permeabilityMin) { m_permeabilityMin = permeabilityMin; } @@ -304,6 +328,9 @@ void nmDataAutomaticFitting::setCfMin(const nmDataAttribute& cfMin) { m_cfMin = nmDataAttribute& nmDataAutomaticFitting::getSwiMin() { return m_swiMin; } void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin = swiMin; } +nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMin() { return m_fractureConductivityMin; } +void nmDataAutomaticFitting::setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin) { m_fractureConductivityMin = fractureConductivityMin; } + // Getter and Setter implementations for iteration parameters nmDataAttribute& nmDataAutomaticFitting::getIterationCount() { return m_iterationCount; } void nmDataAutomaticFitting::setIterationCount(const nmDataAttribute& iterationCount) { m_iterationCount = iterationCount; } diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 2a527ee..34415d0 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -190,7 +190,7 @@ void nmWxAutomaticFitting::renumberVisibleParameterRows(QTableWidget* table) } } -// 根据当前模型类型控制Ct/Cf/Swi参数显示。 +// 根据当前模型类型控制 Ct/Cf/Swi,并根据目标井类型控制裂缝导流能力显示。 void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType) { if(!table) { @@ -231,6 +231,21 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL setParameterRowVisible(table, 6, showCf); // Cf setParameterRowVisible(table, 7, showSwi); // Swi + // Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选, + // 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。 + bool showFractureConductivity = false; + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + if(manager && m_targetWellCombo) { + nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->currentText()); + if(targetWell) { + NM_WELL_MODEL wellType = targetWell->getWellType(); + showFractureConductivity = + wellType == NM_WELL_MODEL::Vertical_Fractured_Well || + wellType == NM_WELL_MODEL::Horizontal_Fractured_Well; + } + } + setParameterRowVisible(table, 8, showFractureConductivity); // Dfc + renumberVisibleParameterRows(table); } @@ -240,20 +255,20 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex, { static const char* parameterNames[] = { "Result_K", "Result_W_Skin", "Result_W_C", "Result_phi", - "Result_h", "Result_Cti", "Result_Cf", "Result_Swi" + "Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc" }; // KAPPA 的边界使用 md、ft、bbl/psi;自动拟合界面使用 Darcy、m、m^3/MPa, // 这里统一换算到界面和 PSO 实际使用的单位:K 除以 1000,h 由 ft 换成 m, // 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。 - // Ct/Cf/Swi 沿用模型参数表边界。 + // Ct/Cf/Swi/Dfc 沿用模型参数表边界。 static const double physicalMin[] = { - 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0 + 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0, 0.0 }; static const double physicalMax[] = { - 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0 + 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0, 1.0e30 }; - if(parameterIndex < 0 || parameterIndex >= 8) { + if(parameterIndex < 0 || parameterIndex >= 9) { return false; } @@ -338,6 +353,10 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, automaticFittingData.getSwiMin().setValue(minValue); automaticFittingData.getSwiMax().setValue(maxValue); break; + case 8: + automaticFittingData.getFractureConductivityMin().setValue(minValue); + automaticFittingData.getFractureConductivityMax().setValue(maxValue); + break; default: break; } @@ -350,7 +369,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double centerValue) { - if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 8 + if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 9 || !nmAutoFitUiIsFinite(centerValue)) { return; } @@ -383,7 +402,9 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, const double skinHalfRange = 10.0; newMin = qMax(physicalMin, reference - skinHalfRange); newMax = qMin(physicalMax, reference + skinHalfRange); - } else if(reference > 0.0 && !(parameterIndex == 7 && centerValue <= 0.0)) { + } else if(reference > 0.0 + && !(parameterIndex == 7 && centerValue <= 0.0) + && !(parameterIndex == 8 && centerValue <= 0.0)) { const double lowerFactor = 0.1; const double upperFactor = 10.0; newMin = qMax(physicalMin, reference * lowerFactor); @@ -392,6 +413,10 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, // 没有可靠 Swi 初值时,不把搜索范围压缩到零附近。 newMin = physicalMin; newMax = physicalMax; + } else if(parameterIndex == 8) { + // Dfc=0 表示无限导流,不存在以零为中心的连续倍率范围。 + newMin = physicalMin; + newMax = physicalMax; } // 任何自动范围都必须包含本次使用的中心值,并且不能越过物理边界。 @@ -412,7 +437,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges() return; } - for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { + for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) { QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); if(initialItem) { bool initialOk = false; @@ -439,7 +464,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges() return; } - for(int parameterIndex = 0; parameterIndex < 8; ++parameterIndex) { + for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) { QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2); QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4); QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); @@ -454,9 +479,12 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges() bool savedMaxOk = false; const double savedMin = minItem->text().toDouble(&savedMinOk); const double savedMax = maxItem->text().toDouble(&savedMaxOk); + // 旧项目没有 Dfc 字段时,nmDataAttribute 会表现为 0~0。Dfc=0 在求解器中 + // 表示无限导流,不能作为连续拟合区间,因此按当前压裂井初值重新建范围。 const bool savedRangeValid = savedMinOk && savedMaxOk && nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax) - && savedMax >= savedMin; + && savedMax >= savedMin + && !(parameterIndex == 8 && savedMax <= 1.0e-10); if(savedRangeValid && physicalMax >= physicalMin) { const double clippedMin = qMax(savedMin, physicalMin); @@ -485,18 +513,18 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par errorMessage = tr("The parameter table is unavailable."); return false; } - if(parameterIndex < -1 || parameterIndex >= 8) { + if(parameterIndex < -1 || parameterIndex >= 9) { errorMessage = tr("The parameter row is invalid."); return false; } static const char* parameterNames[] = { "Permeability", "Skin", "Wellbore storage", "Porosity", - "Thickness", "Ct", "Cf", "Swi" + "Thickness", "Ct", "Cf", "Swi", "Fracture conductivity" }; const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex; - const int lastParameterIndex = parameterIndex < 0 ? 8 : parameterIndex + 1; + const int lastParameterIndex = parameterIndex < 0 ? 9 : parameterIndex + 1; for(int currentParameterIndex = firstParameterIndex; currentParameterIndex < lastParameterIndex; ++currentParameterIndex) { // 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。 @@ -556,6 +584,14 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par .arg(tr(parameterNames[currentParameterIndex])); return false; } + + // 底层用 Dfc=0 表示无限导流,这是离散模型选项,不属于有限导流拟合域。 + if(currentParameterIndex == 8 && m_dfcCheckBox->isChecked() + && (minValue <= 1.0e-10 || initialValue <= 1.0e-10)) { + errorMessage = tr("The minimum value of %1 must be greater than zero for automatic fitting.") + .arg(tr(parameterNames[currentParameterIndex])); + return false; + } } return true; @@ -681,7 +717,7 @@ void nmWxAutomaticFitting::setupUI() void nmWxAutomaticFitting::setupParameterTable() { // 创建表格 - m_parameterTable = new QTableWidget(8, 6, this); + m_parameterTable = new QTableWidget(9, 6, this); // 设置表头 QStringList headers; @@ -790,6 +826,17 @@ void nmWxAutomaticFitting::setupParameterTable() m_parameterTable->setItem(7, 4, new QTableWidgetItem(QString::number(automaticFittingData.getSwiMax().getValue().toDouble()))); m_parameterTable->setItem(7, 5, new QTableWidgetItem("")); + // 裂缝导流能力 (Dfc)。该行只对压裂井显示,初值在 onWellSelected() 中 + // 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。 + m_parameterTable->setItem(8, 0, new QTableWidgetItem("9")); + m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity")); + m_dfcCheckBox->setChecked(automaticFittingData.getFractureConductivitySelected()); + m_parameterTable->setCellWidget(8, 1, m_dfcCheckBox); + m_parameterTable->setItem(8, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble()))); + m_parameterTable->setItem(8, 3, new QTableWidgetItem()); + m_parameterTable->setItem(8, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble()))); + m_parameterTable->setItem(8, 5, new QTableWidgetItem(tr("md.m"))); + // 设置表格行为 for(int i = 0; i < m_parameterTable->rowCount(); ++i) { for(int j = 0; j < 6; ++j) { @@ -989,6 +1036,7 @@ void nmWxAutomaticFitting::onReverseSelection() if(!m_parameterTable->isRowHidden(5)) m_ctCheckBox->setChecked(!m_ctCheckBox->isChecked()); if(!m_parameterTable->isRowHidden(6)) m_cfCheckBox->setChecked(!m_cfCheckBox->isChecked()); if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked()); + if(!m_parameterTable->isRowHidden(8)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked()); } void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) @@ -1107,7 +1155,7 @@ void nmWxAutomaticFitting::onAccept() m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() || m_hCheckBox->isChecked() || m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() || - m_swiCheckBox->isChecked(); + m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked(); if(!hasSelectedParams) { QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!")); @@ -1125,6 +1173,7 @@ void nmWxAutomaticFitting::onAccept() if(m_ctCheckBox->isChecked()) selectedParameterNames << tr("Ct"); if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf"); if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi"); + if(m_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity"); // 启动自动拟合 - 传递双对数历史数据 startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName); @@ -1141,8 +1190,10 @@ void nmWxAutomaticFitting::onWellSelected(int index) // 在分类的井数据中查找匹配的井 bool found = false; + bool fracturedWell = false; double skinValue = 0.0; double wellboreStorageValue = 0.0; + double fractureConductivityValue = 0.0; // 查找垂直井 for(int i = 0; i < m_verticalWells.size(); ++i) { @@ -1172,6 +1223,8 @@ void nmWxAutomaticFitting::onWellSelected(int index) if(m_verticalFracturedWells[i].getWellName() == selectedWellName) { skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble(); + fractureConductivityValue = m_verticalFracturedWells[i].getDfc().getValue().toDouble(); + fracturedWell = true; found = true; break; } @@ -1184,6 +1237,8 @@ void nmWxAutomaticFitting::onWellSelected(int index) if(m_horizontalFracturedWells[i].getWellName() == selectedWellName) { skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble(); + fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble(); + fracturedWell = true; found = true; break; } @@ -1191,6 +1246,11 @@ void nmWxAutomaticFitting::onWellSelected(int index) } if (found) { + // 先按目标井类型刷新可见行,再写入当前井的井级初值。 + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + if(manager) { + updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); + } // 更新表格数据 // 确保表格项存在 if(!m_parameterTable->item(1, 3)) { @@ -1205,10 +1265,16 @@ void nmWxAutomaticFitting::onWellSelected(int index) // 设置井筒储集系数(Wellbore storage) m_parameterTable->item(2, 3)->setText(QString::number(wellboreStorageValue)); + if(fracturedWell && m_parameterTable->item(8, 3)) { + m_parameterTable->item(8, 3)->setText(QString::number(fractureConductivityValue)); + } if(m_autoParameterRanges) { updateRangeForParameter(1, skinValue); updateRangeForParameter(2, wellboreStorageValue); + if(fracturedWell) { + updateRangeForParameter(8, fractureConductivityValue); + } } } } @@ -1224,6 +1290,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.setCtSelected(m_ctCheckBox->isChecked()); automaticFittingData.setCfSelected(m_cfCheckBox->isChecked()); automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked()); + automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked()); automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1); // 保存渗透率的最小值和最大值 @@ -1258,6 +1325,10 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.getSwiMin().setValue(m_parameterTable->item(7, 2)->text().toDouble()); automaticFittingData.getSwiMax().setValue(m_parameterTable->item(7, 4)->text().toDouble()); + // 保存裂缝导流能力的最小值和最大值 + automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble()); + automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble()); + // 保存迭代参数 automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); @@ -1281,6 +1352,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() if(!selectedWellName.isEmpty()) { double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble(); double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble(); + double newFractureConductivityValue = m_parameterTable->item(8, 3)->text().toDouble(); // 直接从数据管理器获取目标井 nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1303,6 +1375,23 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() // 根据井类型单独更新这一口井 NM_WELL_MODEL wellType = pTargetWell->getWellType(); + // Dfc 属于压裂井对象;只修改这一项,裂缝位置、长度和段数保持原值。 + if(wellType == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(newFractureConductivityValue); + fracturedWell->setDfc(dfc); + } + } else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); + if(fracturedWell) { + nmDataAttribute dfc = fracturedWell->getDfc(); + dfc.setValue(newFractureConductivityValue); + fracturedWell->setDfc(dfc); + } + } + if(wellType == NM_WELL_MODEL::Vertical_Well) { nmDataVerticalWell* pVerticalWell = dynamic_cast(pTargetWell); if(pVerticalWell != nullptr) { @@ -1532,6 +1621,7 @@ void nmWxAutomaticFitting::updateBestParametersToTable() if(m_ctCheckBox->isChecked()) enabledParams.append(5); // 综合压缩系数 if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数 if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度 + if(m_dfcCheckBox->isChecked()) enabledParams.append(8); // 裂缝导流能力 // 更新参数值和范围 for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) { From 50606d35641edc315d240deba8a44340ee6b3278 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 10:37:57 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8B=9F=E5=90=88?= =?UTF-8?q?=E6=8D=9F=E5=A4=B1=E5=87=BD=E6=95=B0=E6=94=B9=E7=94=A8=E6=99=AE?= =?UTF-8?q?=E9=80=9A=E5=AF=B9=E6=95=B0=20RMSE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nmCalculation/nmCalculationAutoFitPSO.h | 6 +- .../nmCalculation/nmCalculationAutoFitPSO.cpp | 129 ++++++------------ 2 files changed, 42 insertions(+), 93 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index 0bf415b..748831d 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -30,11 +30,11 @@ struct AutoFitObjectiveBreakdown { double total; double pressureLoss; double derivativeLoss; - // 固定目标网格上的 Huber 等效残差。非代理搜索使用它建立完整 Jacobian, + // 固定目标网格上的普通对数残差。非代理搜索使用它建立完整 Jacobian, // 向量平方和与 total 的平方一致。 QVector residualVector; - // 上下偏差使用压力和导数残差共享的 Huber 稳健中心。 + // 上下偏差使用压力和导数残差共享的算术平均中心。 // verticalCommonBias 为正表示模拟曲线整体偏高,为负表示整体偏低; // verticalReliable=false 时仍保留数值,但不能据此确定参数调整方向。 double verticalCommonBias; @@ -50,7 +50,7 @@ struct AutoFitObjectiveBreakdown { // 诊断量选参,但去除公共中心后的 shapeLoss 仍可用于局部选参。 bool registrationAmbiguous; - // 去除稳健公共中心和可信左右偏差后剩余的整体形状误差;verticalReliable + // 去除公共均值中心和可信左右偏差后剩余的整体形状误差;verticalReliable // 只控制能否把公共中心解释为上下参数方向,不改变 shape 的中心化公式。 double shapeLoss; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 2c75f29..7378599 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -104,8 +104,8 @@ static inline bool isFiniteNumber(double value) #endif } -// 两个 Huber RMS 的差不能直接解释为被消除的独立误差。RMS 的平方才对应 -// 稳健能量,因此先计算 reduced^2-full^2,再开方恢复原量纲。这里用于分别 +// 两个 RMSE 的差不能直接解释为被消除的独立误差。RMSE 的平方才对应 +// 均方能量,因此先计算 reduced^2-full^2,再开方恢复原量纲。这里用于分别 // 提取“消除公共上下偏差”和“消除水平位移”实际减少的误差贡献。 static double nestedRmsContribution(double reducedModelLoss, double fullModelLoss) @@ -3900,7 +3900,7 @@ struct TrustRegionEvaluation {} }; -// LM 只使用固定长度、全部有限的稳健残差。代理路径不会进入本搜索器。 +// LM 只使用固定长度、全部有限的普通残差。代理路径不会进入本搜索器。 static bool trustRegionResidualsValid( const AutoFitObjectiveBreakdown& breakdown) { @@ -4216,7 +4216,7 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() bool modelRebuiltAtMinimumRadius = false; StopReasonPSO stopReason = PSO_MAX_ITERATIONS; - // jacobian 的行对应固定 100 维稳健残差,列对应用户勾选的参数。 + // jacobian 的行对应固定 100 维残差,列对应用户勾选的参数。 // 三个 gradient 单独描述诊断分量对参数的局部变化,只用于本轮选参。 QVector > jacobian; QVector verticalGradient(dimensions, 0.0); @@ -4803,7 +4803,7 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() } stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); - // 用线性模型 r_new ~= r_current + J*step 预测稳健残差,再用平方能量 + // 用线性模型 r_new ~= r_current + J*step 预测残差,再用平方能量 // 的下降量与真实候选下降量比较,作为调整阻尼和半径的依据。 QVector predictedResidual = current.breakdown.residualVector; @@ -6462,9 +6462,6 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( // 位于 log(time)-log(value) 坐标,因此得到的是相对尺度偏差而非原始压力量纲。 const double invalidLoss = 1.0e10; const double valueFloor = 1.0e-12; - // Huber 转折点 qLn(1.2) 对应约 20% 的倍率偏差;小偏差保持平方惩罚, - // 更大的局部尖峰改为近似线性惩罚,避免单点支配整条曲线。 - const double huberDelta = qLn(1.2); const double minimumCoverage = 0.95; const int numPoints = 50; m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); @@ -6704,7 +6701,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } // 通过门槛后最多只缺少首尾少量目标点。按模拟曲线端点趋势补齐后, - // 每个候选仍在固定 50 点上计算 Huber 均值,不能靠少算难拟合端点获益。 + // 每个候选仍在固定 50 点上计算均方根误差,不能靠少算难拟合端点获益。 for(int i = 0; i < numPoints; ++i) { if(isFiniteNumber(pressureResidual[i]) && isFiniteNumber(derivativeResidual[i])) { @@ -6726,8 +6723,8 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( resultLogDerivative - targetLogDerivative[i]; } - // Huber RMS 在小残差处保持平方损失,在异常点处转为线性增长。 - auto huberRmsAround = [huberDelta]( + // 在指定中心附近计算普通均方根误差。 + auto rmseAround = []( const QVector& values, int begin, int end, @@ -6744,12 +6741,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } double difference = values[i] - center; - double absoluteValue = qAbs(difference); - double rho = absoluteValue <= huberDelta - ? difference * difference - : 2.0 * huberDelta * absoluteValue - - huberDelta * huberDelta; - sum += rho; + sum += difference * difference; ++count; } @@ -6758,27 +6750,15 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( : std::numeric_limits::quiet_NaN(); }; - auto huberRms = [&huberRmsAround]( + auto rmse = [&rmseAround]( const QVector& values, int begin, int end) -> double { - return huberRmsAround(values, begin, end, 0.0); + return rmseAround(values, begin, end, 0.0); }; - // 将 Huber 能量转换成带符号的等效残差,使向量平方和与稳健损失一致。 - // 非代理 LM 直接对该固定长度向量建立 Jacobian。 - auto huberEquivalentResidual = [huberDelta](double value) -> double { - double absoluteValue = qAbs(value); - double rho = absoluteValue <= huberDelta - ? value * value - : 2.0 * huberDelta * absoluteValue - - huberDelta * huberDelta; - double magnitude = qSqrt(qMax(0.0, rho)); - return value < 0.0 ? -magnitude : magnitude; - }; - - // Huber 加权中心保留上下偏差的符号,并降低局部尖峰的影响。 - auto huberCenterRange = [huberDelta]( + // 普通算术平均中心保留上下偏差的符号。 + auto meanCenterRange = []( const QVector& values, int begin, int end) -> double { @@ -6797,42 +6777,12 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( if(count == 0) { return std::numeric_limits::quiet_NaN(); } - center /= count; - - for(int iteration = 0; iteration < 8; ++iteration) { - double weightedSum = 0.0; - double weightTotal = 0.0; - for(int i = validBegin; i < validEnd; ++i) { - if(!isFiniteNumber(values[i])) { - continue; - } - - double distance = qAbs(values[i] - center); - double weight = - distance <= huberDelta || - distance < 1.0e-12 - ? 1.0 - : huberDelta / distance; - weightedSum += weight * values[i]; - weightTotal += weight; - } - - if(weightTotal <= 1.0e-12) { - break; - } - double nextCenter = weightedSum / weightTotal; - if(qAbs(nextCenter - center) <= 1.0e-12) { - center = nextCenter; - break; - } - center = nextCenter; - } - return center; + return center / count; }; // 压力和导数合并后只求一个公共中心,表示两条曲线共同的上下位移。 // 分别去中心会把压力与导数之间真实的相对形状差异一并消除。 - auto huberCommonCenterRange = [&huberCenterRange]( + auto commonMeanCenterRange = [&meanCenterRange]( const QVector& pressureValues, const QVector& derivativeValues, int begin, @@ -6853,19 +6803,19 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( combined.append(derivativeValues[i]); } } - return huberCenterRange(combined, 0, combined.size()); + return meanCenterRange(combined, 0, combined.size()); }; - // 两个通道按能量等权合并,返回值与单通道 Huber RMS 保持同一量纲。 - auto jointHuberRmsAround = [&huberRmsAround]( + // 两个通道按能量等权合并,返回值与单通道 RMSE 保持同一量纲。 + auto jointRmseAround = [&rmseAround]( const QVector& pressureValues, const QVector& derivativeValues, int begin, int end, double center) -> double { - double pressureLoss = huberRmsAround( + double pressureLoss = rmseAround( pressureValues, begin, end, center); - double derivativeLoss = huberRmsAround( + double derivativeLoss = rmseAround( derivativeValues, begin, end, center); if(!isFiniteNumber(pressureLoss) || !isFiniteNumber(derivativeLoss)) { @@ -6878,9 +6828,9 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( // 主目标始终使用未做上下或左右校正的完整曲线误差。 breakdown.pressureLoss = - huberRms(pressureResidual, 0, numPoints); + rmse(pressureResidual, 0, numPoints); breakdown.derivativeLoss = - huberRms(derivativeResidual, 0, numPoints); + rmse(derivativeResidual, 0, numPoints); // 压力和导数各占一半权重。缩放后 residualVector 的二范数就是 // sqrt(0.5 * pressureLoss^2 + 0.5 * derivativeLoss^2)。 @@ -6889,12 +6839,12 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( for(int i = 0; i < numPoints; ++i) { breakdown.residualVector.append( residualScale * - huberEquivalentResidual(pressureResidual[i])); + pressureResidual[i]); } for(int i = 0; i < numPoints; ++i) { breakdown.residualVector.append( residualScale * - huberEquivalentResidual(derivativeResidual[i])); + derivativeResidual[i]); } const double logGridStep = @@ -7049,31 +6999,31 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( continue; } - double commonBias = huberCommonCenterRange( + double commonBias = commonMeanCenterRange( shiftedPressureResidual, shiftedDerivativeResidual, registrationBegin, registrationEnd); - double centeredLoss = jointHuberRmsAround( + double centeredLoss = jointRmseAround( shiftedPressureResidual, shiftedDerivativeResidual, registrationBegin, registrationEnd, commonBias); - double pressureBias = huberCenterRange( + double pressureBias = meanCenterRange( shiftedPressureResidual, registrationBegin, registrationEnd); - double derivativeBias = huberCenterRange( + double derivativeBias = meanCenterRange( shiftedDerivativeResidual, registrationBegin, registrationEnd); - double pressureLoss = huberRmsAround( + double pressureLoss = rmseAround( shiftedPressureResidual, registrationBegin, registrationEnd, pressureBias); - double derivativeLoss = huberRmsAround( + double derivativeLoss = rmseAround( shiftedDerivativeResidual, registrationBegin, registrationEnd, @@ -7124,7 +7074,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( return invalidLoss; } - // horizontalGain 是“允许水平位移”相对“固定零位移”减少的稳健能量。 + // horizontalGain 是“允许水平位移”相对“固定零位移”减少的均方能量。 // 只有改善足够明显且最优点不是边界,才把位移解释为可靠左右偏差。 double horizontalGain = nestedRmsContribution( zeroShiftCenteredLoss, bestCenteredLoss); @@ -7183,8 +7133,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( isFiniteNumber(maximumNearOptimalBias) ? maximumNearOptimalBias - minimumNearOptimalBias : std::numeric_limits::infinity(); - bool commonBiasStable = nearOptimalBiasSpread <= - qMax(1.0e-4, huberDelta * 0.05); + bool commonBiasStable = nearOptimalBiasSpread <= 1.0e-2; bool horizontalAtBoundary = halfShiftStepCount > 0 && qAbs(bestShiftStep) == halfShiftStepCount; @@ -7249,18 +7198,18 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( return invalidLoss; } - double commonBias = huberCommonCenterRange( + double commonBias = commonMeanCenterRange( shiftedPressureResidual, shiftedDerivativeResidual, diagnosticBegin, diagnosticEnd); - double rawAlignedLoss = jointHuberRmsAround( + double rawAlignedLoss = jointRmseAround( shiftedPressureResidual, shiftedDerivativeResidual, diagnosticBegin, diagnosticEnd, 0.0); - double centeredAlignedLoss = jointHuberRmsAround( + double centeredAlignedLoss = jointRmseAround( shiftedPressureResidual, shiftedDerivativeResidual, diagnosticBegin, @@ -7297,10 +7246,10 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } } - // shapeLoss 是去除可信左右位移和稳健公共中心后的剩余误差。 - double shapePressureLoss = huberRms( + // shapeLoss 是去除可信左右位移和公共均值中心后的剩余误差。 + double shapePressureLoss = rmse( shapePressure, diagnosticBegin, diagnosticEnd); - double shapeDerivativeLoss = huberRms( + double shapeDerivativeLoss = rmse( shapeDerivative, diagnosticBegin, diagnosticEnd); if(!isFiniteNumber(shapePressureLoss) || !isFiniteNumber(shapeDerivativeLoss)) { @@ -7333,7 +7282,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( 0.5 * breakdown.pressureLoss + 0.5 * breakdown.derivativeLoss; } else { - // 非代理总目标等于固定 100 维 Huber 等效残差的二范数;压力和 + // 非代理总目标等于固定 100 维普通残差的二范数;压力和 // 导数各占一半能量。上下、左右和形状分量不参与候选排序与接受。 breakdown.total = qSqrt( 0.5 * breakdown.pressureLoss * breakdown.pressureLoss + From e73b5ab45335b00ef7d1961b228ca7f2ec3bf434 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 11:23:23 +0800 Subject: [PATCH 09/12] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E5=8E=8B=E8=A3=82?= =?UTF-8?q?=E4=BA=95=E8=A3=82=E7=BC=9D=E5=8D=8A=E9=95=BF=E6=8B=9F=E5=90=88?= =?UTF-8?q?=E5=8F=82=E6=95=B0=20=202.=E5=8F=82=E6=95=B0=E7=89=A9=E7=90=86?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=E7=BB=9F=E4=B8=80=E7=94=A8=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E4=B8=AD=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 93895 -> 93972 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 4 + .../nmCalculation/nmCalculationAutoFitPSO.h | 11 +- Include/nmNum/nmData/nmDataAutomaticFitting.h | 13 +++ Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 1 + .../nmCalculation/nmCalculationAutoFitPSO.cpp | 105 +++++++++++++----- Src/nmNum/nmData/nmDataAutomaticFitting.cpp | 27 +++++ Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 100 ++++++++++------- 8 files changed, 191 insertions(+), 70 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 14a0e5f0d7b69500fcb31946cf2f7bbf3d0a7285..c9e8ed0c65c5f7422395fd6f64d2faf6de240c85 100644 GIT binary patch delta 5605 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 TV(K=%2VLK2Q`~f|WyAjgmn?nV delta 5700 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 Zo#kU9JYD1COVpzUKHf)Fracture conductivity 裂缝导流能力 + + Fracture half length + 裂缝半长 + The minimum value of %1 must be greater than zero for automatic fitting. 自动拟合时,%1 的最小值和初始值必须大于零。 diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index 748831d..9731259 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -372,12 +372,13 @@ private: // 参数索引约定: // 0 k 渗透率;1 skin 表皮系数;2 wellboreC 井筒储集; // 3 phi 孔隙度;4 h 储层厚度;5 Ct 综合压缩系数; - // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度。 + // 6 Cf 岩石压缩系数;7 Swi 初始含水饱和度; + // 8 Dfc 裂缝导流能力;9 fractureHalfLength 裂缝半长。 // m_enabledParamIndices 保存被用户勾选的参数索引,粒子的 position 维度与它一致。 - QVector m_parameterSelected; // 完整 9 个参数是否被用户勾选参与拟合。 - QVector m_parameterLower; // 完整 9 个参数的搜索下界。 - QVector m_parameterUpper; // 完整 9 个参数的搜索上界。 - QVector m_enabledParamIndices; // 被勾选参数在完整 9 维体系中的索引。 + QVector m_parameterSelected; // 完整 10 个参数是否被用户勾选参与拟合。 + QVector m_parameterLower; // 完整 10 个参数的搜索下界。 + QVector m_parameterUpper; // 完整 10 个参数的搜索上界。 + QVector m_enabledParamIndices; // 被勾选参数在完整 10 维体系中的索引。 QVector > m_targetLogLogData; // 目标井 history log-log 曲线:time/pressure/derivative。 QString m_targetWellName; // 目标井名称;读写井参数和读取模拟曲线都依赖它。 diff --git a/Include/nmNum/nmData/nmDataAutomaticFitting.h b/Include/nmNum/nmData/nmDataAutomaticFitting.h index 18bc851..98829eb 100644 --- a/Include/nmNum/nmData/nmDataAutomaticFitting.h +++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h @@ -82,6 +82,13 @@ public: nmDataAttribute& getFractureConductivityMin(); void setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin); + // Getter and Setter for fractureHalfLengthMax + nmDataAttribute& getFractureHalfLengthMax(); + void setFractureHalfLengthMax(const nmDataAttribute& fractureHalfLengthMax); + // Getter and Setter for fractureHalfLengthMin + nmDataAttribute& getFractureHalfLengthMin(); + void setFractureHalfLengthMin(const nmDataAttribute& fractureHalfLengthMin); + // Getter and Setter for iteration count nmDataAttribute& getIterationCount(); void setIterationCount(const nmDataAttribute& iterationCount); @@ -124,6 +131,9 @@ public: bool getFractureConductivitySelected() const; void setFractureConductivitySelected(bool selected); + bool getFractureHalfLengthSelected() const; + void setFractureHalfLengthSelected(bool selected); + private: // 参数最大值 nmDataAttribute m_permeabilityMax; @@ -135,6 +145,7 @@ private: nmDataAttribute m_cfMax; nmDataAttribute m_swiMax; nmDataAttribute m_fractureConductivityMax; + nmDataAttribute m_fractureHalfLengthMax; // 参数最小值 nmDataAttribute m_permeabilityMin; @@ -146,6 +157,7 @@ private: nmDataAttribute m_cfMin; nmDataAttribute m_swiMin; nmDataAttribute m_fractureConductivityMin; + nmDataAttribute m_fractureHalfLengthMin; // 迭代参数 nmDataAttribute m_iterationCount; // 迭代步数 @@ -163,6 +175,7 @@ private: bool m_cfSelected; // 是否选择岩石压缩系数进行拟合 bool m_swiSelected; // 是否选择初始含水饱和度进行拟合 bool m_fractureConductivitySelected; // 是否选择裂缝导流能力进行拟合 + bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合 }; #endif // NMDATAAUTOMATICFITTING_H diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index ada66d0..a8c4f76 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -92,6 +92,7 @@ private: QCheckBox* m_cfCheckBox; // 岩石压缩系数 QCheckBox* m_swiCheckBox; // 初始含水饱和度 QCheckBox* m_dfcCheckBox; // 裂缝导流能力 + QCheckBox* m_fractureHalfLengthCheckBox; // 裂缝半长 // 按钮 QPushButton* m_reverseBtn; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 7378599..0f37602 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -432,7 +432,7 @@ static QString findExecutableInPath(const QString& executableName) static QStringList traceParameterNames() { // trace 和 trace meta 使用的完整参数名顺序。 - // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-8 索引一致。 + // 这个顺序必须与 buildTraceParameterVector() 和 m_parameterSelected 的 0-9 索引一致。 QStringList names; names << "k" << "skin" @@ -442,7 +442,8 @@ static QStringList traceParameterNames() << "Ct" << "Cf" << "Swi" - << "Dfc"; + << "Dfc" + << "fractureHalfLength"; return names; } @@ -1013,7 +1014,7 @@ void nmCalculationAutoFitPSO::writeTraceHeader() { // trace CSV 字段说明: // - 代理模式保持原 k/skin/wellboreC/phi/h/Ct/Cf 契约; - // - 非代理模式额外记录 Swi/Dfc,便于复盘信赖域对两项参数的调整; + // - 非代理模式额外记录 Swi/Dfc/裂缝半长,便于复盘信赖域调整; // - solver_objective 是真实求解器误差; // - surrogate_objective 是 Python 代理评分; // - screening_decision 说明该粒子为什么跑/不跑真实求解器; @@ -1037,7 +1038,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "Cf"; if(!isSurrogateScreeningEnabled()) { cols << "Swi" - << "Dfc"; + << "Dfc" + << "fractureHalfLength"; } cols << "solver_objective" << "solver_success" @@ -1054,7 +1056,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "pbest_Cf"; if(!isSurrogateScreeningEnabled()) { cols << "pbest_Swi" - << "pbest_Dfc"; + << "pbest_Dfc" + << "pbest_fractureHalfLength"; } cols << "gbest_objective" << "gbest_k" @@ -1066,7 +1069,8 @@ void nmCalculationAutoFitPSO::writeTraceHeader() << "gbest_Cf"; if(!isSurrogateScreeningEnabled()) { cols << "gbest_Swi" - << "gbest_Dfc"; + << "gbest_Dfc" + << "gbest_fractureHalfLength"; } cols << "enabled_param_indices" << "pressure_loss" @@ -1132,7 +1136,7 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile() // 非代理 v5 增加带符号诊断列;代理 PSO 保留原 v3 字段和目标,避免改变 // 已有模型的训练和回放契约。 out << " \"schema_version\": " - << (isSurrogateScreeningEnabled() ? 3 : 6) << ",\n"; + << (isSurrogateScreeningEnabled() ? 3 : 7) << ",\n"; out << " \"trace_type\": " << jsonEscape(isSurrogateScreeningEnabled() ? "pso_baseline_replay_meta" @@ -1193,10 +1197,10 @@ void nmCalculationAutoFitPSO::writeTraceMetaFile() QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector& selectedParameters) const { - // 将粒子内部使用的“启用参数向量”还原成完整 9 维参数向量。 + // 将粒子内部使用的“启用参数向量”还原成完整 10 维参数向量。 // 未启用的参数从当前 DataManager 读取,启用的参数用 selectedParameters 覆盖。 // trace CSV、候选 CSV、代理训练域检查都需要这个完整向量。 - QVector fullParams(9, 0.0); + QVector fullParams(10, 0.0); nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1224,12 +1228,14 @@ QVector nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector dynamic_cast(pTargetWell); if(fracturedWell) { fullParams[8] = fracturedWell->getDfc().getValue().toDouble(); + fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble(); } } else if(pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); if(fracturedWell) { fullParams[8] = fracturedWell->getDfc().getValue().toDouble(); + fullParams[9] = fracturedWell->getFractureHalfLength().getValue().toDouble(); } } } @@ -1290,7 +1296,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(currentParams, 6); if(!isSurrogateScreeningEnabled()) { cols << traceParamAt(currentParams, 7) - << traceParamAt(currentParams, 8); + << traceParamAt(currentParams, 8) + << traceParamAt(currentParams, 9); } cols << traceNumber(solverObjective) << QString::number(solverSuccess ? 1 : 0) @@ -1307,7 +1314,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(pbestParams, 6); if(!isSurrogateScreeningEnabled()) { cols << traceParamAt(pbestParams, 7) - << traceParamAt(pbestParams, 8); + << traceParamAt(pbestParams, 8) + << traceParamAt(pbestParams, 9); } cols << traceNumber(m_globalBestFitness) << traceParamAt(gbestParams, 0) @@ -1319,7 +1327,8 @@ void nmCalculationAutoFitPSO::writeTraceRow(int generation, << traceParamAt(gbestParams, 6); if(!isSurrogateScreeningEnabled()) { cols << traceParamAt(gbestParams, 7) - << traceParamAt(gbestParams, 8); + << traceParamAt(gbestParams, 8) + << traceParamAt(gbestParams, 9); } cols << csvEscape(enabledIndices.join(";")); @@ -2917,14 +2926,14 @@ void nmCalculationAutoFitPSO::loadParameterBounds() // 读取用户勾选的拟合参数及上下界。 // // 这里构建三个核心数组: - // - m_parameterSelected[9]:完整参数体系中每个参数是否参与拟合; - // - m_parameterLower/Upper[9]:完整参数体系的搜索上下界; + // - m_parameterSelected[10]:完整参数体系中每个参数是否参与拟合; + // - m_parameterLower/Upper[10]:完整参数体系的搜索上下界; // - m_enabledParamIndices:把粒子内部紧凑向量映射回完整参数索引。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); // 获取参数选择状态 - m_parameterSelected.resize(9); + m_parameterSelected.resize(10); m_parameterSelected[0] = fittingData.getPermeabilitySelected(); m_parameterSelected[1] = fittingData.getSkinSelected(); m_parameterSelected[2] = fittingData.getWellboreStorageSelected(); @@ -2934,10 +2943,11 @@ void nmCalculationAutoFitPSO::loadParameterBounds() m_parameterSelected[6] = fittingData.getCfSelected(); m_parameterSelected[7] = fittingData.getSwiSelected(); m_parameterSelected[8] = fittingData.getFractureConductivitySelected(); + m_parameterSelected[9] = fittingData.getFractureHalfLengthSelected(); // 获取参数边界 - m_parameterLower.resize(9); - m_parameterUpper.resize(9); + m_parameterLower.resize(10); + m_parameterUpper.resize(10); m_parameterLower[0] = fittingData.getPermeabilityMin().getValue().toDouble(); m_parameterUpper[0] = fittingData.getPermeabilityMax().getValue().toDouble(); @@ -2966,6 +2976,9 @@ void nmCalculationAutoFitPSO::loadParameterBounds() m_parameterLower[8] = fittingData.getFractureConductivityMin().getValue().toDouble(); m_parameterUpper[8] = fittingData.getFractureConductivityMax().getValue().toDouble(); + m_parameterLower[9] = fittingData.getFractureHalfLengthMin().getValue().toDouble(); + m_parameterUpper[9] = fittingData.getFractureHalfLengthMax().getValue().toDouble(); + // 更新启用参数索引 m_enabledParamIndices.clear(); @@ -3474,11 +3487,14 @@ bool nmCalculationAutoFitPSO::startAutoFitting() applyParametersToDataManager(m_globalBestPosition); // 即使用户此时停止、不再执行最终完整计算,也要把 PEBI 缓存恢复为 - // 最终已接受的 Dfc,避免缓存仍停留在最后一个被拒绝的候选值。 - if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) { + // 最终已接受的裂缝参数,避免缓存仍停留在最后一个被拒绝的候选值。 + const bool fractureGridParameterSelected = + (m_parameterSelected.size() > 8 && m_parameterSelected[8]) || + (m_parameterSelected.size() > 9 && m_parameterSelected[9]); + if(fractureGridParameterSelected) { nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance(); if(!pebiGrid || !pebiGrid->generateOutputPara()) { - throw std::runtime_error("Failed to refresh final fracture conductivity"); + throw std::runtime_error("Failed to refresh final fracture parameters"); } } @@ -3711,6 +3727,22 @@ void nmCalculationAutoFitPSO::extractUserInitialValues() } } break; + + case 9: // 裂缝半长 + if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getFractureHalfLength().getValue().toDouble(); + } + } else if(pTargetWell && pTargetWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pTargetWell); + if(fracturedWell) { + initialValue = fracturedWell->getFractureHalfLength().getValue().toDouble(); + } + } + break; } m_initialValues.append(initialValue); @@ -5534,12 +5566,15 @@ double nmCalculationAutoFitPSO::evaluateFitness(const QVector& parameter return 1e10; } - // Dfc 位于 PEBI 裂缝数组 crack[5],不是每次求解都会重新组装的 Base/CS 参数。 - // 因此只有勾选 Dfc 时才刷新一次网格输出参数,保证本次真实试算使用刚写入井对象的值。 - if(m_parameterSelected.size() > 8 && m_parameterSelected[8]) { + // Dfc 和裂缝半长都通过 PEBI 裂缝数组传入,不属于每次求解都会重新组装的 Base/CS 参数。 + // 勾选任一裂缝参数时刷新网格输出,保证本次真实试算使用新的导流能力和端点坐标。 + const bool fractureGridParameterSelected = + (m_parameterSelected.size() > 8 && m_parameterSelected[8]) || + (m_parameterSelected.size() > 9 && m_parameterSelected[9]); + if(fractureGridParameterSelected) { nmCalculationPebiGrid* pebiGrid = nmCalculationPebiGrid::getInstance(); if(!pebiGrid || !pebiGrid->generateOutputPara()) { - DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture conductivity") + DEBUG_OUT(QString("%1: Call #%2 - Failed to refresh PEBI fracture parameters") .arg(funcName).arg(callCount)); return 1e10; } @@ -5707,7 +5742,7 @@ void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector& p nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataReservoir reservoirData = dataManager->getReservoirDataCopy(); - // paramIndex 是粒子 position 中的索引;i 是完整 9 个参数体系中的索引。 + // paramIndex 是粒子 position 中的索引;i 是完整 10 个参数体系中的索引。 // 只有 m_parameterSelected[i] 为 true 时,才从 parameters 中消费一个值。 int paramIndex = 0; @@ -5754,7 +5789,7 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame // 更新目标井上的拟合参数。目前井级可拟合参数主要是: // - skin:写入第一个 perforation; // - wellboreC:写入井筒储集系数; - // - Dfc:只写入垂直压裂井或多段压裂水平井的裂缝导流能力。 + // - Dfc/裂缝半长:只写入垂直压裂井或多段压裂水平井。 // 如果目标井不存在或没有射孔数据,这里只记录 debug,不抛异常。 nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); @@ -5811,6 +5846,24 @@ void nmCalculationAutoFitPSO::updateWellParameters(const QVector& parame } } break; + + case 9: { // 裂缝半长 + // 直接修改井对象中的属性,复用已有信号重算裂缝端点。 + if(pWell->getWellType() == NM_WELL_MODEL::Vertical_Fractured_Well) { + nmDataVerticalFracturedWell* fracturedWell = + dynamic_cast(pWell); + if(fracturedWell) { + fracturedWell->getFractureHalfLength().setValue(value); + } + } else if(pWell->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { + nmDataHorizontalFracturedWell* fracturedWell = + dynamic_cast(pWell); + if(fracturedWell) { + fracturedWell->getFractureHalfLength().setValue(value); + } + } + } + break; } paramIndex++; diff --git a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp index a7a3462..af01ce0 100644 --- a/Src/nmNum/nmData/nmDataAutomaticFitting.cpp +++ b/Src/nmNum/nmData/nmDataAutomaticFitting.cpp @@ -12,6 +12,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_cfSelected = false; // 默认不选中 m_swiSelected = false; // 默认不选中 m_fractureConductivitySelected = false; // 仅压裂井可用,默认不选中 + m_fractureHalfLengthSelected = false; // 仅压裂井可用,默认不选中 // 拟合上下界不再使用固定默认值,由自动拟合窗口按数据对象初值和物理边界生成。 m_permeabilityMax = nmDataAttribute("Permeability Max", QVariant(), "Darcy"); @@ -23,6 +24,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_cfMax = nmDataAttribute("Cf Max", QVariant(), ""); m_swiMax = nmDataAttribute("Swi Max", QVariant(), ""); m_fractureConductivityMax = nmDataAttribute("Fracture Conductivity Max", QVariant(), "md.m"); + m_fractureHalfLengthMax = nmDataAttribute("Fracture Half Length Max", QVariant(), "m"); m_permeabilityMin = nmDataAttribute("Permeability Min", QVariant(), "Darcy"); m_skinMin = nmDataAttribute("Skin Min", QVariant(), ""); @@ -33,6 +35,7 @@ nmDataAutomaticFitting::nmDataAutomaticFitting() m_cfMin = nmDataAttribute("Cf Min", QVariant(), ""); m_swiMin = nmDataAttribute("Swi Min", QVariant(), ""); m_fractureConductivityMin = nmDataAttribute("Fracture Conductivity Min", QVariant(), "md.m"); + m_fractureHalfLengthMin = nmDataAttribute("Fracture Half Length Min", QVariant(), "m"); // 初始化迭代参数 m_iterationCount = nmDataAttribute("Iteration Count", 20, ""); @@ -63,6 +66,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_cfSelected = other.m_cfSelected; m_swiSelected = other.m_swiSelected; m_fractureConductivitySelected = other.m_fractureConductivitySelected; + m_fractureHalfLengthSelected = other.m_fractureHalfLengthSelected; // 复制参数最大值 m_permeabilityMax = other.m_permeabilityMax; @@ -74,6 +78,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_cfMax = other.m_cfMax; m_swiMax = other.m_swiMax; m_fractureConductivityMax = other.m_fractureConductivityMax; + m_fractureHalfLengthMax = other.m_fractureHalfLengthMax; // 复制参数最小值 m_permeabilityMin = other.m_permeabilityMin; @@ -85,6 +90,7 @@ nmDataAutomaticFitting& nmDataAutomaticFitting::operator=(const nmDataAutomaticF m_cfMin = other.m_cfMin; m_swiMin = other.m_swiMin; m_fractureConductivityMin = other.m_fractureConductivityMin; + m_fractureHalfLengthMin = other.m_fractureHalfLengthMin; // 复制迭代参数 m_iterationCount = other.m_iterationCount; @@ -109,6 +115,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CfSelected", m_cfSelected, allocator); fittingObject.AddMember("SwiSelected", m_swiSelected, allocator); fittingObject.AddMember("FractureConductivitySelected", m_fractureConductivitySelected, allocator); + fittingObject.AddMember("FractureHalfLengthSelected", m_fractureHalfLengthSelected, allocator); // 序列化参数最大值 fittingObject.AddMember("PermeabilityMax", m_permeabilityMax.ToJsonValue(allocator), allocator); @@ -120,6 +127,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CfMax", m_cfMax.ToJsonValue(allocator), allocator); fittingObject.AddMember("SwiMax", m_swiMax.ToJsonValue(allocator), allocator); fittingObject.AddMember("FractureConductivityMax", m_fractureConductivityMax.ToJsonValue(allocator), allocator); + fittingObject.AddMember("FractureHalfLengthMax", m_fractureHalfLengthMax.ToJsonValue(allocator), allocator); // 序列化参数最小值 fittingObject.AddMember("PermeabilityMin", m_permeabilityMin.ToJsonValue(allocator), allocator); @@ -131,6 +139,7 @@ rapidjson::Value nmDataAutomaticFitting::ToJsonValue(rapidjson::Document::Alloca fittingObject.AddMember("CfMin", m_cfMin.ToJsonValue(allocator), allocator); fittingObject.AddMember("SwiMin", m_swiMin.ToJsonValue(allocator), allocator); fittingObject.AddMember("FractureConductivityMin", m_fractureConductivityMin.ToJsonValue(allocator), allocator); + fittingObject.AddMember("FractureHalfLengthMin", m_fractureHalfLengthMin.ToJsonValue(allocator), allocator); // 序列化迭代参数 fittingObject.AddMember("IterationCount", m_iterationCount.ToJsonValue(allocator), allocator); @@ -171,6 +180,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("FractureConductivitySelected") && jsonValue["FractureConductivitySelected"].IsBool()) { m_fractureConductivitySelected = jsonValue["FractureConductivitySelected"].GetBool(); } + if (jsonValue.HasMember("FractureHalfLengthSelected") && jsonValue["FractureHalfLengthSelected"].IsBool()) { + m_fractureHalfLengthSelected = jsonValue["FractureHalfLengthSelected"].GetBool(); + } // 反序列化参数最大值 if (jsonValue.HasMember("PermeabilityMax") && jsonValue["PermeabilityMax"].IsObject()) { @@ -200,6 +212,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("FractureConductivityMax") && jsonValue["FractureConductivityMax"].IsObject()) { m_fractureConductivityMax.FromJsonValue(jsonValue["FractureConductivityMax"]); } + if (jsonValue.HasMember("FractureHalfLengthMax") && jsonValue["FractureHalfLengthMax"].IsObject()) { + m_fractureHalfLengthMax.FromJsonValue(jsonValue["FractureHalfLengthMax"]); + } // 反序列化参数最小值 if (jsonValue.HasMember("PermeabilityMin") && jsonValue["PermeabilityMin"].IsObject()) { @@ -229,6 +244,9 @@ void nmDataAutomaticFitting::FromJsonValue(const rapidjson::Value& jsonValue) if (jsonValue.HasMember("FractureConductivityMin") && jsonValue["FractureConductivityMin"].IsObject()) { m_fractureConductivityMin.FromJsonValue(jsonValue["FractureConductivityMin"]); } + if (jsonValue.HasMember("FractureHalfLengthMin") && jsonValue["FractureHalfLengthMin"].IsObject()) { + m_fractureHalfLengthMin.FromJsonValue(jsonValue["FractureHalfLengthMin"]); + } // 反序列化迭代参数 if (jsonValue.HasMember("IterationCount") && jsonValue["IterationCount"].IsObject()) { @@ -274,6 +292,9 @@ void nmDataAutomaticFitting::setSwiSelected(bool selected) { m_swiSelected = sel bool nmDataAutomaticFitting::getFractureConductivitySelected() const { return m_fractureConductivitySelected; } void nmDataAutomaticFitting::setFractureConductivitySelected(bool selected) { m_fractureConductivitySelected = selected; } +bool nmDataAutomaticFitting::getFractureHalfLengthSelected() const { return m_fractureHalfLengthSelected; } +void nmDataAutomaticFitting::setFractureHalfLengthSelected(bool selected) { m_fractureHalfLengthSelected = selected; } + // Getter and Setter implementations for Max values nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMax() { return m_permeabilityMax; } @@ -303,6 +324,9 @@ void nmDataAutomaticFitting::setSwiMax(const nmDataAttribute& swiMax) { m_swiMax nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMax() { return m_fractureConductivityMax; } void nmDataAutomaticFitting::setFractureConductivityMax(const nmDataAttribute& fractureConductivityMax) { m_fractureConductivityMax = fractureConductivityMax; } +nmDataAttribute& nmDataAutomaticFitting::getFractureHalfLengthMax() { return m_fractureHalfLengthMax; } +void nmDataAutomaticFitting::setFractureHalfLengthMax(const nmDataAttribute& fractureHalfLengthMax) { m_fractureHalfLengthMax = fractureHalfLengthMax; } + // Getter and Setter implementations for Min values nmDataAttribute& nmDataAutomaticFitting::getPermeabilityMin() { return m_permeabilityMin; } void nmDataAutomaticFitting::setPermeabilityMin(const nmDataAttribute& permeabilityMin) { m_permeabilityMin = permeabilityMin; } @@ -331,6 +355,9 @@ void nmDataAutomaticFitting::setSwiMin(const nmDataAttribute& swiMin) { m_swiMin nmDataAttribute& nmDataAutomaticFitting::getFractureConductivityMin() { return m_fractureConductivityMin; } void nmDataAutomaticFitting::setFractureConductivityMin(const nmDataAttribute& fractureConductivityMin) { m_fractureConductivityMin = fractureConductivityMin; } +nmDataAttribute& nmDataAutomaticFitting::getFractureHalfLengthMin() { return m_fractureHalfLengthMin; } +void nmDataAutomaticFitting::setFractureHalfLengthMin(const nmDataAttribute& fractureHalfLengthMin) { m_fractureHalfLengthMin = fractureHalfLengthMin; } + // Getter and Setter implementations for iteration parameters nmDataAttribute& nmDataAutomaticFitting::getIterationCount() { return m_iterationCount; } void nmDataAutomaticFitting::setIterationCount(const nmDataAttribute& iterationCount) { m_iterationCount = iterationCount; } diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 34415d0..6bd7312 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -43,13 +43,10 @@ bool nmAutoFitUiNearlyEqual(double left, double right) return std::fabs(left - right) <= DBL_EPSILON * 8.0 * scale; } -// 从系统参数表读取物理边界,读取失败时保留调用方提供的兜底边界。 +// 从系统参数表读取物理边界,所有自动拟合参数统一以参数表配置为准。 bool nmAutoFitReadPhysicalRange(const char* parameterName, - double fallbackMin, double fallbackMax, double& minValue, double& maxValue) + double& minValue, double& maxValue) { - minValue = fallbackMin; - maxValue = fallbackMax; - iSysParaHelper* paraHelper = _paraHelper; if(!paraHelper) { return false; @@ -245,6 +242,7 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL } } setParameterRowVisible(table, 8, showFractureConductivity); // Dfc + setParameterRowVisible(table, 9, showFractureConductivity); // 裂缝半长 renumberVisibleParameterRows(table); } @@ -255,29 +253,15 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex, { static const char* parameterNames[] = { "Result_K", "Result_W_Skin", "Result_W_C", "Result_phi", - "Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc" - }; - // KAPPA 的边界使用 md、ft、bbl/psi;自动拟合界面使用 Darcy、m、m^3/MPa, - // 这里统一换算到界面和 PSO 实际使用的单位:K 除以 1000,h 由 ft 换成 m, - // 井筒储集系数的 4.33667154546306e34 bbl/psi 对应约 1e36 m^3/MPa。 - // Ct/Cf/Swi/Dfc 沿用模型参数表边界。 - static const double physicalMin[] = { - 1.01325027383089e-18, -5.0, 0.0, 1.0e-4, 1.0e-5, 1.0e-30, 1.0e-30, 0.0, 0.0 - }; - static const double physicalMax[] = { - 1.01325027383089e42, 5000.0, 1.0e36, 0.9999, 1.0e9, 10.0, 10.0, 1.0, 1.0e30 + "Result_h", "Result_Cti", "Result_Cf", "Result_Swi", "Result_W_Dfc", + "W_FractureHalfLength" }; - - if(parameterIndex < 0 || parameterIndex >= 9) { + if(parameterIndex < 0 || parameterIndex >= 10) { return false; } - minValue = physicalMin[parameterIndex]; - maxValue = physicalMax[parameterIndex]; - bool rangeRead = true; - if(parameterIndex >= 5) { - rangeRead = nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], - physicalMin[parameterIndex], physicalMax[parameterIndex], minValue, maxValue); + if(!nmAutoFitReadPhysicalRange(parameterNames[parameterIndex], minValue, maxValue)) { + return false; } if(parameterIndex == 7) { @@ -294,7 +278,7 @@ bool nmWxAutomaticFitting::getPhysicalParameterRange(int parameterIndex, } } - return rangeRead; + return true; } // 将一组上下界同步到表格和自动拟合数据,保证 PSO 读取到同一份配置。 @@ -357,6 +341,10 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, automaticFittingData.getFractureConductivityMin().setValue(minValue); automaticFittingData.getFractureConductivityMax().setValue(maxValue); break; + case 9: + automaticFittingData.getFractureHalfLengthMin().setValue(minValue); + automaticFittingData.getFractureHalfLengthMax().setValue(maxValue); + break; default: break; } @@ -369,14 +357,16 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex, void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, double centerValue) { - if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 9 + if(!m_parameterTable || parameterIndex < 0 || parameterIndex >= 10 || !nmAutoFitUiIsFinite(centerValue)) { return; } double physicalMin = 0.0; double physicalMax = 0.0; - getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); + if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) { + return; + } double reference = centerValue; const bool positiveParameter = parameterIndex != 1; @@ -437,7 +427,7 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges() return; } - for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) { + for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) { QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); if(initialItem) { bool initialOk = false; @@ -448,8 +438,8 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges() // 数据对象没有提供该初值时使用完整物理区间,不回退到旧的默认范围。 double physicalMin = 0.0; double physicalMax = 0.0; - getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); - if(physicalMax >= physicalMin) { + if(getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax) + && physicalMax >= physicalMin) { setParameterRange(parameterIndex, physicalMin, physicalMax); } } @@ -464,7 +454,7 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges() return; } - for(int parameterIndex = 0; parameterIndex < 9; ++parameterIndex) { + for(int parameterIndex = 0; parameterIndex < 10; ++parameterIndex) { QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2); QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4); QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3); @@ -474,7 +464,9 @@ void nmWxAutomaticFitting::normalizeSavedParameterRanges() double physicalMin = 0.0; double physicalMax = 0.0; - getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax); + if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) { + continue; + } bool savedMinOk = false; bool savedMaxOk = false; const double savedMin = minItem->text().toDouble(&savedMinOk); @@ -513,18 +505,19 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par errorMessage = tr("The parameter table is unavailable."); return false; } - if(parameterIndex < -1 || parameterIndex >= 9) { + if(parameterIndex < -1 || parameterIndex >= 10) { errorMessage = tr("The parameter row is invalid."); return false; } static const char* parameterNames[] = { "Permeability", "Skin", "Wellbore storage", "Porosity", - "Thickness", "Ct", "Cf", "Swi", "Fracture conductivity" + "Thickness", "Ct", "Cf", "Swi", "Fracture conductivity", + "Fracture half length" }; const int firstParameterIndex = parameterIndex < 0 ? 0 : parameterIndex; - const int lastParameterIndex = parameterIndex < 0 ? 9 : parameterIndex + 1; + const int lastParameterIndex = parameterIndex < 0 ? 10 : parameterIndex + 1; for(int currentParameterIndex = firstParameterIndex; currentParameterIndex < lastParameterIndex; ++currentParameterIndex) { // 隐藏参数不参与当前模型拟合,不用它们的历史值阻塞当前设置。 @@ -558,8 +551,8 @@ bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int par double physicalMin = 0.0; double physicalMax = 0.0; - getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax); - if(!nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax) + if(!getPhysicalParameterRange(currentParameterIndex, physicalMin, physicalMax) + || !nmAutoFitUiIsFinite(physicalMin) || !nmAutoFitUiIsFinite(physicalMax) || physicalMax < physicalMin) { errorMessage = tr("The physical range of %1 is invalid.") .arg(tr(parameterNames[currentParameterIndex])); @@ -717,7 +710,7 @@ void nmWxAutomaticFitting::setupUI() void nmWxAutomaticFitting::setupParameterTable() { // 创建表格 - m_parameterTable = new QTableWidget(9, 6, this); + m_parameterTable = new QTableWidget(10, 6, this); // 设置表头 QStringList headers; @@ -837,6 +830,16 @@ void nmWxAutomaticFitting::setupParameterTable() m_parameterTable->setItem(8, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMax().getValue().toDouble()))); m_parameterTable->setItem(8, 5, new QTableWidgetItem(tr("md.m"))); + // 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。 + m_parameterTable->setItem(9, 0, new QTableWidgetItem("10")); + m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length")); + m_fractureHalfLengthCheckBox->setChecked(automaticFittingData.getFractureHalfLengthSelected()); + m_parameterTable->setCellWidget(9, 1, m_fractureHalfLengthCheckBox); + m_parameterTable->setItem(9, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble()))); + m_parameterTable->setItem(9, 3, new QTableWidgetItem()); + m_parameterTable->setItem(9, 4, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMax().getValue().toDouble()))); + m_parameterTable->setItem(9, 5, new QTableWidgetItem(tr("m"))); + // 设置表格行为 for(int i = 0; i < m_parameterTable->rowCount(); ++i) { for(int j = 0; j < 6; ++j) { @@ -1037,6 +1040,7 @@ void nmWxAutomaticFitting::onReverseSelection() if(!m_parameterTable->isRowHidden(6)) m_cfCheckBox->setChecked(!m_cfCheckBox->isChecked()); if(!m_parameterTable->isRowHidden(7)) m_swiCheckBox->setChecked(!m_swiCheckBox->isChecked()); if(!m_parameterTable->isRowHidden(8)) m_dfcCheckBox->setChecked(!m_dfcCheckBox->isChecked()); + if(!m_parameterTable->isRowHidden(9)) m_fractureHalfLengthCheckBox->setChecked(!m_fractureHalfLengthCheckBox->isChecked()); } void nmWxAutomaticFitting::onParameterTableItemChanged(QTableWidgetItem* item) @@ -1155,7 +1159,8 @@ void nmWxAutomaticFitting::onAccept() m_cCheckBox->isChecked() || m_phiCheckBox->isChecked() || m_hCheckBox->isChecked() || m_ctCheckBox->isChecked() || m_cfCheckBox->isChecked() || - m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked(); + m_swiCheckBox->isChecked() || m_dfcCheckBox->isChecked() || + m_fractureHalfLengthCheckBox->isChecked(); if(!hasSelectedParams) { QMessageBox::warning(this, tr("Warning"), tr("Please select at least one parameter for optimization!")); @@ -1174,6 +1179,7 @@ void nmWxAutomaticFitting::onAccept() if(m_cfCheckBox->isChecked()) selectedParameterNames << tr("Cf"); if(m_swiCheckBox->isChecked()) selectedParameterNames << tr("Swi"); if(m_dfcCheckBox->isChecked()) selectedParameterNames << tr("Fracture conductivity"); + if(m_fractureHalfLengthCheckBox->isChecked()) selectedParameterNames << tr("Fracture half length"); // 启动自动拟合 - 传递双对数历史数据 startAutoFitting(targetLogLogData, selectedParameterNames, selectedWellName); @@ -1194,6 +1200,7 @@ void nmWxAutomaticFitting::onWellSelected(int index) double skinValue = 0.0; double wellboreStorageValue = 0.0; double fractureConductivityValue = 0.0; + double fractureHalfLengthValue = 0.0; // 查找垂直井 for(int i = 0; i < m_verticalWells.size(); ++i) { @@ -1224,6 +1231,7 @@ void nmWxAutomaticFitting::onWellSelected(int index) skinValue = m_verticalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_verticalFracturedWells[i].getWellboreStorage().getValue().toDouble(); fractureConductivityValue = m_verticalFracturedWells[i].getDfc().getValue().toDouble(); + fractureHalfLengthValue = m_verticalFracturedWells[i].getFractureHalfLength().getValue().toDouble(); fracturedWell = true; found = true; break; @@ -1238,6 +1246,7 @@ void nmWxAutomaticFitting::onWellSelected(int index) skinValue = m_horizontalFracturedWells[i].getPerforation(0)->getSkin().getValue().toDouble(); wellboreStorageValue = m_horizontalFracturedWells[i].getWellboreStorage().getValue().toDouble(); fractureConductivityValue = m_horizontalFracturedWells[i].getDfc().getValue().toDouble(); + fractureHalfLengthValue = m_horizontalFracturedWells[i].getFractureHalfLength().getValue().toDouble(); fracturedWell = true; found = true; break; @@ -1268,12 +1277,16 @@ void nmWxAutomaticFitting::onWellSelected(int index) if(fracturedWell && m_parameterTable->item(8, 3)) { m_parameterTable->item(8, 3)->setText(QString::number(fractureConductivityValue)); } + if(fracturedWell && m_parameterTable->item(9, 3)) { + m_parameterTable->item(9, 3)->setText(QString::number(fractureHalfLengthValue)); + } if(m_autoParameterRanges) { updateRangeForParameter(1, skinValue); updateRangeForParameter(2, wellboreStorageValue); if(fracturedWell) { updateRangeForParameter(8, fractureConductivityValue); + updateRangeForParameter(9, fractureHalfLengthValue); } } } @@ -1291,6 +1304,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.setCfSelected(m_cfCheckBox->isChecked()); automaticFittingData.setSwiSelected(m_swiCheckBox->isChecked()); automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked()); + automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked()); automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1); // 保存渗透率的最小值和最大值 @@ -1329,6 +1343,10 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.getFractureConductivityMin().setValue(m_parameterTable->item(8, 2)->text().toDouble()); automaticFittingData.getFractureConductivityMax().setValue(m_parameterTable->item(8, 4)->text().toDouble()); + // 保存裂缝半长的最小值和最大值 + automaticFittingData.getFractureHalfLengthMin().setValue(m_parameterTable->item(9, 2)->text().toDouble()); + automaticFittingData.getFractureHalfLengthMax().setValue(m_parameterTable->item(9, 4)->text().toDouble()); + // 保存迭代参数 automaticFittingData.getIterationCount().setValue(m_iterationEdit->text().toInt()); automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); @@ -1353,6 +1371,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() double newSkinValue = m_parameterTable->item(1, 3)->text().toDouble(); double newWellboreStorageValue = m_parameterTable->item(2, 3)->text().toDouble(); double newFractureConductivityValue = m_parameterTable->item(8, 3)->text().toDouble(); + double newFractureHalfLengthValue = m_parameterTable->item(9, 3)->text().toDouble(); // 直接从数据管理器获取目标井 nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); @@ -1382,6 +1401,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() nmDataAttribute dfc = fracturedWell->getDfc(); dfc.setValue(newFractureConductivityValue); fracturedWell->setDfc(dfc); + fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue); } } else if(wellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { nmDataHorizontalFracturedWell* fracturedWell = dynamic_cast(pTargetWell); @@ -1389,6 +1409,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() nmDataAttribute dfc = fracturedWell->getDfc(); dfc.setValue(newFractureConductivityValue); fracturedWell->setDfc(dfc); + fracturedWell->getFractureHalfLength().setValue(newFractureHalfLengthValue); } } @@ -1622,6 +1643,7 @@ void nmWxAutomaticFitting::updateBestParametersToTable() if(m_cfCheckBox->isChecked()) enabledParams.append(6); // 岩石压缩系数 if(m_swiCheckBox->isChecked()) enabledParams.append(7); // 初始含水饱和度 if(m_dfcCheckBox->isChecked()) enabledParams.append(8); // 裂缝导流能力 + if(m_fractureHalfLengthCheckBox->isChecked()) enabledParams.append(9); // 裂缝半长 // 更新参数值和范围 for (int i = 0; i < bestSolution.size() && i < enabledParams.size(); ++i) { From e1a8610df417d0c580451083f676ae336c0bfa96 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 15:32:47 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E6=9B=B2=E7=BA=BF=E9=87=87=E6=A0=B7?= =?UTF-8?q?=E7=82=B9=E6=95=B0=E6=94=B9=E4=B8=BA80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 0f37602..79453f9 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -3936,9 +3936,9 @@ struct TrustRegionEvaluation static bool trustRegionResidualsValid( const AutoFitObjectiveBreakdown& breakdown) { - // 损失函数固定使用 50 个压力点和 50 个导数点。严格校验长度,避免 + // 损失函数固定使用 80 个压力点和 80 个导数点。严格校验长度,避免 // Jacobian 沿用旧维度后访问另一候选的短残差向量。 - if(!breakdown.valid || breakdown.residualVector.size() != 100) { + if(!breakdown.valid || breakdown.residualVector.size() != 160) { return false; } @@ -4248,7 +4248,7 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() bool modelRebuiltAtMinimumRadius = false; StopReasonPSO stopReason = PSO_MAX_ITERATIONS; - // jacobian 的行对应固定 100 维残差,列对应用户勾选的参数。 + // jacobian 的行对应固定 160 维残差,列对应用户勾选的参数。 // 三个 gradient 单独描述诊断分量对参数的局部变化,只用于本轮选参。 QVector > jacobian; QVector verticalGradient(dimensions, 0.0); @@ -6516,7 +6516,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( const double invalidLoss = 1.0e10; const double valueFloor = 1.0e-12; const double minimumCoverage = 0.95; - const int numPoints = 50; + const int numPoints = 80; m_lastObjectiveBreakdown = AutoFitObjectiveBreakdown(); if(!validateLogLogData(target) || !validateLogLogData(result)) { @@ -6754,7 +6754,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( } // 通过门槛后最多只缺少首尾少量目标点。按模拟曲线端点趋势补齐后, - // 每个候选仍在固定 50 点上计算均方根误差,不能靠少算难拟合端点获益。 + // 每个候选仍在固定 80 点上计算均方根误差,不能靠少算难拟合端点获益。 for(int i = 0; i < numPoints; ++i) { if(isFiniteNumber(pressureResidual[i]) && isFiniteNumber(derivativeResidual[i])) { @@ -7335,7 +7335,7 @@ double nmCalculationAutoFitPSO::calculateLogLogCurveError( 0.5 * breakdown.pressureLoss + 0.5 * breakdown.derivativeLoss; } else { - // 非代理总目标等于固定 100 维普通残差的二范数;压力和 + // 非代理总目标等于固定 160 维普通残差的二范数;压力和 // 导数各占一半能量。上下、左右和形状分量不参与候选排序与接受。 breakdown.total = qSqrt( 0.5 * breakdown.pressureLoss * breakdown.pressureLoss + From 83a29152c9fe86615a0e95c4b104b1e015fa6331 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 15:40:16 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=B1=80=E9=83=A8?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E5=88=A4=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 93972 -> 94621 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 12 +++ .../nmCalculation/nmCalculationAutoFitPSO.cpp | 90 ++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index c9e8ed0c65c5f7422395fd6f64d2faf6de240c85..9df8a2617c56f2db44d2b2b5b16f5de42f89553d 100644 GIT binary patch delta 10049 zcmZ{pcU({J|No!Y>%7iu4>K!ySrxJ>StXT>Bt%9OT110N$s4Cq_K32{D3MWQzu$Ht z-bOarvfe)S+n(RY>vg?9l@F*t;1 z>@?1#9L{YO7BX#Za43G?%$Zcixm{x+)B1yfnCCbcO;lQq^B(5JHS2B-#uAM#;GDFS zQ!4f$lK)1``3pD`Yd8Wf1mAIvQgKT8oQuOjygbT~TiJgghWODAC#Ch^0=k)~6ius&(w{!l5518M3 z4e8DNm}()@_2F#riPIKZ!tcykW~M}}%Ljg=+epqH)j54ugMEp{P2rq-o-+p$iorzt zAh|e-XnhnILCn$o95gfhEC>w@{{=$wpf(@`3&OMF@5CCrfoDKyQX$nM+TIBRsJo~O zkY@PWWc5Izuy)%!z|z^g<9Y#`wva2B3} zCd@o(4gQ2C;7ewn^dky@nf5{2fbT?WzgfttxWKHKYj_xl--g2{hTbF!Z43LG2b&^*B^&H}t|zCPqmGz@0^0m(*`z;w`tne&uH zW742$XkrZfW&1@C_x&bFd@$OHGYMue|4=yFLZ+REklP1a%_kZU4HZE%@lahDJlV`0 zcrPCQ5_W-eG!}x5H`boq2V6ijF^X;(O z2b{mv;LP?U`SBQHK6Oc6j*z;WLh{R_VG2xOW|xH|zY$OLT_jPCMLauBVmn00%|a5p z!Y;$K;1ST3Gh_*8%1lma&20=$;elupd$uFWN#p#!*FvV~Poi0M1FmqcUj`>PPka%? z#KUDE-XHEn;_?$veXNCyYICaUa~hg(R$0T@2J_%~iFvw{xE{_ES;0B+9Oou4&V%Kg zr*2uuR6{u3@%&cU`ic*UJFxJnn9$5=`z&ORn>an{khlxZI`}Z>mIWl9kQ0@RF%$ML zJ7T%P)ZUyn8%VrxikNRIiMJh)32ix}B+jiJEo2@~Nnzzp^tA^mW~IVsnvr6~B4XO} zq{ubosuQsd5jTXar4^8rH6m*>n;oX6W0nzX?LZD477+cufLdZA@+;ftxrxTviU`Aw9knyts>`kNMQ4(lXJIG#5|+Og{~0U%Bj<4SNMjV zg{<}y^2`e+Iy!?S@1AMI+C@`$-(y6F>r>C9jzljHS;(5ikdMm}q6>E|WV)%G4Xwy0 zq!FTJC+FnHoY@BQnN>ivqz(D52qC(o;CvcNzE2Udcbuu;kKxD_ZOPvrPQ6AZk^k;y zL?I(NSM4SL{kR^X;T->zbFzwab#oe++J&fA5)IBANQ|CyHj`*b1|-in(U7eBsOa4| z^DooTBapPoFdF)C3yRJW3LFiAUQDFn{U#IrloT|4`W|BH6i%Bf6g1~Ju_|XMs07KS z*KN)>Y71H0VhZ}U876V$jQGfz6vMeKkMmZcg-mmU#+dWUlpKnzwi~r!3q^)6BAV2M zQz|@W88DALiVGV+6x@R1_Cf=54p4joY`60m#ZO!Z=d-4y)eK(Sm8O^%Z0y46sH7Ry zVY)UAX+b11oUK3$qE`^rOr>Sn2(#|fX=AZKl-P%J^GVw4X_AGknn>r=jzqF{3axJ5t&2u2cq^Hm|I#aB&M^h>m=Owwc`vg;f&h> zO88+#B=h$~6nHmhBW&&xJ@~>#ONY=C2rT0eD%iw9oO#}C=8-~T$`Ur$bq{Rn!sg|+ zA=aigTOJrr%-Wx=-QJQ&f1TxdFhtW#wlNn`5Eji@bewI0?=e?bwmlT#?K+>8N_9}( z>SnTQe;+1#oXQ@oM8%UXv4`?tV)nkQGVwARhQX}zI6fHLh<$T|ZwxHq%)KsCR|zE6 zZMjTmUYN&VnZCn&qJI_3ta>3qwfP}yk(CT@zbXUw4N;CeM5MAplsp?2$r5COL;Pl$e1ph{`wvAz$#hBwg5!IL5UBN9XO|) zlg(&znOL2cvh0125d{gd)vuh0)f*^Vd+RvS{9@VKN^n5~nc1tE>a%RyD5To@`(=d> za)`RUkQEhNM>LF;6?>Ktt94xVhcX;pP;bt>3fUh4FK|7JbE8y8_Qy!5=z=6WI=G1F z?^xNfSq7}wC@YN*BXU?ED^Cl<2Q;#4rtPq8HQ9|CXd{9y_NYWnl;+F1 zp%&*KH8@YTkUg&VftZG5kAG|=*6E_`xhvwou2PUKK_@I#x(kZ+{z$>s1jE-Z$mJ^p zOEg4n;as+y^O?I~iGt`#&XtuywFUU_;Fp|RjtEwsRnaY7;+!;+b4P(-)vYQzqFI8K zc~7y^Mz9KkPi6KJtVUKv|F<(xunG-D;#n?OnRh*zO2O)(A5q49p_W5+sQS6k*a1c6 zV}a15c`2SN=Zx9JnS6mWd!k^^qKULYLh}S@N|i1&e?A47@)c+1?}CFComB77LL2n} zVv0~pJ4A_Ed_3EoI@CS;&(sk{Wg>cFgM?5s$M+P%zrt6%ZwRqYhcVx3A#QgsB!a5K zGARQqua_gNUp1asD+ggi-;LO0_z9cu%xjB^RKreaQ z36OaHc)3UN2}Hv&xmPXZiTQ2iUMr9n=KU=n=++NmnJgdp5~kR%kO!UCAk)s4j|jzr z7Y>)?p;mZs;XHYQV+E>OnLKqk7IglxJi`sCvtfPt(m#TTzS+x{_kskw{^l$_z}bZvV{dS%qJFzU1tFj;`bMKjLKPx5UJ@R=xU&Ks*NWL<`F_F5`0+=p^J zP9@*d6Gci5YCq(AuNa7GKa(FFkA%{7jl9HQLK9jjFDuyvXME53 zDob8=3bwuJB0nd=8807`pWiwMJ}^&yAu$`f-(&Lf04!u>q5R&Ha&)~p@+ZMf;X?}f zzka+Y*6W7+#b5A&{+~Hly2w8U97g;P`feT&6?n;i4$440M~K4I5^SA=IA6CH<=DMY znWUp9bGvYhtFR{ku_9!l2IFlndr>Z#D))B3I zyJ25g1WFjJ4i>G^;#1A7Y}r*kRZdgmEyZTW_)F-0|4b zS&Ka-^I%<}*gIziQ423|WJoGeyUya6A8?L}A~9+oe4=Z` zDmSFqCgRK;&{X7h&b(?CGG&@L+YMcCKnZ8QpE$4k31TLDPWQ3m{Loh@LgPefp_d0y z#};B{yD!jy3ulf|T-pzk?q4J>-x@>oeu}uR2W;8rBj?0*V(vh1IHi_z!)h_VYza*H zC+B9RxRrf}#5cvQPj{d@F?u1X3ouW3Z4DvH3o z_e2x=bADT(2;2_M#XsU)y#maG52PzXf}x?iUW$+t=TQq{10DeL6$jRLBf9ohaoD_(`FdV) z)Rqx-s!$wLcOcd=NKv{C`K8|}&aAqMD=*>^|Fxbd?(7+fZSxexqYhAYhh~Z=L*XR) zDT=3$VAK8yoJ&&_FI%AXYGP2l*%=NK*ec$MerJMksyJCy?)W&Ik=>+;h$q1xo*PXtL8#<&bn}@TEyP`obr4 zKKqoBcFmCBmMbG$jDRl~m1CujkW}ZX9M`Ttk#(kW+@)M3h83LaTPveaUqL>|QO0bj zi&XnSIlm#L%KJmE68-f>S$QXnSi`5vr?^iOtvGXy%FlI! ziDs23KX-VCqE$mBpI(S4YNV>E@x}*!s%oB}js-ZVtoN)Wx<6XgK!y4GKH^-oUuB{U z^ZB2N4;Fn?*`**pFFjB>U&Ou#zb&VsL3fUzF`?KnWL(A0~86NzpD4$ok%Q~RQ(5I<>gOR z0W;Pk!@W=qyJbYm{mz*;TNSLiPRzZjYDBeo?4tcRqg!z<4^qXu{UVw>LNy5)mQri0 zlE2s@fqhm@U+syrrMnhNbn=5`!0NwNW$aByT-Q;}UGRhGT##zM&)?WQx~Y~hcz>t9 zstt!W5joqaHjPPw&9gaEZ>zQvob%l>3t7X-svSMrpqbsL+8$~VKJ}wnziamX#i2@m8$!LO0g5Fq53on2Mk@Ss6O{vg0r5ksxKSi ze82jszLdgtzhNI$3%k!%;|zI`!H{94K~GulDPO6sl3@ErNv4J=OUgpn)nM)%kZ0zyx*F zn=d;Nd0tX)U73cu-%nk*^9)q%YaweiSABZs6~uW@_34es8wCf{=YO@w#wI|0F>5$# zMF;hby!!Yjho}1Hj2J|PNiE%u?E%~UseT%X)Y{ET{c=+cY@aLDFN>!VY2K>e*dgH@ zexrWhaW)ooNd2ke4Qj+x^^c=>(dR!?|Lh7b2vGl8ehdegtu?Y&#|RlrqZob&<@udP zySxd)SkhGS^@1jbb2=wzs(DyrhqPGYgQaIQ_2*(m)8jPFHWc8$)Im44U<0f#fXu`QCQxmY8p)V-Wj7rEs z7;eyngbhaOmqIn6eGnc)25=S$ny|Q8m=K)u7|o`wCanA=+UEzH^E@=+d!hOsM>$`a zG%?Rm6aL88Btvt|ZL?-tfZF_T7tZ|I8tDQw;;PZ4{f=nre~@$8eNEbn!>A<__VU4! zl@>CAY0|etWt%%`7R)wabNWuR&>nH#beLx092vZJk|uLV5;1*+W@#7r%H_$L0$7B# z3Dy**=3?ixRC8c8`ibU6nxiM1ia;^WOUm^QIYmX3cQT#|CJz zJOeeKa?RJPHJ_6!aVnps)NgH|`nX+m*r|WU; z*oAoBakw_N<^%{@O*^qOqUGQa&Qoi&Y0_j9>Uohi4HGdi zjE>}7o}-BK^MDK=V}XI zc=}l9?Hq%Ne&~8EzJk;2LptAi6R`<3aNgf+A*WDT%Ce-(^1X>OAUoZ%p7`HTJ+*G-_k(cC z^}6+Wkx1F;x?B@dug@{g==Ge-Z|m|6(4=%|x31t7Y}D$LZf6P>Fy^Ihx8HN9eunON zm>hL`t*%5dkf_;3U8#qNEz>K`=3%S7xl z<&@}aXE%aIU+e2tLWA{(=o<&NNBsY?*0+4q5T{m?^u9t6icXT=e`;Ht`vvPmODfPd zJLxC3gY#WU(WhQNhh-G$XZJ(9Ub4bMW__L0p+vuwp>40eLBFg|I{xP)>USM!j6~H; z|GN&S=`Eh=j}CK12V~BN*N368+n;knD+v{#^+&m&VYsXReC#Nk>a_mnEF_{!a)YYIV-&gmoa-JNv=6h8aKbo8z2y9D31>=e&XtJ< z!@M&H&uj}>qXvem(s6uP-p63IN{toy8cZ#ba1;@ShWRUqY7`rquYxAp>kJMrmf-*X zbvaMmH@G&$0%9+6E;AbXAK3yY^)UF0({Yx($`I5ReZND1Az0^xFdQ6U zO-%R6aDEzW`D?%7`~i4(I|sx0`&D4uWW$AbXus|A43}+w63xD5xNaYSlaK?3N8TRT z(v=$iRWTb)ZL;CL9el)kiQz+5OEe+}C8JLEj;O&>qu$05US7>;{N{#3GO=W-ik^M^>izNa`NMsUVEa<2GfY`5e(4mP$J+s7Hutlr{GOEGq= zEI?k#Hg>_hOpp#5y_Ypb++Q+wKlMA>;_gNtOw8mTjege=HX~d(Cp&R&9Aq3TTw{ed@UZh-O5y$`Vc@5V~qRib76jZf#}+J6=2x@N|w3r`b_S8DtfF$y`^ z5eD@w*>${0Y+WgMv^uRSMkZ=XZOPSdq9ZXFfo0?)HOd+G`Nfy>JNm-wXex8y7m*e$lz$uj40%_YKY;+i>t)vnc-~#z?Az3YpJ{CV4*@xa!~g&Q delta 9504 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 nmCalculationAutoFitPSO + + Effective improvement threshold: max(%1, %2% of baseline error); %3 consecutive ineffective steps trigger convergence confirmation + 有效改善阈值:取 %1 与基准误差的 %2% 中较大值;连续 %3 次无有效改善后进行收敛确认 + + + No effective improvement for %1 consecutive steps; rebuilding sensitivity model for confirmation + 连续 %1 次无有效改善,正在重建灵敏度模型进行确认 + + + Sensitivity rebuild produced no effective improvement; local convergence detected + 灵敏度重建后仍无有效改善,判定为局部收敛 + === User Stop Request Received === === 用户停止请求已接收 === diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 79453f9..94497c7 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -4235,6 +4235,11 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() const double maximumTrustRadius = 0.30; const double columnCorrelationLimit = 0.995; const double diagnosisThreshold = 1.0e-5; + // 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。 + // 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。 + const double effectiveRelativeImprovement = 2.0e-3; + const double effectiveAbsoluteImprovement = 1.0e-5; + const int maximumIneffectiveSteps = 3; // damping 是 LM 阻尼;拒绝或预测失准时增大,真实下降与预测一致时减小。 // 两组累计量控制 Jacobian 重建,避免长期使用已偏离当前工作点的局部模型。 @@ -4243,9 +4248,11 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() int consecutiveRejectedSteps = 0; int consecutiveSolverFailures = 0; int acceptedSinceRebuild = 0; + int consecutiveIneffectiveSteps = 0; double movementSinceRebuild = 0.0; bool rebuildRequested = true; bool modelRebuiltAtMinimumRadius = false; + bool stagnationConfirmationRequested = false; StopReasonPSO stopReason = PSO_MAX_ITERATIONS; // jacobian 的行对应固定 160 维残差,列对应用户勾选的参数。 @@ -4367,6 +4374,54 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() .arg(current.fitness, 0, 'e', 4) .arg(maximumEvaluations)); + // 有效改善始终相对“上一次有效改善后的误差”累计判断,避免一连串微小 + // 下降每次都清零计数;累计达到门槛后才开始新的有效改善基准。 + double effectiveImprovementBaseline = current.fitness; + auto registerEffectiveImprovement = [&](double fitness) -> bool { + const double requiredImprovement = qMax( + effectiveAbsoluteImprovement, + qAbs(effectiveImprovementBaseline) * + effectiveRelativeImprovement); + const double improvement = effectiveImprovementBaseline - fitness; + if(improvement < requiredImprovement) { + return false; + } + + effectiveImprovementBaseline = fitness; + consecutiveIneffectiveSteps = 0; + stagnationConfirmationRequested = false; + return true; + }; + + // 连续三次没有有效改善时只请求一次灵敏度重建。重建完成后由主循环 + // 直接检查累计改善,仍达不到门槛就判定局部收敛,不再继续微小试探。 + auto recordIneffectiveStep = [&]() -> bool { + ++consecutiveIneffectiveSteps; + if(consecutiveIneffectiveSteps < maximumIneffectiveSteps) { + return false; + } + + if(stagnationConfirmationRequested) { + return true; + } + + consecutiveIneffectiveSteps = 0; + stagnationConfirmationRequested = true; + rebuildRequested = true; + emit logMessageGenerated( + tr("No effective improvement for %1 consecutive steps; " + "rebuilding sensitivity model for confirmation") + .arg(maximumIneffectiveSteps)); + return false; + }; + + emit logMessageGenerated( + tr("Effective improvement threshold: max(%1, %2% of baseline error); " + "%3 consecutive ineffective steps trigger convergence confirmation") + .arg(effectiveAbsoluteImprovement, 0, 'e', 2) + .arg(effectiveRelativeImprovement * 100.0, 0, 'f', 2) + .arg(maximumIneffectiveSteps)); + if(current.fitness < m_targetError) { return PSO_TARGET_ACHIEVED; } @@ -4603,6 +4658,8 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() break; } if(rebuildRequested) { + const bool confirmingStagnation = + stagnationConfirmationRequested; if(!rebuildSensitivity()) { stopReason = m_shouldStop ? PSO_USER_STOPPED @@ -4617,6 +4674,15 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() stopReason = PSO_MAX_ITERATIONS; break; } + const bool rebuildEffective = + registerEffectiveImprovement(current.fitness); + if(confirmingStagnation && !rebuildEffective) { + emit logMessageGenerated( + tr("Sensitivity rebuild produced no effective improvement; " + "local convergence detected")); + stopReason = PSO_LOCAL_OPTIMUM; + break; + } } // 先确定当前最突出的可靠诊断误差,用其梯度回答“哪些参数最能改善 @@ -4748,6 +4814,10 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); damping = qMin(1.0e8, damping * 4.0); rebuildRequested = true; + if(recordIneffectiveStep()) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } continue; } @@ -4865,6 +4935,10 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() } rebuildRequested = true; } + if(recordIneffectiveStep()) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } continue; } @@ -4905,6 +4979,10 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() stopReason = PSO_CONSECUTIVE_FAILURES; break; } + if(recordIneffectiveStep()) { + stopReason = PSO_LOCAL_OPTIMUM; + break; + } continue; } @@ -4994,6 +5072,14 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() } } + // 候选只要更优就继续作为 current 保存;是否足以解除停滞,则统一 + // 相对上一次有效改善基准判断。拒绝和微小改善都会累计无效次数。 + const bool effectiveImprovement = + registerEffectiveImprovement(current.fitness); + if(!effectiveImprovement && recordIneffectiveStep()) { + stopReason = PSO_LOCAL_OPTIMUM; + } + writeTraceRow(m_currentIteration, -1, "trust_region_candidate", @@ -5018,6 +5104,10 @@ StopReasonPSO nmCalculationAutoFitPSO::runTrustRegionFitting() .arg(accepted ? tr("accepted") : tr("rejected"))); emit progressUpdated(iteration + 1, m_globalBestFitness); + if(stopReason == PSO_LOCAL_OPTIMUM) { + break; + } + if(current.fitness < m_targetError) { stopReason = PSO_TARGET_ACHIEVED; break; From 924f6f4ee8229c27c2bb48b9073ec0d66e2889b3 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 14 Aug 2026 15:50:28 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=80=99=E9=80=89?= =?UTF-8?q?=E8=AF=84=E4=BB=B7=E4=B8=AD=E7=9A=84=E5=8F=82=E6=95=B0=E4=B8=93?= =?UTF-8?q?=E5=B1=9E=E7=A1=AC=E7=BC=96=E7=A0=81=E9=98=88=E5=80=BC=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nmCalculation/nmCalculationAutoFitPSO.cpp | 53 +------------------ 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 94497c7..cb510f6 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -6327,9 +6327,8 @@ int nmCalculationAutoFitPSO::getEnabledParameterCount() const bool nmCalculationAutoFitPSO::validateParameters(const QVector& parameters) const { - // 参数合法性检查分两层: - // 1. 与用户界面设置一致:维度、有限数、上下界; - // 2. 求解器保护:拦截会导致数值崩溃或明显无物理意义的极端值。 + // 参数物理范围已由拟合窗口统一校验;候选评价只检查维度、有限数和 + // 用户设置的上下界,避免另一套硬编码阈值与实际搜索范围冲突。 if(parameters.size() != getEnabledParameterCount()) { return false; } @@ -6352,54 +6351,6 @@ bool nmCalculationAutoFitPSO::validateParameters(const QVector& paramete } } - // 直接拦截会导致求解器数值崩溃的参数值 - for(int i = 0; i < parameters.size() && i < m_enabledParamIndices.size(); ++i) { - int paramIndex = m_enabledParamIndices[i]; - double value = parameters[i]; - - switch(paramIndex) { - case 0: // 渗透率:必须大于零 - if(value <= 1e-8) { - DEBUG_OUT(QString("Rejecting near-zero permeability: %1").arg(value)); - return false; - } - - break; - - case 2: // 井筒储集系数:必须大于零 - if(value <= 1e-10) { - DEBUG_OUT(QString("Rejecting near-zero wellbore storage: %1").arg(value)); - return false; - } - - break; - - case 3: // 孔隙度:必须在合理范围 - if(value <= 1e-6 || value >= 0.99) { - DEBUG_OUT(QString("Rejecting unrealistic porosity: %1").arg(value)); - return false; - } - - break; - - case 5: // 综合压缩系数:必须大于零 - if(value <= 1e-8) { - DEBUG_OUT(QString("Rejecting near-zero total compressibility: %1").arg(value)); - return false; - } - - break; - - case 8: // 裂缝导流能力:0 表示无限导流,不能作为连续拟合搜索点 - if(value <= 1e-10) { - DEBUG_OUT(QString("Rejecting non-positive fracture conductivity: %1").arg(value)); - return false; - } - - break; - } - } - return true; }