381 lines
27 KiB
Plaintext
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;
|
|
}
|
|
}
|
|
//———————————————————————————————————————————————————————————————————— |