Files
Population-optimization-alg…/MQL5/Include/Math/AOs/PopulationAO/AO_ECBO_EnhancedCollidingBodiesOptimization.mqh
T
2026-01-29 21:24:52 +04:00

381 lines
27 KiB
Plaintext

//+——————————————————————————————————————————————————————————————————+
//| C_AO_ECBO |
//| Copyright 2007-2026, Andrey Dik |
//| https://www.mql5.com/ru/users/joo |
//+——————————————————————————————————————————————————————————————————+
#include "#C_AO.mqh"
//————————————————————————————————————————————————————————————————————
class C_AO_ECBO : public C_AO
{
public:
~C_AO_ECBO () { }
C_AO_ECBO ()
{
ao_name = "ECBO";
ao_desc = "Enhanced Colliding Bodies Optimization";
ao_link = "https://www.mql5.com/ru/articles/21147";
popSize = 50;
ArrayResize (params, 3);
params [0].name = "popSize"; params [0].val = popSize;
params [1].name = "CMsize"; params [1].val = 20; // размер Colliding Memory
params [2].name = "PRO"; params [2].val = 0.7; // вероятность кроссовера
}
void SetParams ()
{
popSize = (int)params [0].val;
CMsize = (int)params [1].val;
PRO = params [2].val;
}
bool Init (const double &rangeMinP [],
const double &rangeMaxP [],
const double &rangeStepP [],
const int epochsP = 0);
void Moving ();
void Revision ();
//------------------------------------------------------------------
int CMsize; // размер Colliding Memory
double PRO; // вероятность кроссовера
private: //—————————————————————————————————————————————————————————
int epochs; // общее число эпох
int epochNow; // текущая эпоха
S_AO_Agent CM []; // Colliding Memory - хранит лучшие позиции
S_AO_Agent pTemp []; // временный массив для сортировки
double mass []; // массы тел
void CalculateMasses ();
};
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
bool C_AO_ECBO::Init (const double &rangeMinP [],
const double &rangeMaxP [],
const double &rangeStepP [],
const int epochsP = 0)
{
if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
//------------------------------------------------------------------
// popSize должен быть чётным
//------------------------------------------------------------------
if (popSize % 2 != 0) popSize++;
epochs = epochsP;
epochNow = 0;
//------------------------------------------------------------------
// Инициализация Colliding Memory
//------------------------------------------------------------------
ArrayResize (CM, CMsize);
for (int i = 0; i < CMsize; i++)
{
CM [i].Init (coords);
CM [i].f = -DBL_MAX;
}
//------------------------------------------------------------------
// Временный массив для сортировки
//------------------------------------------------------------------
ArrayResize (pTemp, popSize);
for (int i = 0; i < popSize; i++) pTemp [i].Init (coords);
//------------------------------------------------------------------
// Массив масс
//------------------------------------------------------------------
ArrayResize (mass, popSize);
for (int i = 0; i < popSize; i++) mass [i] = 1.0 / popSize;
return true;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_ECBO::Moving ()
{
epochNow++;
//------------------------------------------------------------------
// Первая итерация - случайная инициализация (формула 1)
//------------------------------------------------------------------
if (!revision)
{
for (int i = 0; i < popSize; i++)
{
for (int c = 0; c < coords; c++)
{
double r = u.RNDfromCI (0.0, 1.0);
a [i].c [c] = rangeMin [c] + r * (rangeMax [c] - rangeMin [c]);
a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
}
return;
}
//------------------------------------------------------------------
// Сохраняем старые позиции
//------------------------------------------------------------------
for (int i = 0; i < popSize; i++)
{
ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);
}
//------------------------------------------------------------------
// Коэффициент восстановления ε (линейно от 1 до 0)
// Обеспечивает баланс exploration/exploitation
//------------------------------------------------------------------
double epsilon = 1.0 - (double)epochNow / (double)epochs;
if (epsilon < 0.0) epsilon = 0.0;
if (epsilon > 1.0) epsilon = 1.0;
int halfPop = popSize / 2;
//------------------------------------------------------------------
// Основной цикл - вычисление столкновений
//------------------------------------------------------------------
for (int i = 0; i < popSize; i++)
{
for (int c = 0; c < coords; c++)
{
double vPrime = 0.0;
double newPos = 0.0;
if (i < halfPop)
{
//============================================================
// СТАЦИОНАРНЫЕ ТЕЛА (лучшая половина, индексы 0..halfPop-1)
//============================================================
int movingIdx = i + halfPop; // парное движущееся тело
//------------------------------------------------------------
// Скорость движущегося тела ДО столкновения (формула 4):
// v_moving = x_stationary - x_moving
//------------------------------------------------------------
double vMoving = a [i].cP [c] - a [movingIdx].cP [c];
//------------------------------------------------------------
// Скорость стационарного тела ПОСЛЕ столкновения (формула 5):
// v'_stat = ((m_mov + ε*m_mov) * v_mov) / (m_stat + m_mov)
//------------------------------------------------------------
double mStat = mass [i];
double mMove = mass [movingIdx];
double denominator = mStat + mMove;
if (denominator > 1e-10)
{
vPrime = ((mMove + epsilon * mMove) * vMoving) / denominator;
}
//------------------------------------------------------------
// Новая позиция стационарного (формула 8):
// x_new = x_old + rand * v'
// rand ∈ [-1, 1]
//------------------------------------------------------------
double rand_c = u.RNDfromCI (-1.0, 1.0);
newPos = a [i].cP [c] + rand_c * vPrime;
}
else
{
//============================================================
// ДВИЖУЩИЕСЯ ТЕЛА (худшая половина, индексы halfPop..popSize-1)
//============================================================
int stationaryIdx = i - halfPop; // парное стационарное тело
//------------------------------------------------------------
// Скорость движущегося тела ДО столкновения (формула 4):
// v_moving = x_stationary - x_moving
//------------------------------------------------------------
double vMoving = a [stationaryIdx].cP [c] - a [i].cP [c];
//------------------------------------------------------------
// Скорость движущегося тела ПОСЛЕ столкновения (формула 6):
// v'_mov = ((m_mov - ε*m_stat) * v_mov) / (m_stat + m_mov)
//------------------------------------------------------------
double mStat = mass [stationaryIdx];
double mMove = mass [i];
double denominator = mStat + mMove;
if (denominator > 1e-10)
{
vPrime = ((mMove - epsilon * mStat) * vMoving) / denominator;
}
//------------------------------------------------------------
// Новая позиция движущегося (формула 9):
// x_new = x_stationary + rand * v'
// ВАЖНО: стартуем от позиции СТАЦИОНАРНОГО тела!
//------------------------------------------------------------
double rand_c = u.RNDfromCI (-1.0, 1.0);
newPos = a [stationaryIdx].cP [c] + rand_c * vPrime;
}
a [i].c [c] = newPos;
}
//----------------------------------------------------------------
// Кроссовер (формула 10) - только для ECBO
// С вероятностью PRO случайная координата переинициализируется
//----------------------------------------------------------------
if (PRO > 0.0)
{
double ran_i = u.RNDfromCI (0.0, 1.0);
if (ran_i < PRO)
{
int j = u.RNDminusOne (coords); // случайная координата
a [i].c [j] = rangeMin [j] + u.RNDfromCI (0.0, 1.0) * (rangeMax [j] - rangeMin [j]);
}
}
//----------------------------------------------------------------
// Проверка границ
//----------------------------------------------------------------
for (int c = 0; c < coords; c++)
{
if (a [i].c [c] < rangeMin [c]) a [i].c [c] = rangeMin [c];
if (a [i].c [c] > rangeMax [c]) a [i].c [c] = rangeMax [c];
a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
}
}
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
void C_AO_ECBO::Revision ()
{
//------------------------------------------------------------------
// 1. Обновляем глобальное лучшее решение
//------------------------------------------------------------------
for (int i = 0; i < popSize; i++)
{
if (a [i].f > fB)
{
fB = a [i].f;
ArrayCopy (cB, a [i].c, 0, 0, coords);
}
}
//------------------------------------------------------------------
// 2. Обновляем Colliding Memory (ECBO)
// CM хранит лучшие найденные позиции за всё время
//------------------------------------------------------------------
if (CMsize > 0)
{
for (int i = 0; i < popSize; i++)
{
// Находим худший элемент в CM
int worstCMidx = 0;
double worstCMf = CM [0].f;
for (int m = 1; m < CMsize; m++)
{
if (CM [m].f < worstCMf)
{
worstCMf = CM [m].f;
worstCMidx = m;
}
}
// Если текущий агент лучше худшего в CM - заменяем
if (a [i].f > worstCMf)
{
CM [worstCMidx].f = a [i].f;
ArrayCopy (CM [worstCMidx].c, a [i].c, 0, 0, coords);
}
}
//----------------------------------------------------------------
// 3. Замещаем худшие решения в популяции решениями из CM
// Это предотвращает деградацию лучших решений
//----------------------------------------------------------------
for (int m = 0; m < CMsize; m++)
{
// Находим худший элемент в популяции
int worstPopIdx = 0;
double worstPopF = a [0].f;
for (int i = 1; i < popSize; i++)
{
if (a [i].f < worstPopF)
{
worstPopF = a [i].f;
worstPopIdx = i;
}
}
// Если элемент CM лучше - заменяем
if (CM [m].f > worstPopF)
{
a [worstPopIdx].f = CM [m].f;
ArrayCopy (a [worstPopIdx].c, CM [m].c, 0, 0, coords);
}
}
}
//------------------------------------------------------------------
// 4. Сортируем популяцию по убыванию фитнеса
// После сортировки: [0..halfPop-1] - стационарные (лучшие)
// [halfPop..popSize-1] - движущиеся (худшие)
//------------------------------------------------------------------
u.Sorting (a, pTemp, popSize);
//------------------------------------------------------------------
// 5. Вычисляем массы тел
//------------------------------------------------------------------
CalculateMasses ();
revision = true;
}
//————————————————————————————————————————————————————————————————————
//————————————————————————————————————————————————————————————————————
// Вычисление масс тел
// Для максимизации: m_k = f_k / sum(f_i)
// Чем лучше решение (больше f), тем больше масса
//————————————————————————————————————————————————————————————————————
void C_AO_ECBO::CalculateMasses ()
{
//------------------------------------------------------------------
// Находим минимальный фитнес для сдвига (чтобы все значения были > 0)
//------------------------------------------------------------------
double minF = a [0].f;
for (int i = 1; i < popSize; i++)
{
if (a [i].f < minF) minF = a [i].f;
}
//------------------------------------------------------------------
// Сдвигаем все значения в положительную область
//------------------------------------------------------------------
double shift = 0.0;
if (minF <= 0.0) shift = MathAbs (minF) + 1e-10;
//------------------------------------------------------------------
// Вычисляем сумму смещённых фитнесов
//------------------------------------------------------------------
double sumF = 0.0;
for (int i = 0; i < popSize; i++)
{
sumF += a [i].f + shift;
}
if (sumF < 1e-10) sumF = 1e-10;
//------------------------------------------------------------------
// Вычисляем нормализованные массы (формула 2, адаптированная для максимизации)
//------------------------------------------------------------------
for (int i = 0; i < popSize; i++)
{
mass [i] = (a [i].f + shift) / sumF;
}
}
//————————————————————————————————————————————————————————————————————