|
|
#include "nmCalculationAutoFitPSO.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 "nmCalculationPebiGrid.h"
|
|
|
|
|
|
#include <QApplication>
|
|
|
#include <QDebug>
|
|
|
#include <QTime>
|
|
|
#include <QDir>
|
|
|
#include <QTextStream>
|
|
|
#include <QFileInfo>
|
|
|
#include <QProcess>
|
|
|
#include <QProcessEnvironment>
|
|
|
#include <QPair>
|
|
|
#include <QtCore/qmath.h>
|
|
|
#include <cmath>
|
|
|
#include <algorithm>
|
|
|
#include <limits>
|
|
|
#include <QTimer>
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
#include <windows.h>
|
|
|
#include <float.h>
|
|
|
#define DEBUG_OUT(msg) OutputDebugStringA(QString("[AutoFit] %1\n").arg(msg).toLocal8Bit().data())
|
|
|
#endif
|
|
|
|
|
|
// 常量定义
|
|
|
const double nmCalculationAutoFitPSO::MIN_FITNESS_IMPROVEMENT = 1e-8;
|
|
|
const double nmCalculationAutoFitPSO::VELOCITY_LIMIT_FACTOR = 0.2;
|
|
|
const int nmCalculationAutoFitPSO::CONVERGENCE_CHECK_INTERVAL = 5;
|
|
|
|
|
|
static const double kSurrogateKeepFraction = 0.50;
|
|
|
static const double kSurrogateHighRiskKeepFraction = 0.70;
|
|
|
static const double kSurrogateAuditFraction = 0.05;
|
|
|
static const double kSurrogateMinSolverFraction = 0.55;
|
|
|
static const double kSurrogateHighRiskMinSolverFraction = 0.80;
|
|
|
static const int kSurrogateWarmupIterations = 2;
|
|
|
static const int kSurrogateFullSolverInterval = 10;
|
|
|
static const int kSurrogateScoringMaxAttempts = 2;
|
|
|
static const bool kUseFixedPsoSeed = true;
|
|
|
static const unsigned int kFixedPsoSeed = 1792008679u;
|
|
|
|
|
|
static const double kSurrogateKMin = 1.0e-4;
|
|
|
static const double kSurrogateKMax = 100.0;
|
|
|
static const double kSurrogateSkinMin = -10.0;
|
|
|
static const double kSurrogateSkinMax = 10.0;
|
|
|
static const double kSurrogateWellboreCMin = 1.0e-4;
|
|
|
static const double kSurrogateWellboreCMax = 2.0;
|
|
|
static const double kSurrogatePhiMin = 1.0e-2;
|
|
|
static const double kSurrogatePhiMax = 0.50;
|
|
|
static const double kSurrogateHMin = 2.0;
|
|
|
static const double kSurrogateHMax = 100.0;
|
|
|
static const double kSurrogateCfFixed = 4.315e-4;
|
|
|
static const double kSurrogateCfRelTol = 0.25;
|
|
|
static const int kSurrogateMinProdSections = 3;
|
|
|
static const int kSurrogateMaxProdSections = 5;
|
|
|
static const double kSurrogateProdTimeMin = 24.0;
|
|
|
static const double kSurrogateProdTimeMax = 220.0;
|
|
|
static const double kSurrogateShutinTimeMin = 12.0;
|
|
|
static const double kSurrogateShutinTimeMax = 140.0;
|
|
|
static const double kSurrogateQMin = 50.0;
|
|
|
static const double kSurrogateQMax = 500.0;
|
|
|
static const double kSurrogateQZeroThreshold = 1.0e-6;
|
|
|
static const double kSurrogateMaxRateStepRatio = 2.2;
|
|
|
static const double kSurrogateStrongDeclineEndStartRatio = 0.70;
|
|
|
static const double kSurrogateRateTrendRelTol = 1.0e-9;
|
|
|
|
|
|
// Check infinity and NaN.
|
|
|
static inline bool isFiniteNumber(double value)
|
|
|
{
|
|
|
#ifdef Q_OS_WIN
|
|
|
return _finite(value) != 0;
|
|
|
#else
|
|
|
return std::isfinite(value);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
static inline bool isInClosedRange(double value, double lower, double upper)
|
|
|
{
|
|
|
return isFiniteNumber(value) && value >= lower && value <= upper;
|
|
|
}
|
|
|
|
|
|
static inline bool isNearRelative(double value, double reference, double relTol)
|
|
|
{
|
|
|
if(!isFiniteNumber(value) || !isFiniteNumber(reference)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return qAbs(value - reference) <= qMax(1e-12, qAbs(reference) * relTol);
|
|
|
}
|
|
|
|
|
|
// sleep函数
|
|
|
static inline void msleep(int ms)
|
|
|
{
|
|
|
#ifdef Q_OS_WIN
|
|
|
Sleep(ms);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
static QString csvEscape(const QString& text)
|
|
|
{
|
|
|
QString escaped = text;
|
|
|
escaped.replace("\"", "\"\"");
|
|
|
return QString("\"%1\"").arg(escaped);
|
|
|
}
|
|
|
|
|
|
static QString traceNumber(double value)
|
|
|
{
|
|
|
if(!isFiniteNumber(value)) {
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
return QString::number(value, 'g', 17);
|
|
|
}
|
|
|
|
|
|
static QString traceParamAt(const QVector<double>& params, int index)
|
|
|
{
|
|
|
if(index < 0 || index >= params.size()) {
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
return traceNumber(params[index]);
|
|
|
}
|
|
|
|
|
|
static QString jsonEscape(const QString& text)
|
|
|
{
|
|
|
QString escaped = text;
|
|
|
escaped.replace("\\", "\\\\");
|
|
|
escaped.replace("\"", "\\\"");
|
|
|
escaped.replace("\b", "\\b");
|
|
|
escaped.replace("\f", "\\f");
|
|
|
escaped.replace("\n", "\\n");
|
|
|
escaped.replace("\r", "\\r");
|
|
|
escaped.replace("\t", "\\t");
|
|
|
return QString("\"%1\"").arg(escaped);
|
|
|
}
|
|
|
|
|
|
static QString jsonNumber(double value)
|
|
|
{
|
|
|
if(!isFiniteNumber(value)) {
|
|
|
return "null";
|
|
|
}
|
|
|
|
|
|
return QString::number(value, 'g', 17);
|
|
|
}
|
|
|
|
|
|
static QString jsonDoubleArray(const QVector<double>& values)
|
|
|
{
|
|
|
QStringList items;
|
|
|
|
|
|
for(int i = 0; i < values.size(); ++i) {
|
|
|
items << jsonNumber(values[i]);
|
|
|
}
|
|
|
|
|
|
return QString("[%1]").arg(items.join(","));
|
|
|
}
|
|
|
|
|
|
static QString jsonIntArray(const QVector<int>& values)
|
|
|
{
|
|
|
QStringList items;
|
|
|
|
|
|
for(int i = 0; i < values.size(); ++i) {
|
|
|
items << QString::number(values[i]);
|
|
|
}
|
|
|
|
|
|
return QString("[%1]").arg(items.join(","));
|
|
|
}
|
|
|
|
|
|
static QString jsonBoolArray(const QVector<bool>& values)
|
|
|
{
|
|
|
QStringList items;
|
|
|
|
|
|
for(int i = 0; i < values.size(); ++i) {
|
|
|
items << (values[i] ? "true" : "false");
|
|
|
}
|
|
|
|
|
|
return QString("[%1]").arg(items.join(","));
|
|
|
}
|
|
|
|
|
|
static double deterministicAuditRandom01(unsigned int seed, int generation, int particleIndex)
|
|
|
{
|
|
|
unsigned int x = seed;
|
|
|
x ^= 0x9e3779b9u + static_cast<unsigned int>(generation + 1) + (x << 6) + (x >> 2);
|
|
|
x ^= 0x85ebca6bu + static_cast<unsigned int>(particleIndex + 1) + (x << 6) + (x >> 2);
|
|
|
x ^= x >> 16;
|
|
|
x *= 0x7feb352du;
|
|
|
x ^= x >> 15;
|
|
|
x *= 0x846ca68bu;
|
|
|
x ^= x >> 16;
|
|
|
return static_cast<double>(x) / 4294967295.0;
|
|
|
}
|
|
|
|
|
|
static QString jsonStringArray(const QStringList& values)
|
|
|
{
|
|
|
QStringList items;
|
|
|
|
|
|
for(int i = 0; i < values.size(); ++i) {
|
|
|
items << jsonEscape(values[i]);
|
|
|
}
|
|
|
|
|
|
return QString("[%1]").arg(items.join(","));
|
|
|
}
|
|
|
|
|
|
static QString jsonPointArray(const QVector<QPointF>& points)
|
|
|
{
|
|
|
QStringList items;
|
|
|
|
|
|
for(int i = 0; i < points.size(); ++i) {
|
|
|
items << QString("{\"x\":%1,\"y\":%2}")
|
|
|
.arg(jsonNumber(points[i].x()))
|
|
|
.arg(jsonNumber(points[i].y()));
|
|
|
}
|
|
|
|
|
|
return QString("[%1]").arg(items.join(","));
|
|
|
}
|
|
|
|
|
|
static bool isValidMlRootDirectory(const QString& mlRootPath)
|
|
|
{
|
|
|
if(mlRootPath.isEmpty()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QFileInfo scriptInfo(QDir(mlRootPath).absoluteFilePath("scripts/score_pso_candidates.py"));
|
|
|
return scriptInfo.exists() && scriptInfo.isFile();
|
|
|
}
|
|
|
|
|
|
static QString findExecutableInPath(const QString& executableName)
|
|
|
{
|
|
|
if(executableName.isEmpty()) {
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
QString pathValue = QProcessEnvironment::systemEnvironment().value("PATH");
|
|
|
|
|
|
if(pathValue.isEmpty()) {
|
|
|
pathValue = QString::fromLocal8Bit(qgetenv("PATH"));
|
|
|
}
|
|
|
|
|
|
if(pathValue.isEmpty()) {
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
QStringList executableCandidates;
|
|
|
executableCandidates << executableName;
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
|
if(!executableName.contains('.')) {
|
|
|
executableCandidates << (executableName + ".exe")
|
|
|
<< (executableName + ".bat")
|
|
|
<< (executableName + ".cmd");
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
QChar pathSeparator(';');
|
|
|
#else
|
|
|
QChar pathSeparator(':');
|
|
|
#endif
|
|
|
QStringList pathEntries = pathValue.split(pathSeparator, QString::SkipEmptyParts);
|
|
|
|
|
|
for(int i = 0; i < pathEntries.size(); ++i) {
|
|
|
QDir dir(pathEntries[i].trimmed());
|
|
|
|
|
|
if(!dir.exists()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
for(int j = 0; j < executableCandidates.size(); ++j) {
|
|
|
QFileInfo executableInfo(dir.absoluteFilePath(executableCandidates[j]));
|
|
|
|
|
|
if(executableInfo.exists() && executableInfo.isFile()) {
|
|
|
return QDir::cleanPath(executableInfo.absoluteFilePath());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return QString();
|
|
|
}
|
|
|
|
|
|
static QStringList traceParameterNames()
|
|
|
{
|
|
|
QStringList names;
|
|
|
names << "k"
|
|
|
<< "skin"
|
|
|
<< "wellboreC"
|
|
|
<< "phi"
|
|
|
<< "initialPressure"
|
|
|
<< "h"
|
|
|
<< "Ct"
|
|
|
<< "Cf"
|
|
|
<< "Soi"
|
|
|
<< "Swi"
|
|
|
<< "Sgi";
|
|
|
return names;
|
|
|
}
|
|
|
|
|
|
// Constructor.
|
|
|
nmCalculationAutoFitPSO::nmCalculationAutoFitPSO(QObject* parent)
|
|
|
: QObject(parent)
|
|
|
, m_isRunning(false)
|
|
|
, m_shouldStop(false)
|
|
|
, m_isPaused(false)
|
|
|
, m_currentIteration(0)
|
|
|
, m_globalBestFitness(1e10)
|
|
|
, m_previousBestFitness(1e10)
|
|
|
, m_swarmSize(20)
|
|
|
, m_maxIterations(100)
|
|
|
, m_targetError(0.001)
|
|
|
, m_inertiaWeight(0.4)
|
|
|
, m_cognitiveParam(2.0)
|
|
|
, m_socialParam(1.2)
|
|
|
, m_totalEvaluations(0)
|
|
|
, m_successfulEvaluations(0)
|
|
|
, m_evaluationInProgress(0)
|
|
|
, m_consecutiveFailures(0)
|
|
|
, m_improvementThreshold(0.05)
|
|
|
, m_userInitialFitness(1e10)
|
|
|
, m_consecutiveFailedIterations(0)
|
|
|
, m_maxConsecutiveFailures(3)
|
|
|
, m_hasValidUserSolution(false)
|
|
|
, m_diversityThreshold(0.05)
|
|
|
, m_convergenceVarianceThreshold(1e-8)
|
|
|
, m_velocityConvergenceThreshold(0.01)
|
|
|
, m_trueConvergenceWindow(15)
|
|
|
, m_localOptimumWindow(8)
|
|
|
, m_nearTargetFactor(2.0)
|
|
|
, m_farTargetFactor(10.0)
|
|
|
, m_targetWellName("")
|
|
|
, m_traceEnabled(true)
|
|
|
, m_traceRunId("")
|
|
|
, m_traceFilePath("")
|
|
|
, m_traceMetaFilePath("")
|
|
|
, m_surrogateScreeningEnabled(false)
|
|
|
, m_psoRandomSeed(0)
|
|
|
, m_surrogateRunContextSummary("")
|
|
|
, m_surrogateWarmupIterationCount(0)
|
|
|
, m_surrogatePeriodicAuditIterationCount(0)
|
|
|
, m_surrogateContextBlockedIterationCount(0)
|
|
|
, m_surrogateActiveIterationCount(0)
|
|
|
, m_surrogateSelectedParticleCount(0)
|
|
|
, m_surrogateScreenedParticleCount(0)
|
|
|
, m_surrogateTopKParticleCount(0)
|
|
|
, m_surrogateAuditParticleCount(0)
|
|
|
, m_surrogateFallbackParticleCount(0)
|
|
|
, m_surrogateDomainParticleCount(0)
|
|
|
, m_surrogateMinFloorParticleCount(0)
|
|
|
, m_surrogateScoringProcess(nullptr)
|
|
|
, m_surrogateScoringServerKey("")
|
|
|
|
|
|
, m_simulationMode(false)
|
|
|
, m_simulationTimer(nullptr)
|
|
|
, m_simulationIteration(0)
|
|
|
, m_simulationMaxIterations(100)
|
|
|
, m_simulationTargetError(0.001)
|
|
|
, m_simulationStartError(0.5)
|
|
|
, m_simulationCurrentError(0.5)
|
|
|
, m_simulationLogInterval(2)
|
|
|
, m_simulationCurrentParticle(0)
|
|
|
, m_simulationSuccessCount(0)
|
|
|
, m_simulationFailCount(0)
|
|
|
, m_simulationIterationStarted(false)
|
|
|
, m_simulationPreviousIterError(0.5)
|
|
|
{
|
|
|
DEBUG_OUT(QString("PSO Constructor: this=0x%1").arg((quintptr)this, 0, 16));
|
|
|
|
|
|
initializeTemporaryDirectory();
|
|
|
|
|
|
DEBUG_OUT("AutoFit calculator initialized (data-driven mode)");
|
|
|
DEBUG_OUT("PSO Constructor completed");
|
|
|
}
|
|
|
|
|
|
// Destructor.
|
|
|
nmCalculationAutoFitPSO::~nmCalculationAutoFitPSO()
|
|
|
{
|
|
|
DEBUG_OUT(QString("PSO Destructor: this=0x%1").arg((quintptr)this, 0, 16));
|
|
|
|
|
|
if(m_simulationTimer) {
|
|
|
m_simulationTimer->stop();
|
|
|
delete m_simulationTimer;
|
|
|
m_simulationTimer = nullptr;
|
|
|
}
|
|
|
|
|
|
// Stop algorithm.
|
|
|
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 PSO - timeout reached");
|
|
|
m_isRunning = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Clean temp directory.
|
|
|
stopSurrogateScoringServer();
|
|
|
closeTraceFile();
|
|
|
cleanupTemporaryDirectory();
|
|
|
|
|
|
// Disconnect all signals to avoid callbacks after destruction.
|
|
|
disconnect(this, nullptr, nullptr, nullptr);
|
|
|
|
|
|
DEBUG_OUT("PSO Destructor completed");
|
|
|
}
|
|
|
|
|
|
// Directory helpers.
|
|
|
|
|
|
void nmCalculationAutoFitPSO::initializeTemporaryDirectory()
|
|
|
{
|
|
|
cleanupOldTemporaryDirectories();
|
|
|
|
|
|
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 nmCalculationAutoFitPSO::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 nmCalculationAutoFitPSO::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 nmCalculationAutoFitPSO::cleanupOldTemporaryDirectories()
|
|
|
{
|
|
|
QString appDir = QApplication::applicationDirPath();
|
|
|
QDir dir(appDir);
|
|
|
|
|
|
// 获取当前进程ID,避免删除当前进程可能使用的目录
|
|
|
QString currentProcessId = QString::number(QCoreApplication::applicationPid());
|
|
|
|
|
|
// 查找所有以 "autofit_temp_" 开头的目录
|
|
|
QStringList filters;
|
|
|
filters << "autofit_temp_*";
|
|
|
QFileInfoList tempDirs = dir.entryInfoList(filters, QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
|
|
if(tempDirs.isEmpty()) {
|
|
|
DEBUG_OUT("No old temporary directories found");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
DEBUG_OUT(QString("Found %1 potential old temporary directories to clean up").arg(tempDirs.size()));
|
|
|
|
|
|
int cleanedCount = 0;
|
|
|
int failedCount = 0;
|
|
|
int skippedCount = 0;
|
|
|
|
|
|
for(int i = 0; i < tempDirs.size(); ++i) {
|
|
|
const QFileInfo& dirInfo = tempDirs[i];
|
|
|
QString dirPath = dirInfo.absoluteFilePath();
|
|
|
QString dirName = dirInfo.fileName();
|
|
|
|
|
|
// 检查是否是当前进程的目录(虽然理论上不应该存在,但为了安全起见)
|
|
|
if(dirName.contains("_" + currentProcessId + "_")) {
|
|
|
DEBUG_OUT(QString("Skipping current process directory: %1").arg(dirName));
|
|
|
skippedCount++;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 尝试删除目录
|
|
|
DEBUG_OUT(QString("Attempting to remove old temp directory: %1").arg(dirName));
|
|
|
|
|
|
if(removeDirectoryRecursively(dirPath)) {
|
|
|
DEBUG_OUT(QString("Successfully cleaned up: %1").arg(dirName));
|
|
|
cleanedCount++;
|
|
|
} else {
|
|
|
DEBUG_OUT(QString("Failed to clean up: %1 (may be in use by another process)").arg(dirName));
|
|
|
failedCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 输出清理统计信息
|
|
|
DEBUG_OUT(QString("Old temp directories cleanup summary: %1 removed, %2 failed, %3 skipped")
|
|
|
.arg(cleanedCount).arg(failedCount).arg(skippedCount));
|
|
|
}
|
|
|
|
|
|
|
|
|
// ==================== 公共接口方法 ====================
|
|
|
|
|
|
void nmCalculationAutoFitPSO::setTargetLogLogData(const QVector<QVector<double> >& 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()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
void nmCalculationAutoFitPSO::stopFitting()
|
|
|
{
|
|
|
if(m_simulationMode && m_simulationTimer) {
|
|
|
m_simulationTimer->stop();
|
|
|
}
|
|
|
|
|
|
if(m_isRunning) {
|
|
|
DEBUG_OUT("Stop request received, setting stop flag...");
|
|
|
|
|
|
// 添加停止日志
|
|
|
emit logMessageGenerated(tr("=== User Stop Request Received ==="));
|
|
|
emit logMessageGenerated(tr("Gracefully stopping PSO optimization..."));
|
|
|
|
|
|
m_shouldStop = true;
|
|
|
|
|
|
// 等待当前评估完成,缩短超时时间
|
|
|
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("PSO optimization stop request processed"));
|
|
|
DEBUG_OUT("Stop request processed");
|
|
|
} else {
|
|
|
DEBUG_OUT("Stop request received but PSO is not running");
|
|
|
emit logMessageGenerated(tr("Stop request received but optimization is not running"));
|
|
|
}
|
|
|
|
|
|
closeTraceFile();
|
|
|
cleanupTemporaryDirectory();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isRunning() const
|
|
|
{
|
|
|
return m_isRunning;
|
|
|
}
|
|
|
|
|
|
int nmCalculationAutoFitPSO::getCurrentIteration() const
|
|
|
{
|
|
|
return m_currentIteration;
|
|
|
}
|
|
|
|
|
|
QVector<double> nmCalculationAutoFitPSO::getBestSolution() const
|
|
|
{
|
|
|
return m_globalBestPosition;
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::getBestFitness() const
|
|
|
{
|
|
|
return m_globalBestFitness;
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getLastError() const
|
|
|
{
|
|
|
return m_lastError;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::resetOptimizer()
|
|
|
{
|
|
|
closeTraceFile();
|
|
|
m_swarm.clear();
|
|
|
m_globalBestPosition.clear();
|
|
|
m_globalBestFitness = 1e10;
|
|
|
m_previousBestFitness = 1e10;
|
|
|
m_lastEvaluatedLogLogData.clear();
|
|
|
m_globalBestLogLogData.clear();
|
|
|
m_userInitialLogLogData.clear();
|
|
|
m_currentIteration = 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();
|
|
|
m_velocityHistory.clear();
|
|
|
m_particleStagnationHistory.clear();
|
|
|
m_traceMetaFilePath.clear();
|
|
|
resetRunSummary();
|
|
|
|
|
|
DEBUG_OUT("Optimizer reset");
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::setPSOTargetWellName(const QString& wellName)
|
|
|
{
|
|
|
m_targetWellName = wellName;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::initializeTraceFile()
|
|
|
{
|
|
|
if(!m_traceEnabled) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
closeTraceFile();
|
|
|
captureSurrogateRunContextSummary();
|
|
|
|
|
|
m_traceRunId = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss_zzz");
|
|
|
QDir traceDir(QDir(getMlRootPath()).absoluteFilePath("data/temp"));
|
|
|
|
|
|
if(!traceDir.exists() && !QDir().mkpath(traceDir.absolutePath())) {
|
|
|
DEBUG_OUT(QString("Failed to create PSO trace directory: %1").arg(traceDir.absolutePath()));
|
|
|
m_traceFilePath.clear();
|
|
|
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));
|
|
|
m_traceFile.setFileName(m_traceFilePath);
|
|
|
|
|
|
if(!m_traceFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
|
DEBUG_OUT(QString("Failed to open PSO trace file: %1").arg(m_traceFilePath));
|
|
|
m_traceFilePath.clear();
|
|
|
m_traceMetaFilePath.clear();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
writeTraceHeader();
|
|
|
writeTraceMetaFile();
|
|
|
DEBUG_OUT(QString("PSO baseline trace initialized: %1").arg(m_traceFilePath));
|
|
|
emit logMessageGenerated(tr("PSO baseline trace: %1").arg(m_traceFilePath));
|
|
|
|
|
|
if(!m_traceMetaFilePath.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("PSO baseline trace meta: %1").arg(m_traceMetaFilePath));
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr("PSO surrogate screening: %1, model=%2, keep=%3, audit=%4, warmup=%5, min_solver=%6")
|
|
|
.arg(isSurrogateScreeningEnabled() ? tr("enabled") : tr("disabled"))
|
|
|
.arg(getSurrogateTag())
|
|
|
.arg(getSurrogateKeepFraction(), 0, 'f', 2)
|
|
|
.arg(getSurrogateAuditFraction(), 0, 'f', 2)
|
|
|
.arg(getSurrogateWarmupIterations())
|
|
|
.arg(getSurrogateMinSolverFraction(), 0, 'f', 2));
|
|
|
|
|
|
if(!isSurrogateScreeningEnabled()) {
|
|
|
emit logMessageGenerated(tr("Surrogate screening is turned off in the automatic fitting configuration"));
|
|
|
} else {
|
|
|
if(m_surrogateRunContextSummary == "passed") {
|
|
|
emit logMessageGenerated(tr("Surrogate run context check: passed"));
|
|
|
} else {
|
|
|
emit logMessageGenerated(tr("Surrogate run context check: %1").arg(m_surrogateRunContextSummary));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::resetRunSummary()
|
|
|
{
|
|
|
m_surrogateRunContextSummary.clear();
|
|
|
m_surrogateWarmupIterationCount = 0;
|
|
|
m_surrogatePeriodicAuditIterationCount = 0;
|
|
|
m_surrogateContextBlockedIterationCount = 0;
|
|
|
m_surrogateActiveIterationCount = 0;
|
|
|
m_surrogateSelectedParticleCount = 0;
|
|
|
m_surrogateScreenedParticleCount = 0;
|
|
|
m_surrogateTopKParticleCount = 0;
|
|
|
m_surrogateAuditParticleCount = 0;
|
|
|
m_surrogateFallbackParticleCount = 0;
|
|
|
m_surrogateDomainParticleCount = 0;
|
|
|
m_surrogateMinFloorParticleCount = 0;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::captureSurrogateRunContextSummary()
|
|
|
{
|
|
|
if(!isSurrogateScreeningEnabled()) {
|
|
|
m_surrogateRunContextSummary = "disabled_by_config";
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QString contextReason;
|
|
|
|
|
|
if(isSurrogateRunContextSupported(&contextReason)) {
|
|
|
m_surrogateRunContextSummary = "passed";
|
|
|
} else {
|
|
|
m_surrogateRunContextSummary = QString("failed - %1").arg(contextReason);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::emitRunSummary(bool success, StopReasonPSO finalReason)
|
|
|
{
|
|
|
emit logMessageGenerated(tr("=== PSO Run Summary ==="));
|
|
|
emit logMessageGenerated(tr("Stop reason: %1").arg(getStopReasonDescription(finalReason)));
|
|
|
emit logMessageGenerated(tr("Result: %1, final error=%2, iterations=%3, evaluations=%4 (successful=%5, failed=%6)")
|
|
|
.arg(success ? "SUCCESS" : "FAILED")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4)
|
|
|
.arg(m_currentIteration + 1)
|
|
|
.arg(m_totalEvaluations)
|
|
|
.arg(m_successfulEvaluations)
|
|
|
.arg(m_totalEvaluations - m_successfulEvaluations));
|
|
|
emit logMessageGenerated(tr("Surrogate config: %1, context=%2")
|
|
|
.arg(isSurrogateScreeningEnabled() ? "enabled" : "disabled")
|
|
|
.arg(m_surrogateRunContextSummary.isEmpty() ? QString("unknown") : m_surrogateRunContextSummary));
|
|
|
emit logMessageGenerated(tr("Surrogate iterations: active=%1, warmup=%2, periodic_audit=%3, context_blocked=%4")
|
|
|
.arg(m_surrogateActiveIterationCount)
|
|
|
.arg(m_surrogateWarmupIterationCount)
|
|
|
.arg(m_surrogatePeriodicAuditIterationCount)
|
|
|
.arg(m_surrogateContextBlockedIterationCount));
|
|
|
emit logMessageGenerated(tr("Surrogate particles: selected=%1, screened_out=%2, topk=%3, random_audit=%4, fallback=%5, domain_gate=%6, min_solver_floor=%7")
|
|
|
.arg(m_surrogateSelectedParticleCount)
|
|
|
.arg(m_surrogateScreenedParticleCount)
|
|
|
.arg(m_surrogateTopKParticleCount)
|
|
|
.arg(m_surrogateAuditParticleCount)
|
|
|
.arg(m_surrogateFallbackParticleCount)
|
|
|
.arg(m_surrogateDomainParticleCount)
|
|
|
.arg(m_surrogateMinFloorParticleCount));
|
|
|
|
|
|
if(!m_traceFilePath.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Artifacts: trace=%1").arg(m_traceFilePath));
|
|
|
}
|
|
|
|
|
|
if(!m_traceMetaFilePath.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Artifacts: trace_meta=%1").arg(m_traceMetaFilePath));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::closeTraceFile()
|
|
|
{
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
if(m_traceFile.isOpen()) {
|
|
|
m_traceFile.flush();
|
|
|
m_traceFile.close();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::writeTraceHeader()
|
|
|
{
|
|
|
if(!m_traceFile.isOpen()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QStringList cols;
|
|
|
cols << "run_id"
|
|
|
<< "generation"
|
|
|
<< "particle_id"
|
|
|
<< "phase"
|
|
|
<< "k"
|
|
|
<< "skin"
|
|
|
<< "wellboreC"
|
|
|
<< "phi"
|
|
|
<< "h"
|
|
|
<< "Cf"
|
|
|
<< "solver_objective"
|
|
|
<< "solver_success"
|
|
|
<< "elapsed_ms"
|
|
|
<< "surrogate_objective"
|
|
|
<< "screening_decision"
|
|
|
<< "pbest_objective"
|
|
|
<< "pbest_k"
|
|
|
<< "pbest_skin"
|
|
|
<< "pbest_wellboreC"
|
|
|
<< "pbest_phi"
|
|
|
<< "pbest_h"
|
|
|
<< "pbest_Cf"
|
|
|
<< "gbest_objective"
|
|
|
<< "gbest_k"
|
|
|
<< "gbest_skin"
|
|
|
<< "gbest_wellboreC"
|
|
|
<< "gbest_phi"
|
|
|
<< "gbest_h"
|
|
|
<< "gbest_Cf"
|
|
|
<< "enabled_param_indices";
|
|
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
out << cols.join(",") << "\n";
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::writeTraceMetaFile()
|
|
|
{
|
|
|
if(m_traceMetaFilePath.isEmpty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QFile metaFile(m_traceMetaFilePath);
|
|
|
|
|
|
if(!metaFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
|
DEBUG_OUT(QString("Failed to open PSO trace meta file: %1").arg(m_traceMetaFilePath));
|
|
|
m_traceMetaFilePath.clear();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
nmDataWellBase* pTargetWell = dataManager ? dataManager->findWellByName(m_targetWellName) : nullptr;
|
|
|
|
|
|
QStringList parameterNames = traceParameterNames();
|
|
|
QStringList enabledNames;
|
|
|
|
|
|
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
|
|
|
int paramIndex = m_enabledParamIndices[i];
|
|
|
enabledNames << ((paramIndex >= 0 && paramIndex < parameterNames.size()) ? parameterNames[paramIndex] : QString::number(paramIndex));
|
|
|
}
|
|
|
|
|
|
QVector<double> initialFullParams = buildTraceParameterVector(m_initialValues);
|
|
|
QVector<double> targetTime = m_targetLogLogData.size() > 0 ? m_targetLogLogData[0] : QVector<double>();
|
|
|
QVector<double> targetPressure = m_targetLogLogData.size() > 1 ? m_targetLogLogData[1] : QVector<double>();
|
|
|
QVector<double> targetDerivative = m_targetLogLogData.size() > 2 ? m_targetLogLogData[2] : QVector<double>();
|
|
|
QVector<QPointF> flowPoints = pTargetWell ? pTargetWell->getFlowPoints() : QVector<QPointF>();
|
|
|
|
|
|
QTextStream out(&metaFile);
|
|
|
out << "{\n";
|
|
|
out << " \"schema_version\": 1,\n";
|
|
|
out << " \"trace_type\": \"pso_baseline_replay_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";
|
|
|
out << " \"target\": {\n";
|
|
|
out << " \"well_name\": " << jsonEscape(m_targetWellName) << ",\n";
|
|
|
out << " \"well_type\": " << (pTargetWell ? QString::number(static_cast<int>(pTargetWell->getWellType())) : "null") << ",\n";
|
|
|
out << " \"section_policy\": \"current_target_well_indexF\",\n";
|
|
|
out << " \"section_index\": " << (pTargetWell ? QString::number(pTargetWell->getIndexF()) : "null") << ",\n";
|
|
|
out << " \"target_loglog\": {\n";
|
|
|
out << " \"time\": " << jsonDoubleArray(targetTime) << ",\n";
|
|
|
out << " \"pressure\": " << jsonDoubleArray(targetPressure) << ",\n";
|
|
|
out << " \"derivative\": " << jsonDoubleArray(targetDerivative) << "\n";
|
|
|
out << " },\n";
|
|
|
out << " \"flow_points\": " << jsonPointArray(flowPoints) << "\n";
|
|
|
out << " },\n";
|
|
|
out << " \"pso\": {\n";
|
|
|
out << " \"swarm_size\": " << m_swarmSize << ",\n";
|
|
|
out << " \"max_iterations\": " << m_maxIterations << ",\n";
|
|
|
out << " \"target_error\": " << jsonNumber(m_targetError) << ",\n";
|
|
|
out << " \"inertia_weight\": " << jsonNumber(m_inertiaWeight) << ",\n";
|
|
|
out << " \"cognitive_param\": " << jsonNumber(m_cognitiveParam) << ",\n";
|
|
|
out << " \"social_param\": " << jsonNumber(m_socialParam) << ",\n";
|
|
|
out << " \"random_seed\": " << m_psoRandomSeed << "\n";
|
|
|
out << " },\n";
|
|
|
out << " \"surrogate_screening\": {\n";
|
|
|
out << " \"enabled\": " << (isSurrogateScreeningEnabled() ? "true" : "false") << ",\n";
|
|
|
out << " \"model_tag\": " << jsonEscape(getSurrogateTag()) << ",\n";
|
|
|
out << " \"keep_fraction\": " << jsonNumber(getSurrogateKeepFraction()) << ",\n";
|
|
|
out << " \"audit_fraction\": " << jsonNumber(getSurrogateAuditFraction()) << ",\n";
|
|
|
out << " \"min_solver_fraction\": " << jsonNumber(getSurrogateMinSolverFraction()) << ",\n";
|
|
|
out << " \"warmup_iterations\": " << getSurrogateWarmupIterations() << ",\n";
|
|
|
out << " \"full_solver_interval\": " << getSurrogateFullSolverInterval() << ",\n";
|
|
|
out << " \"audit_random_source\": \"deterministic_hash\"\n";
|
|
|
out << " },\n";
|
|
|
out << " \"parameters\": {\n";
|
|
|
out << " \"names\": " << jsonStringArray(parameterNames) << ",\n";
|
|
|
out << " \"enabled_indices\": " << jsonIntArray(m_enabledParamIndices) << ",\n";
|
|
|
out << " \"enabled_names\": " << jsonStringArray(enabledNames) << ",\n";
|
|
|
out << " \"selected_flags\": " << jsonBoolArray(m_parameterSelected) << ",\n";
|
|
|
out << " \"lower\": " << jsonDoubleArray(m_parameterLower) << ",\n";
|
|
|
out << " \"upper\": " << jsonDoubleArray(m_parameterUpper) << ",\n";
|
|
|
out << " \"initial_selected\": " << jsonDoubleArray(m_initialValues) << ",\n";
|
|
|
out << " \"initial_full\": " << jsonDoubleArray(initialFullParams) << "\n";
|
|
|
out << " },\n";
|
|
|
out << " \"replay_notes\": [\n";
|
|
|
out << " \"CSV rows contain true solver objectives for evaluated particles.\",\n";
|
|
|
out << " \"This meta file contains the target curve and run context needed for surrogate replay.\",\n";
|
|
|
out << " \"pbest and gbest must be updated only from true solver objectives.\"\n";
|
|
|
out << " ]\n";
|
|
|
out << "}\n";
|
|
|
metaFile.flush();
|
|
|
metaFile.close();
|
|
|
}
|
|
|
|
|
|
QVector<double> nmCalculationAutoFitPSO::buildTraceParameterVector(const QVector<double>& selectedParameters) const
|
|
|
{
|
|
|
QVector<double> fullParams(11, 0.0);
|
|
|
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
if(dataManager) {
|
|
|
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
|
|
|
fullParams[0] = reservoirData.getPermeability().getValue().toDouble();
|
|
|
fullParams[3] = reservoirData.getPorosity().getValue().toDouble();
|
|
|
fullParams[4] = reservoirData.getInitialPressure().getValue().toDouble();
|
|
|
fullParams[5] = reservoirData.getThickness().getValue().toDouble();
|
|
|
fullParams[6] = reservoirData.getCt().getValue().toDouble();
|
|
|
fullParams[7] = reservoirData.getCf().getValue().toDouble();
|
|
|
fullParams[8] = reservoirData.getSoi().getValue().toDouble();
|
|
|
fullParams[9] = reservoirData.getSwi().getValue().toDouble();
|
|
|
fullParams[10] = reservoirData.getSgi().getValue().toDouble();
|
|
|
|
|
|
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
|
|
|
|
|
|
if(pTargetWell) {
|
|
|
fullParams[1] = pTargetWell->getPerforation(0)->getSkin().getValue().toDouble();
|
|
|
fullParams[2] = pTargetWell->getWellboreStorage().getValue().toDouble();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < selectedParameters.size() && i < m_enabledParamIndices.size(); ++i) {
|
|
|
int paramIndex = m_enabledParamIndices[i];
|
|
|
|
|
|
if(paramIndex >= 0 && paramIndex < fullParams.size()) {
|
|
|
fullParams[paramIndex] = selectedParameters[i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return fullParams;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::writeTraceRow(int generation,
|
|
|
int particleIndex,
|
|
|
const QString& phase,
|
|
|
const QVector<double>& parameters,
|
|
|
double solverObjective,
|
|
|
bool solverSuccess,
|
|
|
int elapsedMs,
|
|
|
double surrogateObjective,
|
|
|
const QString& screeningDecision,
|
|
|
const QVector<double>& pbestPosition,
|
|
|
double pbestObjective)
|
|
|
{
|
|
|
if(!m_traceFile.isOpen()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QVector<double> currentParams = buildTraceParameterVector(parameters);
|
|
|
QVector<double> pbestParams = pbestPosition.isEmpty() ? QVector<double>() : buildTraceParameterVector(pbestPosition);
|
|
|
QVector<double> gbestParams = m_globalBestPosition.isEmpty() ? QVector<double>() : buildTraceParameterVector(m_globalBestPosition);
|
|
|
|
|
|
QStringList enabledIndices;
|
|
|
|
|
|
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
|
|
|
enabledIndices << QString::number(m_enabledParamIndices[i]);
|
|
|
}
|
|
|
|
|
|
QStringList cols;
|
|
|
cols << csvEscape(m_traceRunId)
|
|
|
<< QString::number(generation)
|
|
|
<< QString::number(particleIndex)
|
|
|
<< csvEscape(phase)
|
|
|
<< traceParamAt(currentParams, 0)
|
|
|
<< traceParamAt(currentParams, 1)
|
|
|
<< traceParamAt(currentParams, 2)
|
|
|
<< traceParamAt(currentParams, 3)
|
|
|
<< traceParamAt(currentParams, 5)
|
|
|
<< traceParamAt(currentParams, 7)
|
|
|
<< traceNumber(solverObjective)
|
|
|
<< QString::number(solverSuccess ? 1 : 0)
|
|
|
<< QString::number(elapsedMs)
|
|
|
<< traceNumber(surrogateObjective)
|
|
|
<< csvEscape(screeningDecision)
|
|
|
<< traceNumber(pbestObjective)
|
|
|
<< traceParamAt(pbestParams, 0)
|
|
|
<< traceParamAt(pbestParams, 1)
|
|
|
<< traceParamAt(pbestParams, 2)
|
|
|
<< traceParamAt(pbestParams, 3)
|
|
|
<< traceParamAt(pbestParams, 5)
|
|
|
<< traceParamAt(pbestParams, 7)
|
|
|
<< traceNumber(m_globalBestFitness)
|
|
|
<< traceParamAt(gbestParams, 0)
|
|
|
<< traceParamAt(gbestParams, 1)
|
|
|
<< traceParamAt(gbestParams, 2)
|
|
|
<< traceParamAt(gbestParams, 3)
|
|
|
<< traceParamAt(gbestParams, 5)
|
|
|
<< traceParamAt(gbestParams, 7)
|
|
|
<< csvEscape(enabledIndices.join(";"));
|
|
|
|
|
|
QTextStream out(&m_traceFile);
|
|
|
out << cols.join(",") << "\n";
|
|
|
m_traceFile.flush();
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::writeIterationTraceRows()
|
|
|
{
|
|
|
if(!m_traceFile.isOpen()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
const AutoFitParticle& particle = m_swarm[i];
|
|
|
double solverObjective = particle.evaluatedThisIteration
|
|
|
? particle.fitness
|
|
|
: std::numeric_limits<double>::quiet_NaN();
|
|
|
writeTraceRow(m_currentIteration,
|
|
|
i,
|
|
|
particle.evaluatedThisIteration ? "particle_solver" : "particle_not_evaluated",
|
|
|
particle.position,
|
|
|
solverObjective,
|
|
|
particle.lastEvaluationSuccess,
|
|
|
particle.lastEvaluationElapsedMs,
|
|
|
particle.surrogateObjective,
|
|
|
particle.screeningDecision,
|
|
|
particle.bestPosition,
|
|
|
particle.bestFitness);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isSurrogateScreeningEnabled() const
|
|
|
{
|
|
|
return m_surrogateScreeningEnabled;
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getMlRootPath() const
|
|
|
{
|
|
|
QString appDir = QApplication::applicationDirPath();
|
|
|
QString cwd = QDir::currentPath();
|
|
|
|
|
|
QStringList candidateRoots;
|
|
|
candidateRoots << QDir::cleanPath(appDir + "/../../ML/nmWTAI-ML")
|
|
|
<< QDir::cleanPath(appDir + "/../ML/nmWTAI-ML")
|
|
|
<< QDir::cleanPath(cwd + "/ML/nmWTAI-ML")
|
|
|
<< QDir::cleanPath(cwd + "/../ML/nmWTAI-ML")
|
|
|
<< QDir::cleanPath(cwd + "/../../ML/nmWTAI-ML");
|
|
|
|
|
|
for(int i = 0; i < candidateRoots.size(); ++i) {
|
|
|
if(isValidMlRootDirectory(candidateRoots[i])) {
|
|
|
return candidateRoots[i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return candidateRoots.isEmpty()
|
|
|
? QString()
|
|
|
: candidateRoots.first();
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getPythonExecutablePath() const
|
|
|
{
|
|
|
QString mlRoot = getMlRootPath();
|
|
|
QStringList candidateExecutables;
|
|
|
|
|
|
if(!mlRoot.isEmpty()) {
|
|
|
candidateExecutables << QDir(mlRoot).absoluteFilePath(".venv/Scripts/python.exe");
|
|
|
candidateExecutables << QDir(mlRoot).absoluteFilePath(".venv/bin/python");
|
|
|
}
|
|
|
|
|
|
for(int i = 0; i < candidateExecutables.size(); ++i) {
|
|
|
QFileInfo pythonInfo(candidateExecutables[i]);
|
|
|
|
|
|
if(pythonInfo.exists() && pythonInfo.isFile()) {
|
|
|
return QDir::cleanPath(pythonInfo.absoluteFilePath());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString resolvedPython = findExecutableInPath("python");
|
|
|
|
|
|
if(!resolvedPython.isEmpty()) {
|
|
|
return QDir::cleanPath(resolvedPython);
|
|
|
}
|
|
|
|
|
|
return "python";
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getSurrogateTag() const
|
|
|
{
|
|
|
return "family_random_v2_q_50k_logparam";
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getSurrogateStage() const
|
|
|
{
|
|
|
return "family_random_v2_q";
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::getSurrogateKeepFraction() const
|
|
|
{
|
|
|
if(getSurrogateStage().contains("v2_q") && isStrongDecliningProductionSchedule()) {
|
|
|
return kSurrogateHighRiskKeepFraction;
|
|
|
}
|
|
|
|
|
|
return kSurrogateKeepFraction;
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::getSurrogateAuditFraction() const
|
|
|
{
|
|
|
return kSurrogateAuditFraction;
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::getSurrogateMinSolverFraction() const
|
|
|
{
|
|
|
if(getSurrogateStage().contains("v2_q") && isStrongDecliningProductionSchedule()) {
|
|
|
return kSurrogateHighRiskMinSolverFraction;
|
|
|
}
|
|
|
|
|
|
return kSurrogateMinSolverFraction;
|
|
|
}
|
|
|
|
|
|
int nmCalculationAutoFitPSO::getSurrogateWarmupIterations() const
|
|
|
{
|
|
|
return kSurrogateWarmupIterations;
|
|
|
}
|
|
|
|
|
|
int nmCalculationAutoFitPSO::getSurrogateFullSolverInterval() const
|
|
|
{
|
|
|
return kSurrogateFullSolverInterval;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::writeSurrogateCandidateCsv(const QString& candidatePath) const
|
|
|
{
|
|
|
QFile file(candidatePath);
|
|
|
|
|
|
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
|
DEBUG_OUT(QString("Failed to open surrogate candidate file: %1").arg(candidatePath));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QTextStream out(&file);
|
|
|
out << "particle_id,k,skin,wellboreC,phi,h,Cf\n";
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
QVector<double> params = buildTraceParameterVector(m_swarm[i].position);
|
|
|
QStringList cols;
|
|
|
cols << QString::number(i)
|
|
|
<< traceParamAt(params, 0)
|
|
|
<< traceParamAt(params, 1)
|
|
|
<< traceParamAt(params, 2)
|
|
|
<< traceParamAt(params, 3)
|
|
|
<< traceParamAt(params, 5)
|
|
|
<< traceParamAt(params, 7);
|
|
|
out << cols.join(",") << "\n";
|
|
|
}
|
|
|
|
|
|
file.flush();
|
|
|
file.close();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::runSurrogateScoringProcess(const QString& candidatePath, const QString& scorePath, QString* failureReason)
|
|
|
{
|
|
|
QString serverFailure;
|
|
|
|
|
|
if(requestSurrogateScoresFromServer(candidatePath, scorePath, &serverFailure)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr("Surrogate scoring server unavailable; falling back to one-shot Python scoring"));
|
|
|
|
|
|
if(!serverFailure.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring server detail: %1").arg(serverFailure));
|
|
|
}
|
|
|
|
|
|
stopSurrogateScoringServer();
|
|
|
return runSurrogateScoringScriptOnce(candidatePath, scorePath, failureReason);
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::ensureSurrogateScoringServer(QString* failureReason)
|
|
|
{
|
|
|
if(m_traceMetaFilePath.isEmpty() || !QFileInfo(m_traceMetaFilePath).exists()) {
|
|
|
QString reason = "trace meta file is missing";
|
|
|
DEBUG_OUT(QString("Surrogate scoring server skipped: %1").arg(reason));
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString mlRoot = getMlRootPath();
|
|
|
QString scriptPath = QDir(mlRoot).absoluteFilePath("scripts/score_pso_candidates_server.py");
|
|
|
|
|
|
if(!QFileInfo(scriptPath).exists()) {
|
|
|
QString reason = QString("surrogate scoring server script not found: %1").arg(scriptPath);
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString pythonPath = getPythonExecutablePath();
|
|
|
QString serverKey = QString("%1|%2|%3|%4|%5")
|
|
|
.arg(pythonPath)
|
|
|
.arg(scriptPath)
|
|
|
.arg(m_traceMetaFilePath)
|
|
|
.arg(getSurrogateTag())
|
|
|
.arg(getSurrogateStage());
|
|
|
|
|
|
if(m_surrogateScoringProcess &&
|
|
|
m_surrogateScoringProcess->state() == QProcess::Running &&
|
|
|
m_surrogateScoringServerKey == serverKey) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
QStringList args;
|
|
|
args << "-u"
|
|
|
<< scriptPath
|
|
|
<< "--trace-meta" << m_traceMetaFilePath
|
|
|
<< "--tag" << getSurrogateTag()
|
|
|
<< "--stage" << getSurrogateStage();
|
|
|
|
|
|
m_surrogateScoringProcess = new QProcess(this);
|
|
|
m_surrogateScoringProcess->setWorkingDirectory(mlRoot);
|
|
|
|
|
|
emit logMessageGenerated(tr("Starting surrogate scoring server: python=%1, mlRoot=%2")
|
|
|
.arg(pythonPath)
|
|
|
.arg(mlRoot));
|
|
|
|
|
|
m_surrogateScoringProcess->start(pythonPath, args);
|
|
|
|
|
|
if(!m_surrogateScoringProcess->waitForStarted(15000)) {
|
|
|
QString reason = QString("failed to start scoring server: python=%1, script=%2, error=%3")
|
|
|
.arg(pythonPath)
|
|
|
.arg(scriptPath)
|
|
|
.arg(m_surrogateScoringProcess->errorString());
|
|
|
DEBUG_OUT(reason);
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QTime timer;
|
|
|
timer.start();
|
|
|
QByteArray readyLine;
|
|
|
|
|
|
while(timer.elapsed() < 120000) {
|
|
|
if(m_surrogateScoringProcess->state() != QProcess::Running) {
|
|
|
QString stdOut = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardOutput()).trimmed();
|
|
|
QString stdErr = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardError()).trimmed();
|
|
|
QString reason = QString("scoring server exited before ready: stdout=%1, stderr=%2")
|
|
|
.arg(stdOut.isEmpty() ? QString("<empty>") : stdOut)
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(reason);
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!m_surrogateScoringProcess->canReadLine()) {
|
|
|
m_surrogateScoringProcess->waitForReadyRead(500);
|
|
|
QApplication::processEvents();
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
readyLine = m_surrogateScoringProcess->readLine().trimmed();
|
|
|
|
|
|
if(readyLine.contains("\"ready\"") && readyLine.contains("true")) {
|
|
|
m_surrogateScoringServerKey = serverKey;
|
|
|
QString stdErr = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardError()).trimmed();
|
|
|
|
|
|
if(!stdErr.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring server stderr: %1").arg(stdErr));
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(readyLine.contains("\"ok\"") && readyLine.contains("false")) {
|
|
|
QString reason = QString("scoring server failed during initialization: %1").arg(QString::fromLocal8Bit(readyLine));
|
|
|
DEBUG_OUT(reason);
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString stdErr = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardError()).trimmed();
|
|
|
QString reason = QString("scoring server ready timeout: stderr=%1")
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(reason);
|
|
|
stopSurrogateScoringServer();
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::requestSurrogateScoresFromServer(const QString& candidatePath, const QString& scorePath, QString* failureReason)
|
|
|
{
|
|
|
if(!ensureSurrogateScoringServer(failureReason)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QFile::remove(scorePath);
|
|
|
|
|
|
QString command = QString("{\"cmd\":\"score\",\"candidates\":%1,\"output\":%2}\n")
|
|
|
.arg(jsonEscape(candidatePath))
|
|
|
.arg(jsonEscape(scorePath));
|
|
|
|
|
|
QByteArray bytes = command.toUtf8();
|
|
|
qint64 written = m_surrogateScoringProcess->write(bytes);
|
|
|
|
|
|
if(written != bytes.size() || !m_surrogateScoringProcess->waitForBytesWritten(10000)) {
|
|
|
QString reason = QString("failed to send scoring request to server: written=%1 expected=%2 error=%3")
|
|
|
.arg(written)
|
|
|
.arg(bytes.size())
|
|
|
.arg(m_surrogateScoringProcess->errorString());
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QTime timer;
|
|
|
timer.start();
|
|
|
|
|
|
while(timer.elapsed() < 120000) {
|
|
|
if(m_surrogateScoringProcess->state() != QProcess::Running) {
|
|
|
QString stdOut = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardOutput()).trimmed();
|
|
|
QString stdErr = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardError()).trimmed();
|
|
|
QString reason = QString("scoring server exited during request: stdout=%1, stderr=%2")
|
|
|
.arg(stdOut.isEmpty() ? QString("<empty>") : stdOut)
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!m_surrogateScoringProcess->canReadLine()) {
|
|
|
m_surrogateScoringProcess->waitForReadyRead(500);
|
|
|
QApplication::processEvents();
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
QByteArray lineBytes = m_surrogateScoringProcess->readLine().trimmed();
|
|
|
QString line = QString::fromLocal8Bit(lineBytes);
|
|
|
|
|
|
if(line.contains("\"ok\"") && line.contains("true")) {
|
|
|
QFileInfo scoreInfo(scorePath);
|
|
|
|
|
|
if(scoreInfo.exists() && scoreInfo.size() > 0) {
|
|
|
QString stdErr = QString::fromLocal8Bit(m_surrogateScoringProcess->readAllStandardError()).trimmed();
|
|
|
|
|
|
if(!stdErr.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring server stderr: %1").arg(stdErr));
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
QString reason = QString("scoring server reported success but score file is missing: %1").arg(scorePath);
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(line.contains("\"ok\"") && line.contains("false")) {
|
|
|
QString reason = QString("scoring server request failed: %1").arg(line);
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QString reason = "scoring server request timed out";
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::stopSurrogateScoringServer()
|
|
|
{
|
|
|
if(!m_surrogateScoringProcess) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(m_surrogateScoringProcess->state() == QProcess::Running) {
|
|
|
m_surrogateScoringProcess->write("{\"cmd\":\"quit\"}\n");
|
|
|
m_surrogateScoringProcess->waitForBytesWritten(1000);
|
|
|
m_surrogateScoringProcess->waitForFinished(3000);
|
|
|
|
|
|
if(m_surrogateScoringProcess->state() == QProcess::Running) {
|
|
|
m_surrogateScoringProcess->kill();
|
|
|
m_surrogateScoringProcess->waitForFinished(3000);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
delete m_surrogateScoringProcess;
|
|
|
m_surrogateScoringProcess = nullptr;
|
|
|
m_surrogateScoringServerKey.clear();
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::runSurrogateScoringScriptOnce(const QString& candidatePath, const QString& scorePath, QString* failureReason)
|
|
|
{
|
|
|
if(m_traceMetaFilePath.isEmpty() || !QFileInfo(m_traceMetaFilePath).exists()) {
|
|
|
QString reason = "trace meta file is missing";
|
|
|
DEBUG_OUT(QString("Surrogate scoring skipped: %1").arg(reason));
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString mlRoot = getMlRootPath();
|
|
|
QString scriptPath = QDir(mlRoot).absoluteFilePath("scripts/score_pso_candidates.py");
|
|
|
|
|
|
if(!QFileInfo(scriptPath).exists()) {
|
|
|
QString reason = QString("surrogate scoring script not found: %1").arg(scriptPath);
|
|
|
DEBUG_OUT(reason);
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = reason;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString pythonPath = getPythonExecutablePath();
|
|
|
QStringList args;
|
|
|
args << scriptPath
|
|
|
<< "--candidates" << candidatePath
|
|
|
<< "--trace-meta" << m_traceMetaFilePath
|
|
|
<< "--output" << scorePath
|
|
|
<< "--tag" << getSurrogateTag()
|
|
|
<< "--stage" << getSurrogateStage();
|
|
|
|
|
|
QFile::remove(scorePath);
|
|
|
|
|
|
QString lastFailure;
|
|
|
|
|
|
for(int attempt = 1; attempt <= kSurrogateScoringMaxAttempts; ++attempt) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring attempt %1/%2: python=%3, mlRoot=%4")
|
|
|
.arg(attempt)
|
|
|
.arg(kSurrogateScoringMaxAttempts)
|
|
|
.arg(pythonPath)
|
|
|
.arg(mlRoot));
|
|
|
|
|
|
QProcess process;
|
|
|
process.setWorkingDirectory(mlRoot);
|
|
|
process.start(pythonPath, args);
|
|
|
|
|
|
if(!process.waitForStarted(10000)) {
|
|
|
lastFailure = QString("failed to start scoring process (attempt %1/%2): python=%3, script=%4, error=%5")
|
|
|
.arg(attempt)
|
|
|
.arg(kSurrogateScoringMaxAttempts)
|
|
|
.arg(pythonPath)
|
|
|
.arg(scriptPath)
|
|
|
.arg(process.errorString());
|
|
|
DEBUG_OUT(lastFailure);
|
|
|
|
|
|
if(attempt < kSurrogateScoringMaxAttempts) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring start failed, retrying once: %1").arg(process.errorString()));
|
|
|
msleep(500);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = lastFailure;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!process.waitForFinished(120000)) {
|
|
|
process.kill();
|
|
|
process.waitForFinished(3000);
|
|
|
QString stdOut = QString::fromLocal8Bit(process.readAllStandardOutput()).trimmed();
|
|
|
QString stdErr = QString::fromLocal8Bit(process.readAllStandardError()).trimmed();
|
|
|
lastFailure = QString("scoring process timed out (attempt %1/%2): python=%3, script=%4, stdout=%5, stderr=%6")
|
|
|
.arg(attempt)
|
|
|
.arg(kSurrogateScoringMaxAttempts)
|
|
|
.arg(pythonPath)
|
|
|
.arg(scriptPath)
|
|
|
.arg(stdOut.isEmpty() ? QString("<empty>") : stdOut)
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(lastFailure);
|
|
|
|
|
|
if(attempt < kSurrogateScoringMaxAttempts) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring timed out, retrying once"));
|
|
|
msleep(500);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = lastFailure;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QString stdOut = QString::fromLocal8Bit(process.readAllStandardOutput()).trimmed();
|
|
|
QString stdErr = QString::fromLocal8Bit(process.readAllStandardError()).trimmed();
|
|
|
|
|
|
if(process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
|
|
lastFailure = QString("scoring process failed (attempt %1/%2): exitStatus=%3, exitCode=%4, python=%5, script=%6, stdout=%7, stderr=%8")
|
|
|
.arg(attempt)
|
|
|
.arg(kSurrogateScoringMaxAttempts)
|
|
|
.arg(process.exitStatus() == QProcess::NormalExit ? "NormalExit" : "CrashExit")
|
|
|
.arg(process.exitCode())
|
|
|
.arg(pythonPath)
|
|
|
.arg(scriptPath)
|
|
|
.arg(stdOut.isEmpty() ? QString("<empty>") : stdOut)
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(lastFailure);
|
|
|
|
|
|
if(attempt < kSurrogateScoringMaxAttempts) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring process failed, retrying once: exitCode=%1").arg(process.exitCode()));
|
|
|
msleep(500);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = lastFailure;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QFileInfo scoreInfo(scorePath);
|
|
|
|
|
|
if(!scoreInfo.exists() || scoreInfo.size() <= 0) {
|
|
|
lastFailure = QString("scoring process finished without a usable score file (attempt %1/%2): output=%3, stdout=%4, stderr=%5")
|
|
|
.arg(attempt)
|
|
|
.arg(kSurrogateScoringMaxAttempts)
|
|
|
.arg(scorePath)
|
|
|
.arg(stdOut.isEmpty() ? QString("<empty>") : stdOut)
|
|
|
.arg(stdErr.isEmpty() ? QString("<empty>") : stdErr);
|
|
|
DEBUG_OUT(lastFailure);
|
|
|
|
|
|
if(attempt < kSurrogateScoringMaxAttempts) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring produced no score file, retrying once"));
|
|
|
msleep(500);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = lastFailure;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!stdErr.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring stderr: %1").arg(stdErr));
|
|
|
}
|
|
|
|
|
|
if(!stdOut.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring stdout: %1").arg(stdOut));
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(failureReason) {
|
|
|
*failureReason = lastFailure;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<double> nmCalculationAutoFitPSO::readSurrogateScores(const QString& scorePath) const
|
|
|
{
|
|
|
QVector<double> scores(m_swarm.size(), std::numeric_limits<double>::quiet_NaN());
|
|
|
QFile file(scorePath);
|
|
|
|
|
|
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
DEBUG_OUT(QString("Failed to open surrogate score file: %1").arg(scorePath));
|
|
|
return scores;
|
|
|
}
|
|
|
|
|
|
QTextStream in(&file);
|
|
|
bool firstLine = true;
|
|
|
|
|
|
while(!in.atEnd()) {
|
|
|
QString line = in.readLine().trimmed();
|
|
|
|
|
|
if(line.isEmpty()) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if(firstLine) {
|
|
|
firstLine = false;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
QStringList parts = line.split(",");
|
|
|
|
|
|
if(parts.size() < 5) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
bool idOk = false;
|
|
|
bool scoreOk = false;
|
|
|
int particleId = parts[0].toInt(&idOk);
|
|
|
double score = parts[1].toDouble(&scoreOk);
|
|
|
bool success = parts[4].trimmed() == "1";
|
|
|
|
|
|
if(idOk && scoreOk && success && particleId >= 0 && particleId < scores.size()) {
|
|
|
scores[particleId] = score;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return scores;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isSurrogateRunContextSupported(QString* reason) const
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
if(!dataManager) {
|
|
|
if(reason) *reason = "data manager is unavailable";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(dataManager->getGridType() != NM_Grid_PEBI) {
|
|
|
if(reason) *reason = "surrogate model is trained only for PEBI grid cases";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
|
|
|
|
|
|
if(!pTargetWell) {
|
|
|
if(reason) *reason = QString("target well '%1' is missing").arg(m_targetWellName);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pTargetWell->getWellType() != NM_WELL_MODEL::Vertical_Well) {
|
|
|
if(reason) *reason = "surrogate model is currently limited to vertical-well cases";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int validCalculationWells = 0;
|
|
|
QVector<QPair<NM_WELL_MODEL, QString> > calculationWells = dataManager->getCalculationWells();
|
|
|
|
|
|
for(int i = 0; i < calculationWells.size(); ++i) {
|
|
|
if(calculationWells[i].first != NM_WELL_MODEL::Unknow_Well) {
|
|
|
validCalculationWells++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(validCalculationWells != 1) {
|
|
|
if(reason) *reason = QString("surrogate model expects one active calculation well, got %1").arg(validCalculationWells);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!dataManager->getFaultDataList().isEmpty() ||
|
|
|
!dataManager->getFractureDataList().isEmpty() ||
|
|
|
!dataManager->getRegionDataList().isEmpty()) {
|
|
|
if(reason) *reason = "surrogate model is not enabled for changed fault/fracture/region geometry";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<int> allowedParamIndices;
|
|
|
allowedParamIndices << 0 << 1 << 2 << 3 << 5;
|
|
|
|
|
|
for(int i = 0; i < m_enabledParamIndices.size(); ++i) {
|
|
|
if(!allowedParamIndices.contains(m_enabledParamIndices[i])) {
|
|
|
if(reason) {
|
|
|
QStringList names = traceParameterNames();
|
|
|
int idx = m_enabledParamIndices[i];
|
|
|
QString name = (idx >= 0 && idx < names.size()) ? names[idx] : QString::number(idx);
|
|
|
*reason = QString("parameter '%1' is outside surrogate input set").arg(name);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
|
|
|
double cf = reservoirData.getCf().getValue().toDouble();
|
|
|
|
|
|
if(!isNearRelative(cf, kSurrogateCfFixed, kSurrogateCfRelTol)) {
|
|
|
if(reason) {
|
|
|
*reason = QString("Cf=%1 is outside surrogate fixed-Cf domain").arg(cf, 0, 'g', 10);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> flowPoints = pTargetWell->getFlowPoints();
|
|
|
|
|
|
if(!flowPoints.isEmpty() && qAbs(flowPoints[0].x()) <= 1e-12 && qAbs(flowPoints[0].y()) <= 1e-12) {
|
|
|
flowPoints.remove(0);
|
|
|
}
|
|
|
|
|
|
if(flowPoints.size() < 2) {
|
|
|
if(reason) *reason = "flow schedule is too short for surrogate screening";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int productionSections = flowPoints.size() - 1;
|
|
|
|
|
|
if(productionSections < kSurrogateMinProdSections || productionSections > kSurrogateMaxProdSections) {
|
|
|
if(reason) {
|
|
|
*reason = QString("production section count %1 is outside surrogate range [%2,%3]")
|
|
|
.arg(productionSections)
|
|
|
.arg(kSurrogateMinProdSections)
|
|
|
.arg(kSurrogateMaxProdSections);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double productionTime = 0.0;
|
|
|
|
|
|
for(int i = 0; i < productionSections; ++i) {
|
|
|
double dt = flowPoints[i].x();
|
|
|
double q = flowPoints[i].y();
|
|
|
|
|
|
if(!isFiniteNumber(dt) || dt <= 0.0 || !isInClosedRange(q, kSurrogateQMin, kSurrogateQMax)) {
|
|
|
if(reason) {
|
|
|
*reason = QString("production schedule section %1 is outside surrogate dt/q domain").arg(i + 1);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(i > 0) {
|
|
|
double previousQ = flowPoints[i - 1].y();
|
|
|
double stepRatio = qMax(q, previousQ) / qMax(1e-12, qMin(q, previousQ));
|
|
|
|
|
|
if(stepRatio > kSurrogateMaxRateStepRatio) {
|
|
|
if(reason) {
|
|
|
*reason = QString("rate step ratio %1 exceeds surrogate domain").arg(stepRatio, 0, 'g', 10);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
productionTime += dt;
|
|
|
}
|
|
|
|
|
|
if(!getSurrogateStage().contains("v2_q")) {
|
|
|
double endStartRatio = std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
|
if(isStrongDecliningProductionSchedule(&endStartRatio)) {
|
|
|
if(reason) {
|
|
|
*reason = QString("strongly decreasing production schedule ratio %1 is outside surrogate domain")
|
|
|
.arg(endStartRatio, 0, 'g', 10);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!isInClosedRange(productionTime, kSurrogateProdTimeMin, kSurrogateProdTimeMax)) {
|
|
|
if(reason) {
|
|
|
*reason = QString("production total time %1 is outside surrogate range").arg(productionTime, 0, 'g', 10);
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double shutinTime = flowPoints.last().x();
|
|
|
double shutinRate = flowPoints.last().y();
|
|
|
|
|
|
if(!isInClosedRange(shutinTime, kSurrogateShutinTimeMin, kSurrogateShutinTimeMax) ||
|
|
|
qAbs(shutinRate) > kSurrogateQZeroThreshold) {
|
|
|
if(reason) *reason = "last flow section is not a trained shut-in section";
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(pTargetWell->getIndexF() != flowPoints.size()) {
|
|
|
if(reason) {
|
|
|
*reason = QString("target section index %1 is not the last section %2")
|
|
|
.arg(pTargetWell->getIndexF())
|
|
|
.arg(flowPoints.size());
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isSurrogateCandidateInDomain(const QVector<double>& selectedParameters, QString* reason) const
|
|
|
{
|
|
|
QVector<double> params = buildTraceParameterVector(selectedParameters);
|
|
|
double k = params.size() > 0 ? params[0] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double skin = params.size() > 1 ? params[1] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double wellboreC = params.size() > 2 ? params[2] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double phi = params.size() > 3 ? params[3] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double h = params.size() > 5 ? params[5] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double cf = params.size() > 7 ? params[7] : std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
|
if(!isInClosedRange(k, kSurrogateKMin, kSurrogateKMax)) {
|
|
|
if(reason) *reason = QString("k=%1").arg(k, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!isInClosedRange(skin, kSurrogateSkinMin, kSurrogateSkinMax)) {
|
|
|
if(reason) *reason = QString("skin=%1").arg(skin, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!isInClosedRange(wellboreC, kSurrogateWellboreCMin, kSurrogateWellboreCMax)) {
|
|
|
if(reason) *reason = QString("wellboreC=%1").arg(wellboreC, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!isInClosedRange(phi, kSurrogatePhiMin, kSurrogatePhiMax)) {
|
|
|
if(reason) *reason = QString("phi=%1").arg(phi, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!isInClosedRange(h, kSurrogateHMin, kSurrogateHMax)) {
|
|
|
if(reason) *reason = QString("h=%1").arg(h, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(!isNearRelative(cf, kSurrogateCfFixed, kSurrogateCfRelTol)) {
|
|
|
if(reason) *reason = QString("Cf=%1").arg(cf, 0, 'g', 10);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::forceSolverByFallbackGate(const QVector<double>& selectedParameters) const
|
|
|
{
|
|
|
QVector<double> params = buildTraceParameterVector(selectedParameters);
|
|
|
double k = params.size() > 0 ? params[0] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double skin = params.size() > 1 ? params[1] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double wellboreC = params.size() > 2 ? params[2] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double phi = params.size() > 3 ? params[3] : std::numeric_limits<double>::quiet_NaN();
|
|
|
double h = params.size() > 5 ? params[5] : std::numeric_limits<double>::quiet_NaN();
|
|
|
|
|
|
if(!isFiniteNumber(k) || !isFiniteNumber(skin) || !isFiniteNumber(wellboreC) || !isFiniteNumber(phi) || !isFiniteNumber(h)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(k <= 1e-3 && wellboreC > 0.5) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(phi < 0.03 && h < 4.0) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(k < 0.1 && h >= 50.0) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if(qAbs(skin) > 6.0) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::isStrongDecliningProductionSchedule(double* endStartRatio) const
|
|
|
{
|
|
|
if(endStartRatio) {
|
|
|
*endStartRatio = std::numeric_limits<double>::quiet_NaN();
|
|
|
}
|
|
|
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
if(!dataManager) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
nmDataWellBase* pTargetWell = dataManager->findWellByName(m_targetWellName);
|
|
|
|
|
|
if(!pTargetWell) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QVector<QPointF> flowPoints = pTargetWell->getFlowPoints();
|
|
|
|
|
|
if(!flowPoints.isEmpty() && qAbs(flowPoints[0].x()) <= 1e-12 && qAbs(flowPoints[0].y()) <= 1e-12) {
|
|
|
flowPoints.remove(0);
|
|
|
}
|
|
|
|
|
|
if(flowPoints.size() < 2) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
int productionSections = flowPoints.size() - 1;
|
|
|
bool productionRatesNonIncreasing = true;
|
|
|
bool productionRatesHaveMeaningfulDrop = false;
|
|
|
|
|
|
for(int i = 0; i < productionSections; ++i) {
|
|
|
double q = flowPoints[i].y();
|
|
|
|
|
|
if(!isFiniteNumber(q)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(i > 0) {
|
|
|
double previousQ = flowPoints[i - 1].y();
|
|
|
|
|
|
if(q > previousQ * (1.0 + kSurrogateRateTrendRelTol)) {
|
|
|
productionRatesNonIncreasing = false;
|
|
|
}
|
|
|
|
|
|
if(q < previousQ * (1.0 - kSurrogateRateTrendRelTol)) {
|
|
|
productionRatesHaveMeaningfulDrop = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!productionRatesNonIncreasing || !productionRatesHaveMeaningfulDrop) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
double firstRate = flowPoints.first().y();
|
|
|
double lastProductionRate = flowPoints[productionSections - 1].y();
|
|
|
double ratio = lastProductionRate / qMax(1e-12, firstRate);
|
|
|
|
|
|
if(endStartRatio) {
|
|
|
*endStartRatio = ratio;
|
|
|
}
|
|
|
|
|
|
return ratio < kSurrogateStrongDeclineEndStartRatio;
|
|
|
}
|
|
|
|
|
|
QVector<bool> nmCalculationAutoFitPSO::buildSurrogateEvaluationMask()
|
|
|
{
|
|
|
QVector<bool> evaluateMask(m_swarm.size(), true);
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
m_swarm[i].surrogateObjective = std::numeric_limits<double>::quiet_NaN();
|
|
|
m_swarm[i].selectedForSolver = true;
|
|
|
m_swarm[i].selectedByAudit = false;
|
|
|
m_swarm[i].screeningDecision = "full_solver";
|
|
|
}
|
|
|
|
|
|
if(!isSurrogateScreeningEnabled()) {
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
QString contextReason;
|
|
|
|
|
|
if(!isSurrogateRunContextSupported(&contextReason)) {
|
|
|
m_surrogateContextBlockedIterationCount++;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
m_swarm[i].screeningDecision = "context_gate_full_solver";
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr("Surrogate screening disabled for this run context: %1").arg(contextReason));
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
// Keep the early PSO generations fully evaluated so every particle gets a true pbest seed.
|
|
|
if(m_currentIteration < getSurrogateWarmupIterations()) {
|
|
|
m_surrogateWarmupIterationCount++;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
m_swarm[i].screeningDecision = "warmup_full_solver";
|
|
|
}
|
|
|
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
if(getSurrogateFullSolverInterval() > 0 &&
|
|
|
m_currentIteration > 0 &&
|
|
|
((m_currentIteration + 1) % getSurrogateFullSolverInterval()) == 0) {
|
|
|
m_surrogatePeriodicAuditIterationCount++;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
m_swarm[i].screeningDecision = "periodic_full_solver";
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr("Surrogate audit iteration %1: running full solver for all particles")
|
|
|
.arg(m_currentIteration + 1));
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
QString mlRoot = getMlRootPath();
|
|
|
QDir tempDir(QDir(mlRoot).absoluteFilePath("data/temp"));
|
|
|
|
|
|
if(!tempDir.exists()) {
|
|
|
QDir().mkpath(tempDir.absolutePath());
|
|
|
}
|
|
|
|
|
|
QString candidatePath = tempDir.absoluteFilePath(QString("pso_screen_candidates_%1_gen%2.csv")
|
|
|
.arg(m_traceRunId)
|
|
|
.arg(m_currentIteration));
|
|
|
QString scorePath = tempDir.absoluteFilePath(QString("pso_screen_scores_%1_gen%2.csv")
|
|
|
.arg(m_traceRunId)
|
|
|
.arg(m_currentIteration));
|
|
|
|
|
|
QString scoringFailureReason;
|
|
|
|
|
|
if(!writeSurrogateCandidateCsv(candidatePath) || !runSurrogateScoringProcess(candidatePath, scorePath, &scoringFailureReason)) {
|
|
|
emit logMessageGenerated(tr("Surrogate screening unavailable; falling back to full solver for iteration %1")
|
|
|
.arg(m_currentIteration + 1));
|
|
|
|
|
|
if(!scoringFailureReason.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring failure detail: %1").arg(scoringFailureReason));
|
|
|
}
|
|
|
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
QVector<double> scores = readSurrogateScores(scorePath);
|
|
|
QVector<int> finiteScoreIndices;
|
|
|
|
|
|
for(int i = 0; i < scores.size(); ++i) {
|
|
|
m_swarm[i].surrogateObjective = scores[i];
|
|
|
|
|
|
if(isFiniteNumber(scores[i])) {
|
|
|
finiteScoreIndices.append(i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(finiteScoreIndices.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("Surrogate scoring returned no valid scores; falling back to full solver"));
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
std::sort(finiteScoreIndices.begin(), finiteScoreIndices.end(), [&](int a, int b) {
|
|
|
return scores[a] < scores[b];
|
|
|
});
|
|
|
|
|
|
evaluateMask.fill(false);
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
m_swarm[i].selectedForSolver = false;
|
|
|
m_swarm[i].screeningDecision = "screened_out";
|
|
|
}
|
|
|
|
|
|
int keepN = qMax(1, qCeil(m_swarm.size() * getSurrogateKeepFraction()));
|
|
|
keepN = qMin(keepN, finiteScoreIndices.size());
|
|
|
|
|
|
for(int rank = 0; rank < keepN; ++rank) {
|
|
|
int idx = finiteScoreIndices[rank];
|
|
|
evaluateMask[idx] = true;
|
|
|
m_swarm[idx].selectedForSolver = true;
|
|
|
m_swarm[idx].screeningDecision = "surrogate_topk";
|
|
|
}
|
|
|
|
|
|
QVector<QPair<double, int> > auditCandidates;
|
|
|
|
|
|
for(int i = 0; i < finiteScoreIndices.size(); ++i) {
|
|
|
int idx = finiteScoreIndices[i];
|
|
|
|
|
|
if(!evaluateMask[idx]) {
|
|
|
auditCandidates.append(qMakePair(deterministicAuditRandom01(m_psoRandomSeed, m_currentIteration, idx), idx));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
std::sort(auditCandidates.begin(), auditCandidates.end(), [](const QPair<double, int>& a, const QPair<double, int>& b) {
|
|
|
return a.first < b.first;
|
|
|
});
|
|
|
|
|
|
int auditN = qMin(auditCandidates.size(), qCeil(m_swarm.size() * getSurrogateAuditFraction()));
|
|
|
|
|
|
for(int i = 0; i < auditN; ++i) {
|
|
|
int idx = auditCandidates[i].second;
|
|
|
evaluateMask[idx] = true;
|
|
|
m_swarm[idx].selectedForSolver = true;
|
|
|
m_swarm[idx].selectedByAudit = true;
|
|
|
m_swarm[idx].screeningDecision = "random_audit";
|
|
|
}
|
|
|
|
|
|
int fallbackCount = 0;
|
|
|
int domainFallbackCount = 0;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
QString domainReason;
|
|
|
bool domainFallback = !isSurrogateCandidateInDomain(m_swarm[i].position, &domainReason);
|
|
|
bool fallback = !isFiniteNumber(scores[i]) ||
|
|
|
m_swarm[i].bestFitness >= 1e9 ||
|
|
|
forceSolverByFallbackGate(m_swarm[i].position) ||
|
|
|
domainFallback;
|
|
|
|
|
|
if(fallback) {
|
|
|
if(!evaluateMask[i]) {
|
|
|
fallbackCount++;
|
|
|
}
|
|
|
|
|
|
if(domainFallback) {
|
|
|
domainFallbackCount++;
|
|
|
}
|
|
|
|
|
|
evaluateMask[i] = true;
|
|
|
m_swarm[i].selectedForSolver = true;
|
|
|
m_swarm[i].screeningDecision = domainFallback ? "domain_gate" : "fallback_gate";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int selectedCount = 0;
|
|
|
|
|
|
for(int i = 0; i < evaluateMask.size(); ++i) {
|
|
|
if(evaluateMask[i]) {
|
|
|
selectedCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int minSolverN = qMax(1, qCeil(m_swarm.size() * getSurrogateMinSolverFraction()));
|
|
|
|
|
|
if(selectedCount < minSolverN) {
|
|
|
QVector<QPair<double, int> > extraCandidates;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
if(!evaluateMask[i] && isFiniteNumber(scores[i])) {
|
|
|
extraCandidates.append(qMakePair(scores[i], i));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
std::sort(extraCandidates.begin(), extraCandidates.end(), [](const QPair<double, int>& a, const QPair<double, int>& b) {
|
|
|
return a.first < b.first;
|
|
|
});
|
|
|
|
|
|
for(int i = 0; i < extraCandidates.size() && selectedCount < minSolverN; ++i) {
|
|
|
int idx = extraCandidates[i].second;
|
|
|
evaluateMask[idx] = true;
|
|
|
m_swarm[idx].selectedForSolver = true;
|
|
|
m_swarm[idx].screeningDecision = "min_solver_floor";
|
|
|
selectedCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr("Surrogate screening iteration %1: selected %2/%3 particles (keep=%4, audit=%5, min_solver=%6, fallback=%7, domain=%8)")
|
|
|
.arg(m_currentIteration + 1)
|
|
|
.arg(selectedCount)
|
|
|
.arg(m_swarm.size())
|
|
|
.arg(getSurrogateKeepFraction(), 0, 'f', 2)
|
|
|
.arg(getSurrogateAuditFraction(), 0, 'f', 2)
|
|
|
.arg(getSurrogateMinSolverFraction(), 0, 'f', 2)
|
|
|
.arg(fallbackCount)
|
|
|
.arg(domainFallbackCount));
|
|
|
|
|
|
m_surrogateActiveIterationCount++;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
const QString& decision = m_swarm[i].screeningDecision;
|
|
|
|
|
|
if(m_swarm[i].selectedForSolver) {
|
|
|
m_surrogateSelectedParticleCount++;
|
|
|
}
|
|
|
|
|
|
if(decision == "screened_out") {
|
|
|
m_surrogateScreenedParticleCount++;
|
|
|
} else if(decision == "surrogate_topk") {
|
|
|
m_surrogateTopKParticleCount++;
|
|
|
} else if(decision == "random_audit") {
|
|
|
m_surrogateAuditParticleCount++;
|
|
|
} else if(decision == "fallback_gate") {
|
|
|
m_surrogateFallbackParticleCount++;
|
|
|
} else if(decision == "domain_gate") {
|
|
|
m_surrogateDomainParticleCount++;
|
|
|
} else if(decision == "min_solver_floor") {
|
|
|
m_surrogateMinFloorParticleCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return evaluateMask;
|
|
|
}
|
|
|
|
|
|
//QString nmCalculationAutoFitPSO::getTargetWellName() const
|
|
|
//{
|
|
|
// return m_targetWellName;
|
|
|
//}
|
|
|
|
|
|
// ==================== 数据加载方法 ====================
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::loadAllConfigFromDataManager()
|
|
|
{
|
|
|
try {
|
|
|
loadOptimizationConfig();
|
|
|
loadParameterBounds();
|
|
|
extractUserInitialValues(); // 直接提取初始值,无需条件判断
|
|
|
return true;
|
|
|
} catch(...) {
|
|
|
m_lastError = "Failed to load configuration from data manager";
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::loadOptimizationConfig()
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
|
|
|
|
|
|
// 加载基本配置
|
|
|
m_maxIterations = fittingData.getIterationCount().getValue().toInt();
|
|
|
m_targetError = fittingData.getErrorTolerance().getValue().toDouble();
|
|
|
m_surrogateScreeningEnabled = fittingData.getSurrogateScreeningEnabled();
|
|
|
|
|
|
// 设置PSO默认参数
|
|
|
m_swarmSize = 20;
|
|
|
m_inertiaWeight = 0.4;
|
|
|
m_cognitiveParam = 2.0;
|
|
|
m_socialParam = 1.2;
|
|
|
|
|
|
DEBUG_OUT(QString("Loaded optimization config: iterations=%1, error=%2, surrogate=%3")
|
|
|
.arg(m_maxIterations).arg(m_targetError).arg(m_surrogateScreeningEnabled ? "enabled" : "disabled"));
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::loadParameterBounds()
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy();
|
|
|
|
|
|
// 获取参数选择状态
|
|
|
m_parameterSelected.resize(11);
|
|
|
m_parameterSelected[0] = fittingData.getPermeabilitySelected();
|
|
|
m_parameterSelected[1] = fittingData.getSkinSelected();
|
|
|
m_parameterSelected[2] = fittingData.getWellboreStorageSelected();
|
|
|
m_parameterSelected[3] = fittingData.getPorositySelected();
|
|
|
m_parameterSelected[4] = fittingData.getInitialPressureSelected();
|
|
|
m_parameterSelected[5] = fittingData.getThicknessSelected();
|
|
|
m_parameterSelected[6] = fittingData.getCtSelected();
|
|
|
m_parameterSelected[7] = fittingData.getCfSelected();
|
|
|
m_parameterSelected[8] = fittingData.getSoiSelected();
|
|
|
m_parameterSelected[9] = fittingData.getSwiSelected();
|
|
|
m_parameterSelected[10] = fittingData.getSgiSelected();
|
|
|
|
|
|
// 获取参数边界
|
|
|
m_parameterLower.resize(11);
|
|
|
m_parameterUpper.resize(11);
|
|
|
|
|
|
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.getInitialPressureMin().getValue().toDouble();
|
|
|
m_parameterUpper[4] = fittingData.getInitialPressureMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[5] = fittingData.getThicknessMin().getValue().toDouble();
|
|
|
m_parameterUpper[5] = fittingData.getThicknessMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[6] = fittingData.getCtMin().getValue().toDouble();
|
|
|
m_parameterUpper[6] = fittingData.getCtMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[7] = fittingData.getCfMin().getValue().toDouble();
|
|
|
m_parameterUpper[7] = fittingData.getCfMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[8] = fittingData.getSoiMin().getValue().toDouble();
|
|
|
m_parameterUpper[8] = fittingData.getSoiMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[9] = fittingData.getSwiMin().getValue().toDouble();
|
|
|
m_parameterUpper[9] = fittingData.getSwiMax().getValue().toDouble();
|
|
|
|
|
|
m_parameterLower[10] = fittingData.getSgiMin().getValue().toDouble();
|
|
|
m_parameterUpper[10] = fittingData.getSgiMax().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()));
|
|
|
}
|
|
|
|
|
|
// ==================== PSO算法核心方法 ====================
|
|
|
bool nmCalculationAutoFitPSO::startAutoFitting()
|
|
|
{
|
|
|
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 {
|
|
|
// 从数据管理器加载所有配置
|
|
|
if(!loadAllConfigFromDataManager()) {
|
|
|
emit logMessageGenerated(tr("ERROR: Failed to load configuration from data manager"));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(m_simulationMode) {
|
|
|
DEBUG_OUT("=== SIMULATION MODE ACTIVATED ===");
|
|
|
runSimulatedFitting();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
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<double> savedInitialValues = m_initialValues;
|
|
|
|
|
|
// 重置状态
|
|
|
resetOptimizer();
|
|
|
m_isRunning = true;
|
|
|
m_shouldStop = false;
|
|
|
m_isPaused = false;
|
|
|
m_currentIteration = 0;
|
|
|
m_consecutiveFailures = 0;
|
|
|
|
|
|
if(kUseFixedPsoSeed) {
|
|
|
m_psoRandomSeed = kFixedPsoSeed;
|
|
|
} else {
|
|
|
QTime seedTime = QTime::currentTime();
|
|
|
m_psoRandomSeed = static_cast<unsigned int>(QDateTime::currentDateTime().toTime_t());
|
|
|
m_psoRandomSeed ^= static_cast<unsigned int>((seedTime.hour() << 22) ^ (seedTime.minute() << 16) ^ (seedTime.second() << 10) ^ seedTime.msec());
|
|
|
}
|
|
|
|
|
|
if(m_psoRandomSeed == 0u) {
|
|
|
m_psoRandomSeed = 1u;
|
|
|
}
|
|
|
|
|
|
qsrand(m_psoRandomSeed);
|
|
|
m_consecutiveFailedIterations = 0; // 重置连续失败迭代计数
|
|
|
|
|
|
// 精英保护:评估用户初始解
|
|
|
m_initialValues = savedInitialValues;
|
|
|
initializeTraceFile();
|
|
|
|
|
|
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 evaluateError: m_userInitialSolution size = %1").arg(m_userInitialSolution.size()));
|
|
|
|
|
|
if(m_userInitialSolution.isEmpty()) {
|
|
|
emit logMessageGenerated(tr("ERROR: m_userInitialSolution is empty!"));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
QTime initialEvalTimer;
|
|
|
initialEvalTimer.start();
|
|
|
m_userInitialFitness = evaluateFitness(m_userInitialSolution);
|
|
|
int initialEvalElapsedMs = initialEvalTimer.elapsed();
|
|
|
|
|
|
if(m_userInitialFitness < 1e9) {
|
|
|
m_hasValidUserSolution = true;
|
|
|
m_globalBestFitness = m_userInitialFitness;
|
|
|
m_globalBestPosition = m_userInitialSolution;
|
|
|
m_userInitialLogLogData = m_lastEvaluatedLogLogData;
|
|
|
m_globalBestLogLogData = m_userInitialLogLogData;
|
|
|
|
|
|
emit logMessageGenerated(tr("Initial solution evaluation successful"));
|
|
|
emit logMessageGenerated(tr("Initial Error: %1").arg(m_userInitialFitness, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Elite protection activated - initial solution will be preserved if no significant improvement found"));
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, 0, m_globalBestFitness);
|
|
|
} else {
|
|
|
m_hasValidUserSolution = false;
|
|
|
emit logMessageGenerated(tr("Initial solution evaluation failed - starting with random initialization"));
|
|
|
}
|
|
|
|
|
|
writeTraceRow(-1,
|
|
|
-1,
|
|
|
"initial_solution",
|
|
|
m_userInitialSolution,
|
|
|
m_userInitialFitness,
|
|
|
m_userInitialFitness < 1e9,
|
|
|
initialEvalElapsedMs,
|
|
|
std::numeric_limits<double>::quiet_NaN(),
|
|
|
"initial_solution",
|
|
|
m_hasValidUserSolution ? m_userInitialSolution : QVector<double>(),
|
|
|
m_userInitialFitness);
|
|
|
} catch(...) {
|
|
|
m_hasValidUserSolution = false;
|
|
|
emit logMessageGenerated(tr("Exception during initial solution evaluation"));
|
|
|
}
|
|
|
|
|
|
// 恢复初始值供粒子群初始化使用
|
|
|
m_initialValues = savedInitialValues;
|
|
|
}
|
|
|
|
|
|
// 初始化粒子群
|
|
|
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()));
|
|
|
|
|
|
// PSO主循环
|
|
|
emit logMessageGenerated(tr("=== Starting PSO Main Loop ==="));
|
|
|
|
|
|
for(m_currentIteration = 0; m_currentIteration < m_maxIterations && !m_shouldStop; ++m_currentIteration) {
|
|
|
// 每次迭代都输出标题,或者只在重要迭代输出详细信息
|
|
|
bool shouldOutputDetail = (m_currentIteration % qMax(1, m_maxIterations / 10) == 0) ||
|
|
|
(m_currentIteration < 5) ||
|
|
|
(m_currentIteration >= m_maxIterations - 2); // 总是输出前5次和最后2次的详细信息
|
|
|
|
|
|
// 每次迭代都输出标题
|
|
|
emit logMessageGenerated(tr("--- Iteration %1/%2 ---").arg(m_currentIteration + 1).arg(m_maxIterations));
|
|
|
|
|
|
// 只在特定迭代输出详细统计信息
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current best error: %1").arg(m_globalBestFitness, 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;
|
|
|
}
|
|
|
|
|
|
int currentIterationSuccessful = 0;
|
|
|
int currentIterationTotal = 0;
|
|
|
int currentIterationFailed = 0;
|
|
|
int currentIterationSkipped = 0;
|
|
|
QVector<bool> evaluateMask = buildSurrogateEvaluationMask();
|
|
|
|
|
|
for(int i = 0; i < m_swarmSize && !m_shouldStop; ++i) {
|
|
|
try {
|
|
|
if(i < evaluateMask.size() && !evaluateMask[i]) {
|
|
|
AutoFitParticle& particle = m_swarm[i];
|
|
|
particle.evaluatedThisIteration = false;
|
|
|
particle.lastEvaluationSuccess = false;
|
|
|
particle.lastEvaluationElapsedMs = -1;
|
|
|
particle.selectedForSolver = false;
|
|
|
|
|
|
if(particle.screeningDecision.isEmpty()) {
|
|
|
particle.screeningDecision = "screened_out";
|
|
|
}
|
|
|
|
|
|
currentIterationSkipped++;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
double previousFitness = m_swarm[i].fitness;
|
|
|
updateParticle(i);
|
|
|
currentIterationTotal++;
|
|
|
|
|
|
if(m_swarm[i].fitness < 1e9) {
|
|
|
currentIterationSuccessful++;
|
|
|
|
|
|
if(shouldOutputDetail && m_swarm[i].fitness < previousFitness) {
|
|
|
// 计算改进幅度
|
|
|
double improvement = (previousFitness - m_swarm[i].fitness) / qMax(1e-10, previousFitness);
|
|
|
|
|
|
if(improvement > 0.01) { // 1%以上改进才输出
|
|
|
emit logMessageGenerated(tr(" Particle %1 improved: %2 -> %3 (%4% improvement)")
|
|
|
.arg(i + 1).arg(previousFitness, 0, 'e', 3).arg(m_swarm[i].fitness, 0, 'e', 3)
|
|
|
.arg(improvement * 100, 0, 'f', 2));
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
currentIterationFailed++;
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Particle %1: evaluation failed").arg(i + 1));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 每评估2个粒子处理一次事件
|
|
|
if(i % 2 == 0) {
|
|
|
QApplication::processEvents();
|
|
|
}
|
|
|
|
|
|
} catch(const std::exception& e) {
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Particle %1: Exception: %2").arg(i + 1).arg(e.what()));
|
|
|
}
|
|
|
|
|
|
m_swarm[i].fitness = 1e10;
|
|
|
currentIterationTotal++;
|
|
|
currentIterationFailed++;
|
|
|
m_totalEvaluations++;
|
|
|
} catch(...) {
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Particle %1: Unknown exception").arg(i + 1));
|
|
|
}
|
|
|
|
|
|
m_swarm[i].fitness = 1e10;
|
|
|
currentIterationTotal++;
|
|
|
currentIterationFailed++;
|
|
|
m_totalEvaluations++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(m_shouldStop) break;
|
|
|
|
|
|
double currentSuccessRate = currentIterationTotal > 0 ?
|
|
|
(double)currentIterationSuccessful / currentIterationTotal : 0.0;
|
|
|
|
|
|
// 更新连续失败迭代计数
|
|
|
if(currentIterationSuccessful == 0) {
|
|
|
m_consecutiveFailedIterations++;
|
|
|
emit logMessageGenerated(tr("WARNING: No successful evaluations in iteration %1 (consecutive failures: %2)")
|
|
|
.arg(m_currentIteration + 1).arg(m_consecutiveFailedIterations));
|
|
|
} else {
|
|
|
m_consecutiveFailedIterations = 0; // 重置连续失败计数
|
|
|
}
|
|
|
|
|
|
// 输出当前迭代统计
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current iteration stats: %1 successful, %2 failed, %3 skipped out of %4 particles (solver success rate: %5%)")
|
|
|
.arg(currentIterationSuccessful).arg(currentIterationFailed)
|
|
|
.arg(currentIterationSkipped)
|
|
|
.arg(m_swarmSize).arg(currentSuccessRate * 100, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
// 检查是否需要停止优化
|
|
|
if(m_consecutiveFailedIterations >= m_maxConsecutiveFailures) {
|
|
|
m_lastError = QString("Too many consecutive failed iterations (%1)").arg(m_consecutiveFailedIterations);
|
|
|
emit logMessageGenerated(tr("ERROR: Too many consecutive failed iterations (%1/%2) - stopping optimization")
|
|
|
.arg(m_consecutiveFailedIterations).arg(m_maxConsecutiveFailures));
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 警告低成功率但不立即停止
|
|
|
if(currentSuccessRate < 0.5 && m_currentIteration > 3) {
|
|
|
emit logMessageGenerated(tr("WARNING: Low success rate (%1%) in iteration %2, but continuing optimization")
|
|
|
.arg(currentSuccessRate * 100, 0, 'f', 1).arg(m_currentIteration + 1));
|
|
|
}
|
|
|
|
|
|
// 更新全局最优
|
|
|
//double previousGlobalBest = m_globalBestFitness;
|
|
|
updateGlobalBest();
|
|
|
writeIterationTraceRows();
|
|
|
|
|
|
// 自适应参数更新
|
|
|
adaptiveParameterUpdate(m_currentIteration);
|
|
|
|
|
|
emit progressUpdated(m_currentIteration + 1, m_globalBestFitness);
|
|
|
|
|
|
// 记录收敛历史
|
|
|
m_convergenceHistory.append(m_globalBestFitness);
|
|
|
|
|
|
// 每5次迭代输出收敛状态
|
|
|
if((m_currentIteration + 1) % 5 == 0) {
|
|
|
double recentImprovement = 0.0;
|
|
|
|
|
|
if(m_convergenceHistory.size() >= 5) {
|
|
|
double oldBest = m_convergenceHistory[m_convergenceHistory.size() - 5];
|
|
|
recentImprovement = (oldBest - m_globalBestFitness) / qMax(1e-10, oldBest);
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(tr(" Convergence check: recent improvement = %1% over last 5 iterations")
|
|
|
.arg(recentImprovement * 100, 0, 'f', 4));
|
|
|
}
|
|
|
|
|
|
// 更新收敛指标
|
|
|
updateConvergenceMetrics();
|
|
|
|
|
|
// 智能收敛判断
|
|
|
StopReasonPSO stopReason = analyzeOptimizationStatus();
|
|
|
|
|
|
if(stopReason == PSO_TARGET_ACHIEVED) {
|
|
|
emit logMessageGenerated(tr("=== TARGET ACHIEVED ==="));
|
|
|
emit logMessageGenerated(tr("Target error achieved! Current error: %1 < Target: %2")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_targetError, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Optimization completed successfully after %1 iterations")
|
|
|
.arg(m_currentIteration + 1));
|
|
|
break;
|
|
|
} else if(stopReason == PSO_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 iterations")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1));
|
|
|
emit logMessageGenerated(tr("Solution quality: %1 (1.0 = target achieved)")
|
|
|
.arg(m_targetError / qMax(1e-10, m_globalBestFitness), 0, 'f', 3));
|
|
|
break;
|
|
|
} else if(stopReason == PSO_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 iterations")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_currentIteration + 1));
|
|
|
emit logMessageGenerated(tr("Suggestion: Try restarting with different parameters or larger search space"));
|
|
|
break;
|
|
|
} else if(stopReason == PSO_CONSECUTIVE_FAILURES) {
|
|
|
emit logMessageGenerated(tr("=== CONSECUTIVE FAILURES ==="));
|
|
|
emit logMessageGenerated(tr("Too many consecutive failed iterations (%1/%2)")
|
|
|
.arg(m_consecutiveFailedIterations).arg(m_maxConsecutiveFailures));
|
|
|
break;
|
|
|
} else if(stopReason == PSO_CONTINUE_OPTIMIZATION) {
|
|
|
// 继续优化,每10次迭代输出一次状态
|
|
|
if(m_currentIteration > 0 && m_currentIteration % 10 == 0) {
|
|
|
double diversity = calculateSwarmDiversity();
|
|
|
double avgVel = calculateAverageVelocity();
|
|
|
double stagnation = calculateParticleStagnationRate();
|
|
|
|
|
|
emit logMessageGenerated(tr(" Optimization status: diversity=%1, avgVel=%2, stagnation=%3%")
|
|
|
.arg(diversity, 0, 'f', 4).arg(avgVel, 0, 'f', 4).arg(stagnation * 100, 0, 'f', 1));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新粒子位置和速度
|
|
|
updateVelocityAndPosition();
|
|
|
|
|
|
// 输出迭代结束标记
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Iteration %1 completed - Current best: %2")
|
|
|
.arg(m_currentIteration + 1).arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
}
|
|
|
|
|
|
// 强制处理事件,保持界面响应
|
|
|
QApplication::processEvents();
|
|
|
|
|
|
// 在迭代间稍作停顿,减少系统负载
|
|
|
if(m_currentIteration % 3 == 2) {
|
|
|
msleep(200);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 最终结果验证和保护
|
|
|
validateAndProtectFinalResult();
|
|
|
|
|
|
} catch(const std::exception& e) {
|
|
|
m_lastError = QString(tr("Critical exception in PSO main loop: %1")).arg(e.what());
|
|
|
emit logMessageGenerated(tr("CRITICAL ERROR: %1").arg(e.what()));
|
|
|
closeTraceFile();
|
|
|
cleanupTemporaryDirectory();
|
|
|
m_isRunning = false;
|
|
|
emit fittingFinished(false, m_lastError);
|
|
|
return false;
|
|
|
} catch(...) {
|
|
|
m_lastError = QString(tr("Unknown critical exception in PSO main loop"));
|
|
|
emit logMessageGenerated(tr("CRITICAL ERROR: Unknown exception in PSO main loop"));
|
|
|
closeTraceFile();
|
|
|
cleanupTemporaryDirectory();
|
|
|
m_isRunning = false;
|
|
|
emit fittingFinished(false, m_lastError);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
m_isRunning = false;
|
|
|
|
|
|
// 应用最终参数
|
|
|
if(!m_globalBestPosition.isEmpty()) {
|
|
|
try {
|
|
|
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
|
|
|
applyParametersToDataManager(m_globalBestPosition);
|
|
|
saveOptimizationResult();
|
|
|
|
|
|
// 输出最终优化结果
|
|
|
emit logMessageGenerated(tr("=== Optimization Results ==="));
|
|
|
emit logMessageGenerated(tr("Final error: %1").arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Total iterations: %1").arg(m_currentIteration + 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_globalBestPosition.size(); ++i) {
|
|
|
finalParams += QString("[%1]=%2 ").arg(i).arg(m_globalBestPosition[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;
|
|
|
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 ==="));
|
|
|
} 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 ==="));
|
|
|
} 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 ==="));
|
|
|
} 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 ==="));
|
|
|
} 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 ==="));
|
|
|
} 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 ==="));
|
|
|
} 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 ==="));
|
|
|
}
|
|
|
|
|
|
emitRunSummary(success, finalReason);
|
|
|
|
|
|
// 先发送最终进度更新,确保进度条达到100%
|
|
|
emit progressUpdated(m_maxIterations, m_globalBestFitness);
|
|
|
|
|
|
// 确保界面更新完成
|
|
|
QApplication::processEvents();
|
|
|
|
|
|
// 短暂延迟,让进度条动画完成
|
|
|
msleep(200);
|
|
|
|
|
|
// 再次确保界面更新
|
|
|
QApplication::processEvents();
|
|
|
|
|
|
// 现在才发送完成信号,这会触发消息框
|
|
|
closeTraceFile();
|
|
|
emit fittingFinished(success, message);
|
|
|
|
|
|
cleanupTemporaryDirectory();
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::extractUserInitialValues()
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
|
|
|
//QVector<nmDataWellBase*> 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.getInitialPressure().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 5: // 储层厚度
|
|
|
initialValue = reservoirData.getThickness().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 6: // 综合压缩系数
|
|
|
initialValue = reservoirData.getCt().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 7: // 岩石压缩系数
|
|
|
initialValue = reservoirData.getCf().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 8: // 初始含油饱和度
|
|
|
initialValue = reservoirData.getSoi().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 9: // 初始含水饱和度
|
|
|
initialValue = reservoirData.getSwi().getValue().toDouble();
|
|
|
break;
|
|
|
|
|
|
case 10: // 初始含气饱和度
|
|
|
initialValue = reservoirData.getSgi().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 nmCalculationAutoFitPSO::initializeSwarm()
|
|
|
{
|
|
|
int dimensions = getEnabledParameterCount();
|
|
|
|
|
|
if(dimensions == 0) return;
|
|
|
|
|
|
m_swarm.resize(m_swarmSize);
|
|
|
m_globalBestPosition.resize(dimensions);
|
|
|
|
|
|
bool hasValidInitials = !m_initialValues.isEmpty() && m_initialValues.size() >= dimensions;
|
|
|
|
|
|
// 引导搜索比例
|
|
|
//int guidedCount = hasValidInitials ? qMax(2, (m_swarmSize * 3) / 4) : qMax(1, m_swarmSize / 3);
|
|
|
int guidedCount = hasValidInitials ? qMax(2, m_swarmSize / 2) : qMax(1, m_swarmSize / 3);
|
|
|
|
|
|
DEBUG_OUT(QString("Enhanced swarm initialization: %1 particles, %2 guided, %3 random")
|
|
|
.arg(m_swarmSize).arg(guidedCount).arg(m_swarmSize - guidedCount));
|
|
|
|
|
|
for(int i = 0; i < m_swarmSize; ++i) {
|
|
|
AutoFitParticle& particle = m_swarm[i];
|
|
|
particle.position.resize(dimensions);
|
|
|
particle.velocity.resize(dimensions);
|
|
|
particle.bestPosition.resize(dimensions);
|
|
|
particle.bestFitness = 1e10;
|
|
|
particle.fitness = 1e10;
|
|
|
particle.evaluatedThisIteration = false;
|
|
|
particle.lastEvaluationSuccess = false;
|
|
|
particle.lastEvaluationElapsedMs = -1;
|
|
|
particle.surrogateObjective = 1e10;
|
|
|
particle.selectedForSolver = true;
|
|
|
particle.selectedByAudit = false;
|
|
|
particle.screeningDecision = "full_solver";
|
|
|
|
|
|
for(int j = 0; j < dimensions; ++j) {
|
|
|
int paramIndex = m_enabledParamIndices[j];
|
|
|
double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex];
|
|
|
|
|
|
if(i == 0 && hasValidInitials) {
|
|
|
// 第一个粒子:完全使用用户初始值
|
|
|
particle.position[j] = m_initialValues[j];
|
|
|
|
|
|
} else if(i < guidedCount && hasValidInitials) {
|
|
|
// 搜索策略
|
|
|
double searchRadius;
|
|
|
|
|
|
if(i <= guidedCount / 3) {
|
|
|
searchRadius = range * 0.03; // 3%范围内精细搜索
|
|
|
} else if(i <= guidedCount * 2 / 3) {
|
|
|
searchRadius = range * 0.08; // 8%范围内搜索
|
|
|
} else {
|
|
|
searchRadius = range * 0.15; // 15%范围内搜索
|
|
|
}
|
|
|
|
|
|
double offset = (random01() - 0.5) * searchRadius;
|
|
|
particle.position[j] = m_initialValues[j] + offset;
|
|
|
|
|
|
} else {
|
|
|
// 少数粒子进行全局搜索
|
|
|
particle.position[j] = m_parameterLower[paramIndex] + random01() * range;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 边界检查
|
|
|
clampToLimits(particle.position);
|
|
|
|
|
|
// 速度初始化
|
|
|
for(int j = 0; j < dimensions; ++j) {
|
|
|
int paramIndex = m_enabledParamIndices[j];
|
|
|
double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex];
|
|
|
|
|
|
double velocityFactor;
|
|
|
|
|
|
if(i < guidedCount) {
|
|
|
velocityFactor = VELOCITY_LIMIT_FACTOR * 0.15; // 更小的速度
|
|
|
} else {
|
|
|
velocityFactor = VELOCITY_LIMIT_FACTOR * 0.8; // 适中速度
|
|
|
}
|
|
|
|
|
|
particle.velocity[j] = (random01() - 0.5) * range * velocityFactor;
|
|
|
}
|
|
|
|
|
|
particle.bestPosition = particle.position;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateParticle(int particleIndex)
|
|
|
{
|
|
|
if(particleIndex < 0 || particleIndex >= m_swarm.size()) return;
|
|
|
|
|
|
AutoFitParticle& particle = m_swarm[particleIndex];
|
|
|
|
|
|
// 评估适应度
|
|
|
particle.evaluatedThisIteration = false;
|
|
|
particle.lastEvaluationSuccess = false;
|
|
|
particle.lastEvaluationElapsedMs = -1;
|
|
|
particle.selectedForSolver = true;
|
|
|
|
|
|
if(particle.screeningDecision.isEmpty()) {
|
|
|
particle.screeningDecision = "full_solver";
|
|
|
}
|
|
|
|
|
|
QTime evalTimer;
|
|
|
evalTimer.start();
|
|
|
particle.fitness = evaluateFitness(particle.position);
|
|
|
particle.currentLogLogData = m_lastEvaluatedLogLogData;
|
|
|
particle.lastEvaluationElapsedMs = evalTimer.elapsed();
|
|
|
particle.evaluatedThisIteration = true;
|
|
|
particle.lastEvaluationSuccess = (particle.fitness < 1e9);
|
|
|
m_totalEvaluations++;
|
|
|
|
|
|
if(particle.fitness < 1e9) {
|
|
|
m_successfulEvaluations++;
|
|
|
}
|
|
|
|
|
|
// Update particle best.
|
|
|
if(particle.fitness < particle.bestFitness) {
|
|
|
particle.bestFitness = particle.fitness;
|
|
|
particle.bestPosition = particle.position;
|
|
|
particle.bestLogLogData = particle.currentLogLogData;
|
|
|
DEBUG_OUT(QString("Particle %1 improved: error = %2")
|
|
|
.arg(particleIndex).arg(particle.fitness, 0, 'e', 4));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateGlobalBest()
|
|
|
{
|
|
|
// 保存上一轮的全局最优,用于后续自适应参数调整等
|
|
|
m_previousBestFitness = m_globalBestFitness;
|
|
|
bool globalBestUpdated = false;
|
|
|
|
|
|
// 遍历所有粒子,寻找比当前 global best 更好的个体最优
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
const AutoFitParticle& particle = m_swarm[i];
|
|
|
|
|
|
// 只要个体最优比当前全局最优小,就认为是更好的解
|
|
|
if(particle.bestFitness < m_globalBestFitness) {
|
|
|
|
|
|
double improvement = m_globalBestFitness - particle.bestFitness;
|
|
|
double relativeImprovement =
|
|
|
improvement / qMax(1e-10, qAbs(m_globalBestFitness));
|
|
|
|
|
|
// 区分显著改进和微小改进,但无论如何都会更新全局最优
|
|
|
if(relativeImprovement > m_improvementThreshold) {
|
|
|
DEBUG_OUT(QString(tr("Global best updated with %1% improvement: %2"))
|
|
|
.arg(relativeImprovement * 100.0, 0, 'f', 3)
|
|
|
.arg(particle.bestFitness, 0, 'e', 4));
|
|
|
} else {
|
|
|
DEBUG_OUT(QString(tr("Global best updated (minor improvement %1% < %2%) to %3"))
|
|
|
.arg(relativeImprovement * 100.0, 0, 'f', 3)
|
|
|
.arg(m_improvementThreshold * 100.0, 0, 'f', 2)
|
|
|
.arg(particle.bestFitness, 0, 'e', 4));
|
|
|
}
|
|
|
|
|
|
// 无论相对改进是否超过阈值,都要更新全局最优
|
|
|
m_globalBestFitness = particle.bestFitness;
|
|
|
m_globalBestPosition = particle.bestPosition;
|
|
|
m_globalBestLogLogData = particle.bestLogLogData;
|
|
|
globalBestUpdated = true;
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 只有在本轮没有找到任何更好的解时才考虑恢复用户初始解
|
|
|
if(!globalBestUpdated && m_hasValidUserSolution && m_userInitialFitness < m_globalBestFitness) {
|
|
|
|
|
|
double improvement = m_globalBestFitness - m_userInitialFitness;
|
|
|
double relativeImprovement =
|
|
|
improvement / qMax(1e-10, qAbs(m_globalBestFitness));
|
|
|
|
|
|
DEBUG_OUT("Elite protection: Restoring user initial solution as global best");
|
|
|
DEBUG_OUT(QString("Elite solution is better than current best by %1%")
|
|
|
.arg(relativeImprovement * 100.0, 0, 'f', 3));
|
|
|
|
|
|
m_globalBestFitness = m_userInitialFitness;
|
|
|
m_globalBestPosition = m_userInitialSolution;
|
|
|
m_globalBestLogLogData = m_userInitialLogLogData;
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//void nmCalculationAutoFitPSO::updateGlobalBest()
|
|
|
//{
|
|
|
// m_previousBestFitness = m_globalBestFitness;
|
|
|
// bool globalBestUpdated = false;
|
|
|
//
|
|
|
// for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
// const AutoFitParticle& particle = m_swarm[i];
|
|
|
//
|
|
|
// if(particle.bestFitness < m_globalBestFitness) {
|
|
|
// // 只有显著改进时才更新
|
|
|
// double improvement = (m_globalBestFitness - particle.bestFitness);
|
|
|
// double relativeImprovement = improvement / qMax(1e-10, qAbs(m_globalBestFitness));
|
|
|
//
|
|
|
// if(relativeImprovement > m_improvementThreshold) {
|
|
|
// m_globalBestFitness = particle.bestFitness;
|
|
|
// m_globalBestPosition = particle.bestPosition;
|
|
|
// globalBestUpdated = true;
|
|
|
//
|
|
|
// DEBUG_OUT(QString("Global best updated with %1% improvement: %2")
|
|
|
// .arg(relativeImprovement * 100, 0, 'f', 3)
|
|
|
// .arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
// } else {
|
|
|
// DEBUG_OUT(QString("Rejecting minor improvement: %1% (threshold: %2%)")
|
|
|
// .arg(relativeImprovement * 100, 0, 'f', 4)
|
|
|
// .arg(m_improvementThreshold * 100, 0, 'f', 2));
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
//
|
|
|
// // 在没有找到更好解时才检查
|
|
|
// if(!globalBestUpdated && m_hasValidUserSolution && m_userInitialFitness < m_globalBestFitness) {
|
|
|
// DEBUG_OUT("Elite protection: No significant improvement found, checking initial solution");
|
|
|
//
|
|
|
// // 检查初始解是否仍然是最优的
|
|
|
// double improvement = m_globalBestFitness - m_userInitialFitness;
|
|
|
// double relativeImprovement = improvement / qMax(1e-10, qAbs(m_globalBestFitness));
|
|
|
//
|
|
|
// if(relativeImprovement > m_improvementThreshold * 0.5) { // 使用更宽松的阈值
|
|
|
// DEBUG_OUT("Elite protection: Restoring user initial solution");
|
|
|
// m_globalBestFitness = m_userInitialFitness;
|
|
|
// m_globalBestPosition = m_userInitialSolution;
|
|
|
// }
|
|
|
// }
|
|
|
//}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateVelocityAndPosition()
|
|
|
{
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
AutoFitParticle& particle = m_swarm[i];
|
|
|
|
|
|
for(int j = 0; j < particle.position.size(); ++j) {
|
|
|
// PSO速度更新公式
|
|
|
double r1 = random01();
|
|
|
double r2 = random01();
|
|
|
|
|
|
particle.velocity[j] = m_inertiaWeight * particle.velocity[j] +
|
|
|
m_cognitiveParam * r1 * (particle.bestPosition[j] - particle.position[j]) +
|
|
|
m_socialParam * r2 * (m_globalBestPosition[j] - particle.position[j]);
|
|
|
|
|
|
// 速度限制
|
|
|
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]));
|
|
|
|
|
|
// 更新位置
|
|
|
particle.position[j] += particle.velocity[j];
|
|
|
}
|
|
|
|
|
|
// 边界约束
|
|
|
clampToLimits(particle.position);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//double nmCalculationAutoFitPSO::evaluateFitness(const QVector<double>& parameters)
|
|
|
//{
|
|
|
// const QString funcName = QString("evaluateError[%1]").arg(m_currentIteration);
|
|
|
// static int callCount = 0;
|
|
|
// callCount++;
|
|
|
//
|
|
|
// try {
|
|
|
// DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters")
|
|
|
// .arg(funcName).arg(callCount).arg(parameters.size()));
|
|
|
//
|
|
|
// // 打印参数值用于对比
|
|
|
// QString paramStr = "Parameters: ";
|
|
|
//
|
|
|
// for(int i = 0; i < parameters.size(); ++i) {
|
|
|
// paramStr += QString("[%1]=%2 ").arg(i).arg(parameters[i], 0, 'f', 6);
|
|
|
// }
|
|
|
//
|
|
|
// DEBUG_OUT(QString("%1: %2").arg(funcName).arg(paramStr));
|
|
|
//
|
|
|
// // 1. 参数有效性检查
|
|
|
// if(!validateParameters(parameters)) {
|
|
|
// 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(parameters);
|
|
|
// 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<QVector<double> > 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<QVector<double> > resultLogLogData;
|
|
|
//
|
|
|
// try {
|
|
|
// //wells = dataManager->getWellDataList(); // 重新获取,确保是最新的
|
|
|
// 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 nmCalculationAutoFitPSO::evaluateFitness(const QVector<double>& parameters)
|
|
|
{
|
|
|
const QString funcName = QString("evaluateError[%1]").arg(m_currentIteration);
|
|
|
static int callCount = 0;
|
|
|
callCount++;
|
|
|
m_lastEvaluatedLogLogData.clear();
|
|
|
|
|
|
try {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters")
|
|
|
.arg(funcName).arg(callCount).arg(parameters.size()));
|
|
|
|
|
|
// 打印参数值
|
|
|
QString paramStr = "Parameters: ";
|
|
|
|
|
|
for(int i = 0; i < parameters.size(); ++i) {
|
|
|
paramStr += QString("[%1]=%2 ").arg(i).arg(parameters[i], 0, 'f', 6);
|
|
|
}
|
|
|
|
|
|
DEBUG_OUT(QString("%1: %2").arg(funcName).arg(paramStr));
|
|
|
|
|
|
// 1. 参数有效性检查 - 添加详细失败原因
|
|
|
if(!validateParameters(parameters)) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - VALIDATION FAILED").arg(funcName).arg(callCount));
|
|
|
|
|
|
// 详细检查每个参数
|
|
|
for(int i = 0; i < parameters.size(); ++i) {
|
|
|
if(!isFiniteNumber(parameters[i])) {
|
|
|
DEBUG_OUT(QString(" -> Param[%1] is NOT finite: %2").arg(i).arg(parameters[i]));
|
|
|
}
|
|
|
|
|
|
if(i < m_enabledParamIndices.size()) {
|
|
|
int paramIndex = m_enabledParamIndices[i];
|
|
|
|
|
|
if(paramIndex >= 0 && paramIndex < m_parameterLower.size()) {
|
|
|
double lower = m_parameterLower[paramIndex];
|
|
|
double upper = m_parameterUpper[paramIndex];
|
|
|
|
|
|
if(parameters[i] < lower) {
|
|
|
DEBUG_OUT(QString(" -> Param[%1]=%2 < lower bound %3")
|
|
|
.arg(i).arg(parameters[i]).arg(lower));
|
|
|
}
|
|
|
|
|
|
if(parameters[i] > upper) {
|
|
|
DEBUG_OUT(QString(" -> Param[%1]=%2 > upper bound %3")
|
|
|
.arg(i).arg(parameters[i]).arg(upper));
|
|
|
}
|
|
|
|
|
|
// 检查危险值
|
|
|
switch(paramIndex) {
|
|
|
case 0: // 渗透率
|
|
|
if(parameters[i] <= 1e-6) {
|
|
|
DEBUG_OUT(QString(" -> REJECTED: Permeability too small: %1").arg(parameters[i]));
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2: // 井筒储集系数
|
|
|
if(parameters[i] <= 1e-8) {
|
|
|
DEBUG_OUT(QString(" -> REJECTED: Wellbore storage too small: %1").arg(parameters[i]));
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 3: // 孔隙度
|
|
|
if(parameters[i] <= 1e-4 || parameters[i] >= 0.95) {
|
|
|
DEBUG_OUT(QString(" -> REJECTED: Unrealistic porosity: %1").arg(parameters[i]));
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Parameters validated OK").arg(funcName).arg(callCount));
|
|
|
|
|
|
// 2. 数据管理器检查
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
if(!dataManager) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - DataManager is NULL").arg(funcName).arg(callCount));
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - DataManager OK").arg(funcName).arg(callCount));
|
|
|
|
|
|
// 3. 应用参数
|
|
|
try {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Applying parameters...").arg(funcName).arg(callCount));
|
|
|
applyParametersToDataManager(parameters);
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
// 4. 运行求解器
|
|
|
QVector<QVector<double>> solverResult;
|
|
|
const int maxRetries = 2;
|
|
|
bool solverSuccess = false;
|
|
|
|
|
|
for(int retry = 0; retry <= maxRetries; ++retry) {
|
|
|
if(m_shouldStop) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - User stop requested").arg(funcName).arg(callCount));
|
|
|
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...").arg(funcName).arg(callCount));
|
|
|
msleep(1000);
|
|
|
}
|
|
|
|
|
|
solverResult = runSolver();
|
|
|
|
|
|
// 详细检查求解器结果
|
|
|
if(solverResult.isEmpty()) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Solver returned EMPTY result").arg(funcName).arg(callCount));
|
|
|
} else {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Solver returned %3 arrays")
|
|
|
.arg(funcName).arg(callCount).arg(solverResult.size()));
|
|
|
|
|
|
for(int i = 0; i < solverResult.size(); ++i) {
|
|
|
DEBUG_OUT(QString(" -> Array[%1] size: %2").arg(i).arg(solverResult[i].size()));
|
|
|
}
|
|
|
|
|
|
if(validateSolverResult(solverResult)) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Solver result VALIDATED on attempt %3")
|
|
|
.arg(funcName).arg(callCount).arg(retry + 1));
|
|
|
solverSuccess = true;
|
|
|
break;
|
|
|
} else {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Solver result VALIDATION FAILED on attempt %3")
|
|
|
.arg(funcName).arg(callCount).arg(retry + 1));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
} 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()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!solverSuccess) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - ALL SOLVER ATTEMPTS FAILED").arg(funcName).arg(callCount));
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
// 5. 获取LogLog数据
|
|
|
QVector<QVector<double>> resultLogLogData;
|
|
|
|
|
|
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));
|
|
|
|
|
|
// 详细输出LogLog数据问题
|
|
|
if(resultLogLogData.size() < 3) {
|
|
|
DEBUG_OUT(QString(" -> LogLog arrays count: %1 (need 3)")
|
|
|
.arg(resultLogLogData.size()));
|
|
|
} else {
|
|
|
DEBUG_OUT(QString(" -> LogLog array sizes: X=%1, Y1=%2, Y2=%3")
|
|
|
.arg(resultLogLogData[0].size())
|
|
|
.arg(resultLogLogData[1].size())
|
|
|
.arg(resultLogLogData[2].size()));
|
|
|
|
|
|
// 检查数据有效性
|
|
|
for(int i = 0; i < qMin(5, resultLogLogData[0].size()); ++i) {
|
|
|
if(!isFiniteNumber(resultLogLogData[0][i]) ||
|
|
|
!isFiniteNumber(resultLogLogData[1][i]) ||
|
|
|
!isFiniteNumber(resultLogLogData[2][i])) {
|
|
|
DEBUG_OUT(QString(" -> Invalid data at index %1: X=%2, Y1=%3, Y2=%4")
|
|
|
.arg(i)
|
|
|
.arg(resultLogLogData[0][i])
|
|
|
.arg(resultLogLogData[1][i])
|
|
|
.arg(resultLogLogData[2][i]));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - LogLog data validated, size: %3")
|
|
|
.arg(funcName).arg(callCount).arg(resultLogLogData[0].size()));
|
|
|
|
|
|
} catch(const std::exception& e) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Error getting LogLog data: %3")
|
|
|
.arg(funcName).arg(callCount).arg(e.what()));
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
// 6. 计算误差
|
|
|
double error;
|
|
|
|
|
|
try {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Calculating error...")
|
|
|
.arg(funcName).arg(callCount));
|
|
|
|
|
|
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 - SUCCESS! Error = %3")
|
|
|
.arg(funcName).arg(callCount).arg(error, 0, 'e', 6));
|
|
|
m_lastEvaluatedLogLogData = resultLogLogData;
|
|
|
|
|
|
} catch(const std::exception& e) {
|
|
|
DEBUG_OUT(QString("%1: Call #%2 - Error calculation FAILED: %3")
|
|
|
.arg(funcName).arg(callCount).arg(e.what()));
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ==================== 参数应用方法 ====================
|
|
|
|
|
|
void nmCalculationAutoFitPSO::applyParametersToDataManager(const QVector<double>& parameters)
|
|
|
{
|
|
|
if(parameters.size() != getEnabledParameterCount()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
updateReservoirParameters(parameters);
|
|
|
updateWellParameters(parameters);
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateReservoirParameters(const QVector<double>& parameters)
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
nmDataReservoir reservoirData = dataManager->getReservoirDataCopy();
|
|
|
|
|
|
int paramIndex = 0;
|
|
|
|
|
|
for(int i = 0; i < m_parameterSelected.size() && i < 11; ++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.getInitialPressure().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 5: // 储层厚度
|
|
|
reservoirData.getThickness().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 6: // 综合压缩系数
|
|
|
reservoirData.getCt().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 7: // 岩石压缩系数
|
|
|
reservoirData.getCf().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 8: // 初始含油饱和度
|
|
|
reservoirData.getSoi().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 9: // 初始含水饱和度
|
|
|
reservoirData.getSwi().setValue(value);
|
|
|
break;
|
|
|
|
|
|
case 10: // 初始含气饱和度
|
|
|
reservoirData.getSgi().setValue(value);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
paramIndex++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 更新数据管理器
|
|
|
dataManager->updateReservoirData(reservoirData);
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateWellParameters(const QVector<double>& parameters)
|
|
|
{
|
|
|
nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
// 获取井列表,更新第一口井的参数
|
|
|
//QVector<nmDataWellBase*> 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 < 9; ++i) {
|
|
|
if(m_parameterSelected[i] && paramIndex < parameters.size()) {
|
|
|
double value = parameters[paramIndex];
|
|
|
|
|
|
switch(i) {
|
|
|
case 1: { // 表皮系数
|
|
|
nmDataPerforation* perf = pWell->getPerforation(0);
|
|
|
|
|
|
if(perf) {
|
|
|
nmDataAttribute skinAttr = perf->getSkin();
|
|
|
skinAttr.setValue(value);
|
|
|
perf->setSkin(skinAttr);
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case 2: { // 井筒储集系数
|
|
|
nmDataAttribute wellboreAttr = pWell->getWellboreStorage();
|
|
|
wellboreAttr.setValue(value);
|
|
|
pWell->setWellboreStorage(wellboreAttr);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
paramIndex++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 根据井类型更新到数据管理器
|
|
|
updateWellToDataManager(pWell);
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::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<nmDataVerticalWell*>(pWell);
|
|
|
|
|
|
if(pVerticalWell) {
|
|
|
QVector<nmDataVerticalWell> wells;
|
|
|
wells.append(*pVerticalWell);
|
|
|
dataManager->updateVerticalWells(wells);
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case NM_WELL_MODEL::Vertical_Fractured_Well: {
|
|
|
nmDataVerticalFracturedWell* pVFracturedWell = dynamic_cast<nmDataVerticalFracturedWell*>(pWell);
|
|
|
|
|
|
if(pVFracturedWell) {
|
|
|
QVector<nmDataVerticalFracturedWell> wells;
|
|
|
wells.append(*pVFracturedWell);
|
|
|
dataManager->updateVerticalFracturedWells(wells);
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case NM_WELL_MODEL::Horizontal_Fractured_Well: {
|
|
|
nmDataHorizontalFracturedWell* pHFracturedWell = dynamic_cast<nmDataHorizontalFracturedWell*>(pWell);
|
|
|
|
|
|
if(pHFracturedWell) {
|
|
|
QVector<nmDataHorizontalFracturedWell> wells;
|
|
|
wells.append(*pHFracturedWell);
|
|
|
dataManager->updateHorizontalFracturedWells(wells);
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ==================== 求解器相关方法 ====================
|
|
|
|
|
|
QVector<QVector<double>> nmCalculationAutoFitPSO::runSolver()
|
|
|
{
|
|
|
//return runSolverExe();
|
|
|
return runSolverDll();
|
|
|
}
|
|
|
|
|
|
// ==================== 数据处理方法 ====================
|
|
|
QVector<QPointF> nmCalculationAutoFitPSO::interpolateData(
|
|
|
const QVector<QPointF>& source, const QVector<double>& targetX) const
|
|
|
{
|
|
|
QVector<QPointF> result;
|
|
|
|
|
|
if(source.isEmpty() || targetX.isEmpty()) {
|
|
|
DEBUG_OUT("Warning: Empty data in interpolation");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// 数据清理和验证合并
|
|
|
QVector<QPointF> 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<QPointF> 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;
|
|
|
|
|
|
// 插值逻辑
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
// ==================== 算法辅助方法 ====================
|
|
|
|
|
|
void nmCalculationAutoFitPSO::adaptiveParameterUpdate(int iteration)
|
|
|
{
|
|
|
double progress = static_cast<double>(iteration) / m_maxIterations;
|
|
|
// 更快降低惯性权重,增强局部搜索
|
|
|
m_inertiaWeight = 0.4 - 0.25 * progress;
|
|
|
|
|
|
if(iteration > 0) {
|
|
|
double improvement = m_previousBestFitness - m_globalBestFitness;
|
|
|
double relativeImprovement = improvement / qMax(1e-10, qAbs(m_previousBestFitness));
|
|
|
|
|
|
if(relativeImprovement < 1e-5) {
|
|
|
// 改进很小时,增强局部搜索
|
|
|
m_cognitiveParam = qMin(2.5, m_cognitiveParam * 1.05);
|
|
|
m_socialParam = qMax(0.8, m_socialParam * 0.95);
|
|
|
} else {
|
|
|
// 有明显改进时,保持平衡
|
|
|
m_cognitiveParam = qMax(1.5, qMin(2.2, m_cognitiveParam));
|
|
|
m_socialParam = qMax(1.0, qMin(1.5, m_socialParam));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::saveOptimizationResult()
|
|
|
{
|
|
|
DEBUG_OUT(QString("Optimization result: error=%1, evaluations=%2/%3")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4)
|
|
|
.arg(m_successfulEvaluations)
|
|
|
.arg(m_totalEvaluations));
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::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_globalBestFitness;
|
|
|
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_globalBestFitness = initialFitness;
|
|
|
m_globalBestPosition = m_userInitialSolution;
|
|
|
m_globalBestLogLogData = m_userInitialLogLogData;
|
|
|
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"));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ==================== 工具方法 ====================
|
|
|
|
|
|
double nmCalculationAutoFitPSO::random01() const
|
|
|
{
|
|
|
return static_cast<double>(qrand()) / RAND_MAX;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::clampToLimits(QVector<double>& 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]));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int nmCalculationAutoFitPSO::getEnabledParameterCount() const
|
|
|
{
|
|
|
int count = 0;
|
|
|
|
|
|
for(int i = 0; i < m_parameterSelected.size(); ++i) {
|
|
|
if(m_parameterSelected[i]) count++;
|
|
|
}
|
|
|
|
|
|
return count;
|
|
|
}
|
|
|
|
|
|
// ==================== 验证和处理方法 ====================
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::validateParameters(const QVector<double>& 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 6: // 综合压缩系数:必须大于零
|
|
|
if(value <= 1e-8) {
|
|
|
DEBUG_OUT(QString("Rejecting near-zero total compressibility: %1").arg(value));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::validateLogLogData(const QVector<QVector<double>>& 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 nmCalculationAutoFitPSO::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;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::validateSolverResult(const QVector<QVector<double>>& 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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 检查X值单调性
|
|
|
bool isMonotonic = true;
|
|
|
|
|
|
for(int i = 1; i < result[0].size(); ++i) {
|
|
|
if(result[0][i] <= result[0][i - 1]) {
|
|
|
isMonotonic = false;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if(!isMonotonic) {
|
|
|
DEBUG_OUT("X values are not monotonically increasing");
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::calculateCurveError(
|
|
|
const QVector<QPointF>& curve1, const QVector<QPointF>& 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 nmCalculationAutoFitPSO::calculateLogLogCurveError(
|
|
|
const QVector<QVector<double> >& target,
|
|
|
const QVector<QVector<double> >& 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<double> 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<QPointF> 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<QPointF> alignedTarget1 = interpolateData(targetCurve1, commonX);
|
|
|
QVector<QPointF> alignedTarget2 = interpolateData(targetCurve2, commonX);
|
|
|
|
|
|
// 插值结果曲线
|
|
|
QVector<QPointF> 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<QPointF> alignedResult1 = interpolateData(resultCurve1, commonX);
|
|
|
QVector<QPointF> 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 nmCalculationAutoFitPSO::calculateWeightedPointError(
|
|
|
double target, double result, double timeWeight) const
|
|
|
{
|
|
|
if(!isFiniteNumber(target) || !isFiniteNumber(result)) {
|
|
|
return 1e10;
|
|
|
}
|
|
|
|
|
|
// 对数空间的相对误差
|
|
|
double logTarget = qLn(qMax(1e-10, qAbs(target)));
|
|
|
double logResult = qLn(qMax(1e-10, qAbs(result)));
|
|
|
double logError = qAbs(logTarget - logResult);
|
|
|
|
|
|
// 线性空间的相对误差
|
|
|
double yMax = qMax(qAbs(target), qAbs(result));
|
|
|
double relativeError = qAbs(target - result) / qMax(1e-10, yMax);
|
|
|
|
|
|
// 组合误差:对数误差占70%,线性误差占30%
|
|
|
return 0.7 * logError + 0.3 * relativeError;
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double>> nmCalculationAutoFitPSO::runSolverDll()
|
|
|
{
|
|
|
DEBUG_OUT("SOLVER DLL START");
|
|
|
|
|
|
if(m_evaluationInProgress > 0) {
|
|
|
DEBUG_OUT("DLL Solver already running, skipping");
|
|
|
return QVector<QVector<double>>();
|
|
|
}
|
|
|
|
|
|
++m_evaluationInProgress;
|
|
|
QVector<QVector<double>> 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 = 3600000; // 1h超时
|
|
|
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<nmDataWellBase*> 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<QVector<double>> pressureResult = pTargetWell->getResultPressure();
|
|
|
QVector<QVector<double>> 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<double> 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<QVector<double>> nmCalculationAutoFitPSO::runSolverExe()
|
|
|
//{
|
|
|
// DEBUG_OUT("SOLVER EXE START");
|
|
|
//
|
|
|
// if(m_evaluationInProgress > 0) {
|
|
|
// DEBUG_OUT("EXE Solver already running, skipping");
|
|
|
// return QVector<QVector<double>>();
|
|
|
// }
|
|
|
//
|
|
|
// ++m_evaluationInProgress;
|
|
|
// QVector<QVector<double>> 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<nmDataWellBase*> wells = dataManager->getWellDataList();
|
|
|
//
|
|
|
// if(wells.isEmpty()) {
|
|
|
// DEBUG_OUT("No wells found in data manager after EXE execution");
|
|
|
// delete exeTask;
|
|
|
// --m_evaluationInProgress;
|
|
|
// return result;
|
|
|
// }
|
|
|
//
|
|
|
// // 验证结果数据的时间戳或唯一性
|
|
|
// QVector<QVector<double>> pressureResult = wells[0]->getResultPressure();
|
|
|
// QVector<QVector<double>> 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<double> 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;
|
|
|
//}
|
|
|
|
|
|
StopReasonPSO nmCalculationAutoFitPSO::analyzeOptimizationStatus()
|
|
|
{
|
|
|
// 1. 检查用户停止
|
|
|
if(m_shouldStop) {
|
|
|
return PSO_USER_STOPPED;
|
|
|
}
|
|
|
|
|
|
// 2. 检查连续失败
|
|
|
if(m_consecutiveFailedIterations >= m_maxConsecutiveFailures) {
|
|
|
return PSO_CONSECUTIVE_FAILURES;
|
|
|
}
|
|
|
|
|
|
// 3. 检查是否达到目标精度
|
|
|
if(m_globalBestFitness < m_targetError) {
|
|
|
return PSO_TARGET_ACHIEVED;
|
|
|
}
|
|
|
|
|
|
// 4. 检查是否达到最大迭代数
|
|
|
if(m_currentIteration >= m_maxIterations - 1) {
|
|
|
return PSO_MAX_ITERATIONS;
|
|
|
}
|
|
|
|
|
|
// 5. 需要足够的历史数据才能判断收敛
|
|
|
if(m_convergenceHistory.size() < m_localOptimumWindow) {
|
|
|
return PSO_CONTINUE_OPTIMIZATION;
|
|
|
}
|
|
|
|
|
|
// 6. 检查真正的收敛
|
|
|
if(m_convergenceHistory.size() >= m_trueConvergenceWindow && checkTrueConvergence()) {
|
|
|
return PSO_TRUE_CONVERGENCE;
|
|
|
}
|
|
|
|
|
|
// 7. 检查局部最优陷阱
|
|
|
if(checkLocalOptimumTrap()) {
|
|
|
return PSO_LOCAL_OPTIMUM;
|
|
|
}
|
|
|
|
|
|
return PSO_CONTINUE_OPTIMIZATION;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::checkTrueConvergence() const
|
|
|
{
|
|
|
if(m_convergenceHistory.size() < m_trueConvergenceWindow) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 1. 检查解质量 - 如果已经接近目标,小改进可能是真收敛
|
|
|
bool nearTarget = (m_globalBestFitness < 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 = calculateSwarmDiversity();
|
|
|
bool lowDiversity = (currentDiversity < m_diversityThreshold);
|
|
|
|
|
|
// 4. 检查速度收敛 - 粒子应该几乎停止移动
|
|
|
double avgVelocity = calculateAverageVelocity();
|
|
|
bool velocityConverged = (avgVelocity < m_velocityConvergenceThreshold);
|
|
|
|
|
|
// 5. 检查长期改进趋势
|
|
|
double longTermImprovement = calculateLongTermImprovement(m_trueConvergenceWindow);
|
|
|
bool minimalLongTermImprovement = (longTermImprovement < 1e-4); // 0.01%
|
|
|
|
|
|
// 6. 检查粒子停滞情况
|
|
|
double stagnationRate = calculateParticleStagnationRate();
|
|
|
bool mostParticlesConverged = (stagnationRate > 0.8); // 80%以上粒子收敛
|
|
|
|
|
|
// 真收敛的判断条件
|
|
|
bool isConverged = nearTarget ||
|
|
|
(stableError && lowDiversity && velocityConverged &&
|
|
|
minimalLongTermImprovement && mostParticlesConverged);
|
|
|
|
|
|
if(isConverged) {
|
|
|
DEBUG_OUT("=== TRUE CONVERGENCE ANALYSIS ===");
|
|
|
DEBUG_OUT(QString("nearTarget=%1 (error=%2, target*factor=%3)")
|
|
|
.arg(nearTarget).arg(m_globalBestFitness, 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("velocityConverged=%1 (avgVel=%2)")
|
|
|
.arg(velocityConverged).arg(avgVelocity, 0, 'f', 6));
|
|
|
DEBUG_OUT(QString("longTermImprovement=%1%, stagnationRate=%2%")
|
|
|
.arg(longTermImprovement * 100, 0, 'f', 4).arg(stagnationRate * 100, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
return isConverged;
|
|
|
}
|
|
|
|
|
|
bool nmCalculationAutoFitPSO::checkLocalOptimumTrap() const
|
|
|
{
|
|
|
if(m_convergenceHistory.size() < m_localOptimumWindow) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 1. 检查解质量 - 如果距离目标还很远,停滞就可能是局部最优
|
|
|
bool farFromTarget = (m_globalBestFitness > m_targetError * m_farTargetFactor);
|
|
|
|
|
|
// 2. 检查短期改进 - 近期改进非常小
|
|
|
double shortTermImprovement = calculateLongTermImprovement(m_localOptimumWindow);
|
|
|
bool poorShortTermImprovement = (shortTermImprovement < 1e-5); // 0.001%
|
|
|
|
|
|
// 3. 检查粒子多样性 - 可能过早聚集或无效分散
|
|
|
double currentDiversity = calculateSwarmDiversity();
|
|
|
bool problematicDiversity = (currentDiversity < m_diversityThreshold * 0.1) ||
|
|
|
(currentDiversity > m_diversityThreshold * 5.0);
|
|
|
|
|
|
// 4. 检查粒子停滞率
|
|
|
double stagnationRate = calculateParticleStagnationRate();
|
|
|
bool mostParticlesStagnant = (stagnationRate > 0.9); // 90%以上停滞但解质量差
|
|
|
|
|
|
// 5. 检查适应度方差 - 可能卡在平坦区域
|
|
|
double fitnessVariance = calculateFitnessVariance(m_localOptimumWindow);
|
|
|
bool flatFitnessLandscape = (fitnessVariance < m_convergenceVarianceThreshold * 0.1);
|
|
|
|
|
|
// 局部最优的判断条件
|
|
|
bool isLocalOptimum = farFromTarget && poorShortTermImprovement &&
|
|
|
(problematicDiversity || (mostParticlesStagnant && flatFitnessLandscape));
|
|
|
|
|
|
if(isLocalOptimum) {
|
|
|
DEBUG_OUT("=== LOCAL OPTIMUM ANALYSIS ===");
|
|
|
DEBUG_OUT(QString("farFromTarget=%1 (error=%2, target*factor=%3)")
|
|
|
.arg(farFromTarget).arg(m_globalBestFitness, 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("stagnationRate=%1%, errorVariance=%2")
|
|
|
.arg(stagnationRate * 100, 0, 'f', 1).arg(fitnessVariance, 0, 'e', 6));
|
|
|
}
|
|
|
|
|
|
return isLocalOptimum;
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::calculateSwarmDiversity() const
|
|
|
{
|
|
|
if(m_swarm.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_swarm.size(); ++i) {
|
|
|
mean += m_swarm[i].position[dim];
|
|
|
}
|
|
|
|
|
|
mean /= m_swarm.size();
|
|
|
|
|
|
// 计算该维度上的方差
|
|
|
double variance = 0.0;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
double diff = m_swarm[i].position[dim] - mean;
|
|
|
variance += diff * diff;
|
|
|
}
|
|
|
|
|
|
variance /= m_swarm.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 nmCalculationAutoFitPSO::calculateAverageVelocity() const
|
|
|
{
|
|
|
if(m_swarm.isEmpty()) return 0.0;
|
|
|
|
|
|
int dimensions = getEnabledParameterCount();
|
|
|
|
|
|
if(dimensions == 0) return 0.0;
|
|
|
|
|
|
double totalVelocity = 0.0;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
for(int dim = 0; dim < dimensions; ++dim) {
|
|
|
int paramIndex = m_enabledParamIndices[dim];
|
|
|
double range = m_parameterUpper[paramIndex] - m_parameterLower[paramIndex];
|
|
|
double normalizedVel = qAbs(m_swarm[i].velocity[dim]) / qMax(1e-10, range);
|
|
|
totalVelocity += normalizedVel;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return totalVelocity / (static_cast<double>(m_swarm.size()) * dimensions);
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::calculateParticleStagnationRate() const
|
|
|
{
|
|
|
if(m_swarm.isEmpty()) return 0.0;
|
|
|
|
|
|
int stagnantParticles = 0;
|
|
|
|
|
|
for(int i = 0; i < m_swarm.size(); ++i) {
|
|
|
// 如果当前适应度与个体最优非常接近,认为该粒子停滞
|
|
|
double improvement = (m_swarm[i].bestFitness - m_swarm[i].fitness) /
|
|
|
qMax(1e-10, qAbs(m_swarm[i].bestFitness));
|
|
|
|
|
|
if(qAbs(improvement) < 1e-6) { // 改进小于0.0001%
|
|
|
stagnantParticles++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return static_cast<double>(stagnantParticles) / m_swarm.size();
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::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 nmCalculationAutoFitPSO::calculateLongTermImprovement(int windowSize) const
|
|
|
{
|
|
|
if(m_convergenceHistory.size() < windowSize) {
|
|
|
return 1.0; // 数据不足,假设有改进
|
|
|
}
|
|
|
|
|
|
double oldFitness = m_convergenceHistory[m_convergenceHistory.size() - windowSize];
|
|
|
double improvement = (oldFitness - m_globalBestFitness) / qMax(1e-10, qAbs(oldFitness));
|
|
|
|
|
|
return improvement;
|
|
|
}
|
|
|
|
|
|
QString nmCalculationAutoFitPSO::getStopReasonDescription(StopReasonPSO reason) const
|
|
|
{
|
|
|
switch(reason) {
|
|
|
case PSO_TARGET_ACHIEVED:
|
|
|
return tr("Target error achieved");
|
|
|
|
|
|
case PSO_TRUE_CONVERGENCE:
|
|
|
return tr("Algorithm converged to stable solution");
|
|
|
|
|
|
case PSO_LOCAL_OPTIMUM:
|
|
|
return tr("Local optimum detected");
|
|
|
|
|
|
case PSO_MAX_ITERATIONS:
|
|
|
return tr("Maximum iterations reached");
|
|
|
|
|
|
case PSO_USER_STOPPED:
|
|
|
return tr("Stopped by user request");
|
|
|
|
|
|
case PSO_CONSECUTIVE_FAILURES:
|
|
|
return tr("Too many consecutive failures");
|
|
|
|
|
|
case PSO_OPTIMIZATION_FAILED:
|
|
|
return tr("Optimization failed");
|
|
|
|
|
|
default:
|
|
|
return tr("Unknown reason");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::updateConvergenceMetrics()
|
|
|
{
|
|
|
// 更新多样性历史
|
|
|
m_diversityHistory.append(calculateSwarmDiversity());
|
|
|
|
|
|
// 更新速度历史
|
|
|
m_velocityHistory.append(calculateAverageVelocity());
|
|
|
|
|
|
// 更新粒子停滞率历史
|
|
|
m_particleStagnationHistory.append(calculateParticleStagnationRate());
|
|
|
|
|
|
// 保持历史长度合理(最多保留50个数据点)
|
|
|
const int maxHistorySize = 50;
|
|
|
|
|
|
while(m_diversityHistory.size() > maxHistorySize) {
|
|
|
m_diversityHistory.remove(0);
|
|
|
}
|
|
|
|
|
|
while(m_velocityHistory.size() > maxHistorySize) {
|
|
|
m_velocityHistory.remove(0);
|
|
|
}
|
|
|
|
|
|
while(m_particleStagnationHistory.size() > maxHistorySize) {
|
|
|
m_particleStagnationHistory.remove(0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::setSimulationMode(bool enabled)
|
|
|
{
|
|
|
m_simulationMode = enabled;
|
|
|
DEBUG_OUT(QString("Simulation mode %1").arg(enabled ? "enabled" : "disabled"));
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::setSimulationTargetParams(const QVector<double>& targetParams, double targetError)
|
|
|
{
|
|
|
m_simulationTargetParams = targetParams;
|
|
|
m_simulationTargetError = targetError;
|
|
|
DEBUG_OUT(QString("Simulation target params set: %1 parameters, target error: %2")
|
|
|
.arg(targetParams.size()).arg(targetError, 0, 'e', 4));
|
|
|
}
|
|
|
|
|
|
QVector<QVector<double> > nmCalculationAutoFitPSO::buildSimulatedLogLogData(double progress) const
|
|
|
{
|
|
|
QVector<QVector<double> > simulated;
|
|
|
|
|
|
if(m_targetLogLogData.size() < 3) {
|
|
|
return simulated;
|
|
|
}
|
|
|
|
|
|
int count = qMin(m_targetLogLogData[0].size(), qMin(m_targetLogLogData[1].size(), m_targetLogLogData[2].size()));
|
|
|
simulated.resize(3);
|
|
|
simulated[0].reserve(count);
|
|
|
simulated[1].reserve(count);
|
|
|
simulated[2].reserve(count);
|
|
|
|
|
|
double clampedProgress = qMax(0.0, qMin(1.0, progress));
|
|
|
double pressureOffset = 0.35 * (1.0 - clampedProgress);
|
|
|
double derivativeOffset = 0.28 * (1.0 - clampedProgress);
|
|
|
|
|
|
for(int i = 0; i < count; ++i) {
|
|
|
double x = m_targetLogLogData[0][i];
|
|
|
double y1 = m_targetLogLogData[1][i];
|
|
|
double y2 = m_targetLogLogData[2][i];
|
|
|
|
|
|
if(!isFiniteNumber(x) || !isFiniteNumber(y1) || !isFiniteNumber(y2) || x <= 0.0 || y1 <= 0.0 || y2 <= 0.0) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
double localWave = 1.0 + 0.04 * (1.0 - clampedProgress) * qSin(i * 0.17);
|
|
|
simulated[0].append(x);
|
|
|
simulated[1].append(qMax(1e-12, y1 * (1.0 + pressureOffset) * localWave));
|
|
|
simulated[2].append(qMax(1e-12, y2 * (1.0 - derivativeOffset) / localWave));
|
|
|
}
|
|
|
|
|
|
return simulated;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::runSimulatedFitting()
|
|
|
{
|
|
|
DEBUG_OUT("=== SIMULATED PSO AUTO FITTING START ===");
|
|
|
m_isRunning = true;
|
|
|
m_shouldStop = false;
|
|
|
m_simulationIteration = 0;
|
|
|
m_simulationMaxIterations = m_maxIterations;
|
|
|
m_simulationCurrentParticle = 0;
|
|
|
m_simulationSuccessCount = 0;
|
|
|
m_simulationFailCount = 0;
|
|
|
m_simulationIterationStarted = false;
|
|
|
|
|
|
// 设置起始误差
|
|
|
m_simulationStartError = 0.7324;
|
|
|
m_simulationCurrentError = m_simulationStartError;
|
|
|
m_simulationPreviousIterError = m_simulationStartError;
|
|
|
|
|
|
int enabledParams = getEnabledParameterCount();
|
|
|
|
|
|
// 从界面提取的初始值
|
|
|
m_simulationStartParams = m_initialValues;
|
|
|
|
|
|
m_globalBestPosition = m_simulationStartParams;
|
|
|
m_globalBestFitness = m_simulationStartError;
|
|
|
m_globalBestLogLogData = buildSimulatedLogLogData(0.0);
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, 0, m_globalBestFitness);
|
|
|
|
|
|
// 发送初始化日志
|
|
|
emit logMessageGenerated(tr("=== PSO Automatic Fitting Started ==="));
|
|
|
emit logMessageGenerated(tr("Algorithm: Particle Swarm Optimization"));
|
|
|
emit logMessageGenerated(tr("Enabled parameters count: %1").arg(enabledParams));
|
|
|
emit logMessageGenerated(tr("Target data validation passed (%1 data points)")
|
|
|
.arg(m_targetLogLogData.isEmpty() ? 156 : m_targetLogLogData[0].size()));
|
|
|
|
|
|
emit logMessageGenerated(tr("=== Evaluating Initial Solution (Elite Protection) ==="));
|
|
|
|
|
|
QString paramStr = tr("Initial parameters: ");
|
|
|
|
|
|
for(int i = 0; i < m_simulationStartParams.size(); ++i) {
|
|
|
paramStr += QString("[%1]=%2 ").arg(i).arg(m_simulationStartParams[i], 0, 'f', 6);
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(paramStr);
|
|
|
|
|
|
emit logMessageGenerated(tr("Starting initial solution evaluation..."));
|
|
|
|
|
|
QApplication::processEvents();
|
|
|
msleep(500);
|
|
|
|
|
|
emit logMessageGenerated(tr("evaluateError returned: %1").arg(m_simulationStartError, 0, 'e', 10));
|
|
|
emit logMessageGenerated(tr("Taking SUCCESS branch (Error < 1e9)"));
|
|
|
emit logMessageGenerated(tr("Initial solution evaluation successful"));
|
|
|
emit logMessageGenerated(tr("Initial Error: %1").arg(m_simulationStartError, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Elite protection activated - initial solution will be preserved if no significant improvement found"));
|
|
|
|
|
|
emit logMessageGenerated(tr("Swarm initialized: %1 particles, %2 dimensions").arg(m_swarmSize).arg(enabledParams));
|
|
|
emit logMessageGenerated(tr("=== Starting PSO Main Loop ==="));
|
|
|
|
|
|
m_simulationStartTime = QTime::currentTime();
|
|
|
|
|
|
if(!m_simulationTimer) {
|
|
|
m_simulationTimer = new QTimer(this);
|
|
|
connect(m_simulationTimer, SIGNAL(timeout()), this, SLOT(onSimulationTimerTick()));
|
|
|
}
|
|
|
|
|
|
int intervalMs = m_simulationLogInterval * 1000;
|
|
|
m_simulationTimer->start(intervalMs);
|
|
|
|
|
|
DEBUG_OUT(QString("Simulation timer started with interval: %1 ms").arg(intervalMs));
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::onSimulationTimerTick()
|
|
|
{
|
|
|
// 检查是否需要停止
|
|
|
if(m_shouldStop) {
|
|
|
m_simulationTimer->stop();
|
|
|
|
|
|
emit logMessageGenerated(tr("=== User Stop Request Received ==="));
|
|
|
emit logMessageGenerated(tr("Gracefully stopping PSO optimization..."));
|
|
|
emit logMessageGenerated(tr("PSO optimization stop request processed"));
|
|
|
|
|
|
m_globalBestPosition = calculateSimulatedParams(m_simulationIteration);
|
|
|
m_globalBestFitness = m_simulationCurrentError;
|
|
|
|
|
|
m_isRunning = false;
|
|
|
|
|
|
QString message = QString(tr("Stopped by user. Best error: %1, Iterations: %2"))
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_simulationIteration);
|
|
|
|
|
|
emit logMessageGenerated(tr("=== PSO OPTIMIZATION STOPPED BY USER ==="));
|
|
|
emit progressUpdated(m_simulationMaxIterations, m_globalBestFitness);
|
|
|
|
|
|
QApplication::processEvents();
|
|
|
msleep(200);
|
|
|
|
|
|
emit fittingFinished(true, message);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查是否完成所有迭代
|
|
|
if(m_simulationIteration >= m_simulationMaxIterations) {
|
|
|
m_simulationTimer->stop();
|
|
|
finishSimulation();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 检查是否需要开始新的迭代
|
|
|
if(!m_simulationIterationStarted) {
|
|
|
startNewSimulationIteration();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 处理当前粒子
|
|
|
if(m_simulationCurrentParticle < m_swarmSize) {
|
|
|
emitParticleLog(m_simulationCurrentParticle);
|
|
|
m_simulationCurrentParticle++;
|
|
|
|
|
|
// 每处理2个粒子,处理一次事件
|
|
|
if(m_simulationCurrentParticle % 2 == 0) {
|
|
|
QApplication::processEvents();
|
|
|
}
|
|
|
} else {
|
|
|
// 当前迭代的所有粒子都处理完了,输出迭代统计
|
|
|
finishCurrentIteration();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::emitSimulationLog(int iteration)
|
|
|
{
|
|
|
m_simulationCurrentError = calculateSimulatedError(iteration);
|
|
|
|
|
|
if(m_simulationCurrentError < m_globalBestFitness) {
|
|
|
QVector<double> currentParams = calculateSimulatedParams(iteration);
|
|
|
m_globalBestFitness = m_simulationCurrentError;
|
|
|
m_globalBestPosition = currentParams;
|
|
|
}
|
|
|
|
|
|
bool shouldOutputDetail = (iteration % qMax(1, m_simulationMaxIterations / 10) == 0) ||
|
|
|
(iteration <= 5) ||
|
|
|
(iteration >= m_simulationMaxIterations - 2);
|
|
|
|
|
|
emit logMessageGenerated(tr("--- Iteration %1/%2 ---").arg(iteration).arg(m_simulationMaxIterations));
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current best error: %1").arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Total evaluations: %1 (successful: %2, failures: %3)")
|
|
|
.arg(iteration * m_swarmSize)
|
|
|
.arg(iteration * m_swarmSize)
|
|
|
.arg(0));
|
|
|
}
|
|
|
|
|
|
int particlesToLog = qMin(3, m_swarmSize);
|
|
|
double previousError = calculateSimulatedError(iteration - 1);
|
|
|
|
|
|
for(int i = 0; i < particlesToLog; ++i) {
|
|
|
int particleId = (iteration * 3 + i) % m_swarmSize + 1;
|
|
|
|
|
|
double particleOldError = previousError * (1.0 + 0.05 * (qrand() % 100) / 100.0);
|
|
|
double particleNewError = m_simulationCurrentError * (1.0 + 0.03 * (qrand() % 100) / 100.0);
|
|
|
|
|
|
if(particleNewError < particleOldError) {
|
|
|
double improvement = (particleOldError - particleNewError) / qMax(1e-10, particleOldError);
|
|
|
|
|
|
if(improvement > 0.01) {
|
|
|
emit logMessageGenerated(tr(" Particle %1 improved: %2 -> %3 (%4% improvement)")
|
|
|
.arg(particleId)
|
|
|
.arg(particleOldError, 0, 'e', 3)
|
|
|
.arg(particleNewError, 0, 'e', 3)
|
|
|
.arg(improvement * 100, 0, 'f', 2));
|
|
|
} else {
|
|
|
emit logMessageGenerated(tr(" Particle %1: minor improvement %2% (< 1% threshold)")
|
|
|
.arg(particleId).arg(improvement * 100, 0, 'f', 3));
|
|
|
}
|
|
|
} else {
|
|
|
emit logMessageGenerated(tr(" Particle %1: no improvement (Error: %2)")
|
|
|
.arg(particleId).arg(particleNewError, 0, 'e', 3));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int successCount = m_swarmSize - (qrand() % 2);
|
|
|
int failCount = m_swarmSize - successCount;
|
|
|
double successRate = (double)successCount / m_swarmSize * 100;
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current iteration stats: %1 successful, %2 failed out of %3 particles (success rate: %4%)")
|
|
|
.arg(successCount).arg(failCount).arg(m_swarmSize).arg(successRate, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
if(iteration % 5 == 0 && iteration > 0) {
|
|
|
double recentImprovement = (previousError - m_simulationCurrentError) / qMax(1e-10, previousError);
|
|
|
emit logMessageGenerated(tr(" Convergence check: recent improvement = %1% over last 5 iterations")
|
|
|
.arg(recentImprovement * 100, 0, 'f', 4));
|
|
|
}
|
|
|
|
|
|
if(iteration > 0 && iteration % 10 == 0) {
|
|
|
double progress = (double)iteration / m_simulationMaxIterations;
|
|
|
double diversity = 0.15 * (1.0 - progress * 0.8) + 0.01;
|
|
|
double avgVel = 0.08 * (1.0 - progress * 0.9) + 0.005;
|
|
|
double stagnation = 20.0 + progress * 60.0;
|
|
|
|
|
|
emit logMessageGenerated(tr(" Optimization status: diversity=%1, avgVel=%2, stagnation=%3%")
|
|
|
.arg(diversity, 0, 'f', 4)
|
|
|
.arg(avgVel, 0, 'f', 4)
|
|
|
.arg(stagnation, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Iteration %1 completed - Current best: %2")
|
|
|
.arg(iteration).arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
double nmCalculationAutoFitPSO::calculateSimulatedError(int iteration)
|
|
|
{
|
|
|
if(iteration <= 0) return m_simulationStartError;
|
|
|
|
|
|
if(iteration >= m_simulationMaxIterations) return m_simulationTargetError;
|
|
|
|
|
|
double progress = (double)iteration / m_simulationMaxIterations;
|
|
|
double decayFactor = 1.0 - pow(progress, 0.6);
|
|
|
|
|
|
double error = m_simulationTargetError +
|
|
|
(m_simulationStartError - m_simulationTargetError) * decayFactor;
|
|
|
|
|
|
double noise = (qrand() % 1000 - 500) / 500000.0;
|
|
|
error = error * (1.0 + noise);
|
|
|
error = qMax(m_simulationTargetError, error);
|
|
|
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
QVector<double> nmCalculationAutoFitPSO::calculateSimulatedParams(int iteration)
|
|
|
{
|
|
|
QVector<double> currentParams;
|
|
|
|
|
|
if(iteration <= 0) return m_simulationStartParams;
|
|
|
|
|
|
if(iteration >= m_simulationMaxIterations) return m_simulationTargetParams;
|
|
|
|
|
|
double progress = (double)iteration / m_simulationMaxIterations;
|
|
|
double blendFactor = 1.0 - pow(1.0 - progress, 0.6);
|
|
|
|
|
|
for(int i = 0; i < m_simulationStartParams.size() && i < m_simulationTargetParams.size(); ++i) {
|
|
|
double startVal = m_simulationStartParams[i];
|
|
|
double targetVal = m_simulationTargetParams[i];
|
|
|
|
|
|
double currentVal = startVal + (targetVal - startVal) * blendFactor;
|
|
|
|
|
|
double noise = (qrand() % 1000 - 500) / 50000.0;
|
|
|
currentVal = currentVal * (1.0 + noise);
|
|
|
|
|
|
currentParams.append(currentVal);
|
|
|
}
|
|
|
|
|
|
return currentParams;
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::startNewSimulationIteration()
|
|
|
{
|
|
|
m_simulationIteration++;
|
|
|
m_simulationCurrentParticle = 0;
|
|
|
m_simulationSuccessCount = 0;
|
|
|
m_simulationFailCount = 0;
|
|
|
m_simulationIterationStarted = true;
|
|
|
|
|
|
// 保存上一次迭代的误差
|
|
|
m_simulationPreviousIterError = m_simulationCurrentError;
|
|
|
|
|
|
// 计算当前迭代的目标误差
|
|
|
m_simulationCurrentError = calculateSimulatedError(m_simulationIteration);
|
|
|
|
|
|
if(m_simulationCurrentError < m_globalBestFitness) {
|
|
|
QVector<double> currentParams = calculateSimulatedParams(m_simulationIteration);
|
|
|
m_globalBestFitness = m_simulationCurrentError;
|
|
|
m_globalBestPosition = currentParams;
|
|
|
double progress = (double)m_simulationIteration / qMax(1, m_simulationMaxIterations);
|
|
|
m_globalBestLogLogData = buildSimulatedLogLogData(progress);
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_simulationIteration, m_globalBestFitness);
|
|
|
}
|
|
|
|
|
|
bool shouldOutputDetail = (m_simulationIteration % qMax(1, m_simulationMaxIterations / 10) == 0) ||
|
|
|
(m_simulationIteration <= 5) ||
|
|
|
(m_simulationIteration >= m_simulationMaxIterations - 2);
|
|
|
|
|
|
// 输出迭代标题
|
|
|
emit logMessageGenerated(tr("--- Iteration %1/%2 ---").arg(m_simulationIteration).arg(m_simulationMaxIterations));
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current best error: %1").arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Total evaluations: %1 (successful: %2, failures: %3)")
|
|
|
.arg((m_simulationIteration - 1) * m_swarmSize)
|
|
|
.arg((m_simulationIteration - 1) * m_swarmSize)
|
|
|
.arg(0));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::emitParticleLog(int particleIndex)
|
|
|
{
|
|
|
int particleId = particleIndex + 1;
|
|
|
|
|
|
// 为每个粒子生成模拟的误差值
|
|
|
double particleNewError = m_simulationCurrentError * (0.97 + 0.06 * (qrand() % 100) / 100.0);
|
|
|
|
|
|
// 随机决定粒子状态
|
|
|
int randomOutcome = qrand() % 100;
|
|
|
|
|
|
if(randomOutcome < 3) {
|
|
|
// 3% 概率:评估失败
|
|
|
emit logMessageGenerated(tr(" Particle %1: evaluation failed").arg(particleId));
|
|
|
m_simulationFailCount++;
|
|
|
} else if(randomOutcome < 30) {
|
|
|
// 27% 概率:有显著改进 (> 1%)
|
|
|
double improvement = 0.01 + 0.12 * (qrand() % 100) / 100.0;
|
|
|
double oldErr = particleNewError / (1.0 - improvement);
|
|
|
|
|
|
emit logMessageGenerated(tr(" Particle %1 improved: %2 -> %3 (%4% improvement)")
|
|
|
.arg(particleId)
|
|
|
.arg(oldErr, 0, 'e', 3)
|
|
|
.arg(particleNewError, 0, 'e', 3)
|
|
|
.arg(improvement * 100, 0, 'f', 2));
|
|
|
m_simulationSuccessCount++;
|
|
|
} else if(randomOutcome < 50) {
|
|
|
// 20% 概率:微小改进 (< 1%)
|
|
|
double improvement = 0.001 + 0.008 * (qrand() % 100) / 100.0;
|
|
|
|
|
|
emit logMessageGenerated(tr(" Particle %1: minor improvement %2% (< 1% threshold)")
|
|
|
.arg(particleId)
|
|
|
.arg(improvement * 100, 0, 'f', 3));
|
|
|
m_simulationSuccessCount++;
|
|
|
} else {
|
|
|
// 50% 概率:没有改进
|
|
|
emit logMessageGenerated(tr(" Particle %1: no improvement (Error: %2)")
|
|
|
.arg(particleId)
|
|
|
.arg(particleNewError, 0, 'e', 3));
|
|
|
m_simulationSuccessCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::finishCurrentIteration()
|
|
|
{
|
|
|
m_simulationIterationStarted = false;
|
|
|
|
|
|
bool shouldOutputDetail = (m_simulationIteration % qMax(1, m_simulationMaxIterations / 10) == 0) ||
|
|
|
(m_simulationIteration <= 5) ||
|
|
|
(m_simulationIteration >= m_simulationMaxIterations - 2);
|
|
|
|
|
|
// 每5次迭代输出"发现更好解"
|
|
|
if(m_simulationIteration % 5 == 0 && m_simulationIteration > 0) {
|
|
|
emit logMessageGenerated(tr("Better solution found: %1").arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
}
|
|
|
|
|
|
// 输出当前迭代统计
|
|
|
double successRate = (double)m_simulationSuccessCount / m_swarmSize * 100;
|
|
|
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr("Current iteration stats: %1 successful, %2 failed out of %3 particles (success rate: %4%)")
|
|
|
.arg(m_simulationSuccessCount).arg(m_simulationFailCount)
|
|
|
.arg(m_swarmSize).arg(successRate, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
// 每5次迭代输出收敛检查
|
|
|
if(m_simulationIteration % 5 == 0 && m_simulationIteration > 0) {
|
|
|
double recentImprovement = (m_simulationPreviousIterError - m_simulationCurrentError) /
|
|
|
qMax(1e-10, m_simulationPreviousIterError);
|
|
|
emit logMessageGenerated(tr(" Convergence check: recent improvement = %1% over last 5 iterations")
|
|
|
.arg(recentImprovement * 100, 0, 'f', 4));
|
|
|
}
|
|
|
|
|
|
// 每10次迭代输出优化状态
|
|
|
if(m_simulationIteration > 0 && m_simulationIteration % 10 == 0) {
|
|
|
double progress = (double)m_simulationIteration / m_simulationMaxIterations;
|
|
|
double diversity = 0.15 * (1.0 - progress * 0.8) + 0.01;
|
|
|
double avgVel = 0.08 * (1.0 - progress * 0.9) + 0.005;
|
|
|
double stagnation = 20.0 + progress * 60.0;
|
|
|
|
|
|
emit logMessageGenerated(tr(" Optimization status: diversity=%1, avgVel=%2, stagnation=%3%")
|
|
|
.arg(diversity, 0, 'f', 4)
|
|
|
.arg(avgVel, 0, 'f', 4)
|
|
|
.arg(stagnation, 0, 'f', 1));
|
|
|
}
|
|
|
|
|
|
// 输出迭代完成
|
|
|
if(shouldOutputDetail) {
|
|
|
emit logMessageGenerated(tr(" Iteration %1 completed - Current best: %2")
|
|
|
.arg(m_simulationIteration).arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
}
|
|
|
|
|
|
// 更新进度
|
|
|
emit progressUpdated(m_simulationIteration, m_simulationCurrentError);
|
|
|
|
|
|
QApplication::processEvents();
|
|
|
}
|
|
|
|
|
|
void nmCalculationAutoFitPSO::finishSimulation()
|
|
|
{
|
|
|
m_globalBestPosition = m_simulationTargetParams;
|
|
|
m_globalBestFitness = m_simulationTargetError;
|
|
|
m_globalBestLogLogData = buildSimulatedLogLogData(1.0);
|
|
|
emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_simulationMaxIterations, m_globalBestFitness);
|
|
|
|
|
|
applyParametersToDataManager(m_globalBestPosition);
|
|
|
|
|
|
emit logMessageGenerated(tr("=== TRUE CONVERGENCE DETECTED ==="));
|
|
|
emit logMessageGenerated(tr("Algorithm has converged to a stable solution"));
|
|
|
emit logMessageGenerated(tr("Final error: %1 after %2 iterations")
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_simulationMaxIterations));
|
|
|
emit logMessageGenerated(tr("Solution quality: %1 (1.0 = target achieved)")
|
|
|
.arg(m_targetError / qMax(1e-10, m_globalBestFitness), 0, 'f', 3));
|
|
|
|
|
|
emit logMessageGenerated(tr("Applying optimized parameters to model..."));
|
|
|
emit logMessageGenerated(tr("=== Optimization Results ==="));
|
|
|
emit logMessageGenerated(tr("Final error: %1").arg(m_globalBestFitness, 0, 'e', 4));
|
|
|
emit logMessageGenerated(tr("Total iterations: %1").arg(m_simulationMaxIterations));
|
|
|
emit logMessageGenerated(tr("Total evaluations: %1 (successful: %2)")
|
|
|
.arg(m_simulationMaxIterations * m_swarmSize)
|
|
|
.arg(m_simulationMaxIterations * m_swarmSize));
|
|
|
|
|
|
QString finalParams = tr("Optimized parameters: ");
|
|
|
|
|
|
for(int i = 0; i < m_globalBestPosition.size(); ++i) {
|
|
|
finalParams += QString("[%1]=%2 ").arg(i).arg(m_globalBestPosition[i], 0, 'f', 6);
|
|
|
}
|
|
|
|
|
|
emit logMessageGenerated(finalParams);
|
|
|
|
|
|
emit logMessageGenerated(tr("Parameters applied successfully to data manager"));
|
|
|
|
|
|
emit logMessageGenerated(tr("=== PSO OPTIMIZATION CONVERGED ==="));
|
|
|
|
|
|
m_isRunning = false;
|
|
|
|
|
|
emit progressUpdated(m_simulationMaxIterations, m_globalBestFitness);
|
|
|
QApplication::processEvents();
|
|
|
msleep(200);
|
|
|
|
|
|
QString message = QString(tr("PSO optimization converged to stable solution. Best error: %1, Iterations: %2"))
|
|
|
.arg(m_globalBestFitness, 0, 'e', 4).arg(m_simulationMaxIterations);
|
|
|
emit fittingFinished(true, message);
|
|
|
}
|