//+——————————————————————————————————————————————————————————————————+ //| 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; } } //————————————————————————————————————————————————————————————————————