preview
Алгоритм поисковой оптимизации Эбола — Ebola Optimization Search Algorithm (EOSA)

Алгоритм поисковой оптимизации Эбола — Ebola Optimization Search Algorithm (EOSA)

MetaTrader 5Тестер |
55 0
Andrey Dik
Andrey Dik

Содержание

  1. Введение
  2. Реализация алгоритма
  3. Результаты тестов
  4. Выводы


Введение

В данной статье разберем еще один алгоритм оптимизации, скорее перенесем его идею в реализацию, основанную на природном явлении распространения вируса. 

Вирус Эбола — один из самых смертоносных патогенов, известных человечеству. Вспышка 2014-2016 годов в Западной Африке продемонстрировала, насколько эффективно вирус может распространяться в популяции. Именно эта мрачная эффективность вдохновила исследователей Oyelade и Ezugwu на создание нового метода оптимизации. 

EOSA — это новый био‑вдохновлённый метаэвристический алгоритм оптимизации, основанный на модели распространения вируса Эбола в популяции и опубликованный в 2021 году. Представьте деревню, где появился первый заболевший — "нулевой пациент". Он становится источником инфекции, суперраспространителем. Вирус передаётся двумя путями:

  • Короткодистанционная передача — через близкий контакт между людьми, такой как рукопожатие, объятие, уход за больным. Заражённый остаётся в своём районе, но активно инфицирует соседей. В терминах оптимизации это эксплуатация — интенсивный поиск в окрестности лучшего решения.
  • Длиннодистанционная передача — через путешествия и миграцию. Инфицированный садится в автобус и едет в соседний город, неся вирус в новые, ещё не затронутые районы. Это исследование — глобальный поиск в неизведанных областях пространства решений.

Карантин изолирует часть заболевших, предотвращая лавинообразное распространение. В алгоритме это сохраняет разнообразие популяции, не давая всем агентам слиться в одной точке. Что из этого получилось, посмотрим дальше.



Реализация алгоритма

Математическая модель SEIR-HDVQ

Авторы построили сложную эпидемиологическую модель с восемью компартментами (субпопуляциями):

Обозначение
Состояние
Описание
S
Susceptible
Восприимчивые — ещё не заражённые
E
Exposed
Подвергшиеся воздействию — в инкубационном периоде
I
Infected
Инфицированные — активные распространители
H
Hospitalized
Госпитализированные
R
Recovered
Выздоровевшие
D
Dead
Умершие
V
Vaccinated
Вакцинированные
QQuarantineНа карантине

Модель описывается системой дифференциальных уравнений (Eq. 6-12), определяющих переходы между состояниями:

(6)  ∂S/∂t = π - (β₁I + β₃D + β₄R + β₂(PE)η)S - (τS + ΓI)

(7)  ∂I/∂t = (β₁I + β₃D + β₄R + β₂(PE)λ)S - (Γ + γ)I - τS

(8)  ∂H/∂t = αI - (γ + ω)H

(9)  ∂R/∂t = γI - ΓR

(10) ∂V/∂t = γI - (μ + θ)V

(11) ∂D/∂t = (τS + ΓI) - δD

(12) ∂Q/∂t = (πI - (γR + ΓD)) - ξQ

где π - скорость рекрутирования, β₁-β₄ - коэффициенты контакта, η и λ - коэффициенты затухания, τ - естественная смертность, Γ - смертность от болезни, α - скорость госпитализации, γ - скорость выздоровления, ω - скорость лечения, μ - скорость вакцинации, θ - ответ на вакцину, δ - скорость захоронения, ξ - скорость карантина.

Давайте разберем основные уравнения обновления позиций агентов из оригинальной статьи:

Eq.1 - Обновление позиции: mIᵢᵗ⁺¹ = mIᵢᵗ + ρ × M(I); где ρ - масштабный коэффициент, M(I) - скорость перемещения.

Eq.2 - Эксплуатация (короткая дистанция): M(I) = srate × rand(0,1) + M(Ind_best).

Eq.3 - Исследование (длинная дистанция): M(s) = lrate × rand(0,1) + M(Ind_best).

Eq.4 - Инициализация: individualᵢ = Lᵢ + rand(0,1) × (Uᵢ + Lᵢ).

Проблемы оригинальных формул

При анализе уравнений выявлены критические несоответствия:

  • Eq.2 и Eq.3 не содержат направленного движения. Формула M(I) = srate × rand(0,1) + M(Ind_best) просто добавляет случайное число к позиции лучшего. Отсутствует вектор направления (target - current), который необходим для движения агента к цели. Без этого компонента агент не перемещается к лучшему решению, а получает позицию, не связанную с его текущим положением.
  • Eq.4 содержит явную опечатку. Формула L + rand × (U + L) даёт некорректный диапазон. При границах U=10, L=5 результат попадает в интервал [5, 20], что выходит за верхнюю границу. Стандартная формула инициализации: L + rand × (U - L).
  • Eq.2 и Eq.3 практически идентичны. Единственное отличие — коэффициент srate vs lrate. При этом текстовое описание в статье говорит о движении к лучшему решению (exploitation) и движении через случайного агента-передатчика (exploration), что не отражено в формулах.

Основные уравнения обновления позиций агентов теперь в новом виде:

  • Eq.2 — Эксплуатация (короткая дистанция): M(I) = srate × rand(0,1) × (IndBest - current). Добавлен вектор направления (Ind_best - current), обеспечивающий движение агента к лучшему решению. Без этого компонента формула не имеет смысла для оптимизации.
  • Eq.3 — Исследование (длинная дистанция): M(s) = lrate × rand(0,1) × (transmitter - current) + srate × rand(0,1) × (IndBest - current). Добавлен компонент движения к случайному агенту-передатчику (transmitter - current), что соответствует текстовому описанию длиннодистанционной передачи через контакт с другими особями.
  • Eq.4 — Инициализация: individualᵢ = Lᵢ + rand(0,1) × (Uᵢ - Lᵢ). Исправлена очевидная опечатка: плюс заменён на минус для корректной генерации в заданном диапазоне [L, U].

Псевдокод алгоритма:

1.  Инициализировать S, E, I, H, R, V, Q ← ∅
2.  Создать начальную популяцию S по Eq.4
3.  Выбрать index case (нулевого пациента) → I
4.  
5.  WHILE epoch ≤ max_epoch AND |I| > 0:
6.      Q ← карантинировать часть I по Eq.12
7.      fracI ← I \ Q  (активные инфицированные)
8.      
9.      FOR each agent in fracI:
10.         IF прошёл инкубационный период:
11.             Вычислить neighborhood
12.             IF neighborhood < 0.5:
13.                 Exploitation по Eq.2
14.             ELSE:
15.                 Exploration по Eq.3
16.     
17.     // Переходы между состояниями:
18.     H ← госпитализировать часть I по Eq.8
19.     R ← выздоровевшие из I по Eq.9
20.     V ← вакцинировать часть H по Eq.10
21.     D ← умершие из I по Eq.11
22.     
23.     I ← I - R - D
24.     S ← S + R        // выздоровевшие возвращаются
25.     S ← S - D + new  // умершие заменяются новыми
26.     
27.     Обновить лучшее решение

Проблема: разрыв между описанием и оптимизацией. При попытке буквально реализовать алгоритм возникли критические проблемы:

  • Некорректные формулы движения. Оригинальные Eq.2 и Eq.3 не содержат направленного движения к цели. Без вектора (target - current) агенты не могут систематически улучшать свои позиции.
  • Популяция вымирает. На старте только один агент "index case" — является инфицированным (активным). При высокой смертности (δ = 0.5) и низком заражении популяция быстро сокращается до нуля.
  • Заморозка агентов. Госпитализированные (H), вакцинированные (V), умершие (D) не участвуют в поиске. Они "выключены" из оптимизации, что резко снижает эффективность.
  • Слабая генерация новых решений. Формула заражения numNew = β₁ × |I| × srate при малом |I| даёт ноль новых агентов.
  • Неопределённость "neighborhood". Статья не даёт явной формулы расчёта параметра "neighborhood", от которого зависит выбор стратегии эксплуатации или исследования.

Итоговая реализация

Философия адаптации: мне пришлось сохранить дух эпидемиологической метафоры, но переосмыслить её для оптимизации. Вместо буквального моделирования распространения болезни — используем ключевые механизмы как источник вдохновения. Представьте не саму эпидемию, а информацию о хорошем решении, которая распространяется в популяции подобно вирусу:

  • близкие к лучшему решению агенты "заражаются" его качеством — уточняют позицию локально;
  • далёкие агенты совершают "путешествия" — прыжки Леви, в поисках новых перспективных областей.

Введем упрощённую структуру. Вместо восьми состояний — все агенты активны:

Параметр
Значение
Описание
popSize
50
Размер популяции
srate
1.5Интенсивность эксплуатации
lrate
1.0Интенсивность исследования
quarantine
0.05Вероятность пропуска итерации

Механизм выбора стратегии

Параметр "neighborhood" вычисляется случайно с учётом качества агента: neighborhood = rand() × (1 - fitness_ratio × 0.5).

Лучшие агенты чаще попадают в режим эксплуатации, худшие — чаще исследуют. Это интуитивно верно: если вы нашли золотую жилу, копайте глубже; если ваша шахта пуста — ищите новое место.

Эксплуатация: локальный поиск. Когда (neighborhood < 0.5), агент находится в режиме "близкого контакта":

// Целевая точка: смесь глобального и личного лучшего
target = w × gBest + (1-w) × pBest

// Движение к цели с локальным шумом
M = srate × rand() × (target - current)
M += Gaussian_noise × range × 0.05

new_position = current + ρ × M

Проведем аналогию: заражённый в своём районе. Он ходит к соседям (gBest, pBest), но не выходит за пределы квартала. Небольшой гауссов шум — это случайные встречи на улице.

Исследование: глобальный поиск. Когда (neighborhood ≥ 0.5), агент "путешествует". Два варианта. Вариант A — Прыжки Леви (50% случаев):

levy = LevyFlight()  // распределение Леви
step = lrate × levy × range × 0.1
attraction = 0.1 × rand() × (gBest - current)

new_position = current + step + attraction

Пример: инфицированный садится в самолёт и летит в случайный город. Распределение Леви — это много "коротких перелётов + редкие трансконтинентальные рейсы". Именно так распространяются реальные эпидемии в эпоху глобализации.

Вариант B — Передача через агента (50% случаев):

j = random_agent ≠ i

M = lrate × rand() × (agent[j] - current)
M += 0.3 × srate × rand() × (gBest - current)

new_position = current + ρ × M

Пример: заражённый встречает путешественника из другого города и перенимает информацию о тамошней ситуации.

Прыжки Леви, это математика дальних путешествий. Распределение Леви — это тяжелохвостое распределение, характерное для перемещений животных в поисках пищи, миграции людей и распространения эпидемий.

Его особенностью является много мелких шагов, однако изредка — гигантские прыжки.

double LevyFlight()
{
    // Mantegna's algorithm, β = 1.5
    double sigma_u = 0.6966;  // предвычисленная константа
    
    double u = Gaussian() × sigma_u;
    double v = Gaussian();
    
    double step = u / |v|^0.6667;
    
    return clamp(step, -3, 3);
}

Карантин: сохранение разнообразия. С вероятностью 5% агент пропускает итерацию:

if (rand() < quarantine)
    continue;  // агент в изоляции

Зачем? Без карантина все агенты устремляются к лучшему решению и могут слиться в одну точку. Карантин — это "инерция": часть популяции сохраняет свои позиции, обеспечивая разнообразие для будущих итераций.

Личная память (pBest). Каждый агент помнит свою лучшую позицию:

if (current.fitness > pBest.fitness)
    pBest = current;

В режиме эксплуатации агент движется к смеси "gBest" и "pBest". Это предотвращает ситуацию, когда все агенты слепо следуют за одним лидером. Аналогия: вы помните, где было хорошо лично вам, даже если все говорят, что сейчас лучше в другом месте.

Почему были предприняты такие изменения? Оккам и метаэвристики. Сложность эпидемиологической модели не транслируется в качество оптимизации. Восемь состояний и шестнадцать параметров создают большое количество "степеней свободы" и хаотическую динамику, где большинство агентов заморожены в неактивных состояниях. Можно охарактеризовать ситуацию принципом бритвы Оккама: из двух объяснений выбирай простейшее. Из двух алгоритмов — тот, где меньше параметров для настройки и внешней оптимизации.

Levy flights — является научно обоснованной заменой. Оригинальное исследование говорит о "дальних перемещениях", но использует простую линейную комбинацию. Levy flights — это математически обоснованная модель дальних перемещений.

Fitness bias — адаптивный баланс. Вместо фиксированного порога (neighborhood = 0.5) используем адаптивный механизм, когда хорошие решения эксплуатируются, а плохие решения продолжат исследоваться. Это естественно: успешный бизнесмен расширяет то, что работает, а начинающий — пробует разное.
EOSA — является примером того, как биологическая метафора может вдохновить на создание алгоритма оптимизации, но её буквальная реализация не всегда оптимальна и может иметь несколько интерпретаций. В итоге были сохранены ключевые идеи:
  • два режима поиска (exploitation/exploration) — как аналог короткой/длинной передачи вируса;
  • карантин — как механизм сохранения разнообразия;
  • масштабный коэффициент "ρ" — как затухание эпидемии со временем.

Добавлены проверенные техники: "Levy flights" — для глобального исследования, личная память "pBest" — для устойчивости, а "Fitness bias" — для адаптивного баланса стратегий. В результате получился компактный алгоритм с четырьмя параметрами, сохраняющий эпидемиологическую интуицию оригинала. Приведу формулы итоговой реализации:

Инициализация (Eq.4): aᵢ = rangeMin + rand(0,1) × (rangeMax - rangeMin)

Масштабный коэффициент: ρ = 1.0 - epoch/max_epoch × 0.5  (от 1.0 до 0.5)

Exploitation (neighborhood < 0.5):

target = w × gBest + (1-w) × pBest,  w ∈ [0.3, 0.7]

M = srate × rand() × (target - aᵢ) + Gaussian() × range × 0.05 × ρ

aᵢ = aᵢ + ρ × M

Exploration — Levy (50%):

step = lrate × LevyFlight() × range × 0.1

attraction = 0.1 × rand() × (gBest - aᵢ)

aᵢ = aᵢ + step + attraction × ρ

Exploration — через агента (50%):

M = lrate × rand() × (aⱼ - aᵢ) + 0.3 × srate × rand() × (gBest - aᵢ)

aᵢ = aᵢ + ρ × M

Levy Flight (β = 1.5):

σᵤ = 0.6966

u = Gaussian() × σᵤ

v = Gaussian()

step = u / |v|^0.6667

return clamp(step, -3, 3)

Можем посмотреть на схему-иллюстрацию для версии реализации алгоритма EOSA.

EOSA

Рисунок 1. Схема-иллюстрация работы алгоритма EOSA

Верхняя часть иллюстрации — два режима работы:

EXPLOITATION (слева): агент движется к target (смесь gBest и pBest) + гауссов шум для локального поиска; EXPLORATION (справа): Lévy flights или передача через случайного агента j (50/50).

Сформируем подробный псевдокод для дальнейшей реализации алгоритма в коде.

Algorithm EOSA
    Input:  popSize, srate, lrate, ξ, epochs, rangeMin[], rangeMax[]
    Output: gBest — best solution found

BEGIN
    // Initialization
    FOR each agent i DO
        a[i] ← random position in [rangeMin, rangeMax]
        pBest[i] ← a[i]
    END FOR
    gBest ← best of population

    // Main loop
    FOR epoch = 1 TO epochs DO
        ρ ← 1.0 − epoch/epochs × 0.5                    // scale factor: 1.0 → 0.5

        FOR each agent i DO
            IF rand() < ξ THEN CONTINUE                  // quarantine

            neighborhood ← rand() × (1 − fitnessBias)

            IF neighborhood < 0.5 THEN
                //——————————————————————————————————————
                // EXPLOITATION: local search
                //——————————————————————————————————————
                w ← rand(0.3, 0.7)
                target ← w·gBest + (1−w)·pBest[i]
                M ← srate·rand()·(target − a[i]) + GaussianNoise
                a[i] ← a[i] + ρ·M

            ELSE
                //——————————————————————————————————————
                // EXPLORATION: global search
                //——————————————————————————————————————
                IF rand() < 0.5 THEN
                    // Lévy flight
                    step ← lrate · LévyFlight() · range · 0.1
                    a[i] ← a[i] + step + 0.1·rand()·(gBest − a[i])
                ELSE
                    // Via random agent j
                    j ← random agent, j ≠ i
                    M ← lrate·rand()·(a[j] − a[i]) + 0.3·srate·rand()·(gBest − a[i])
                    a[i] ← a[i] + ρ·M
                END IF
            END IF

            a[i] ← clamp(a[i], rangeMin, rangeMax)       // boundary control
        END FOR

        // Update best solutions
        FOR each agent i DO
            IF f(a[i]) > f(pBest[i]) THEN pBest[i] ← a[i]
            IF f(a[i]) > f(gBest) THEN gBest ← a[i]
        END FOR
    END FOR

    RETURN gBest
END
// Lévy Flight (Mantegna's algorithm, β = 1.5)

Function LévyFlight()
    u ← Gaussian() × 0.6966
    v ← Gaussian()
    RETURN clamp(u / |v|^0.6667, −3, 3)
END
// Default parameters: popSize=50, srate=1.5, lrate=1.0, ξ=0.05

Можем перейти к описанию реализации алгоритма EOSA.

Класс "C_AO_EOSA" реализует алгоритм оптимизации EOSA (Ebola Optimization Search Algorithm), вдохновлённый механизмами распространения вируса Эбола. Класс наследуется от базового класса "C_AO" и содержит четыре настраиваемых параметра: popSize — размер популяции (по умолчанию 50), srate — интенсивность эксплуатации (1.5), lrate — интенсивность исследования с использованием полётов Леви (1.0), и quarantine — вероятность карантина агента (0.05). Внутренние переменные "epochs" и "currentEpoch" отслеживают общее количество итераций и текущую эпоху соответственно.

//————————————————————————————————————————————————————————————————————
class C_AO_EOSA : public C_AO
{
  public:
  ~C_AO_EOSA () { }

  C_AO_EOSA ()
  {
    ao_name = "EOSA";
    ao_desc = "Ebola Optimization Search Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/20932";

    popSize    = 50;
    srate      = 1.5;
    lrate      = 1.0;
    quarantine = 0.05;

    ArrayResize (params, 4);
    params [0].name = "popSize";    params [0].val = popSize;
    params [1].name = "srate";      params [1].val = srate;      // exploitation intensity
    params [2].name = "lrate";      params [2].val = lrate;      // exploration intensity (Levy)
    params [3].name = "quarantine"; params [3].val = quarantine; // quarantine rate
  }

  void SetParams ()
  {
    popSize    = (int)params [0].val;
    srate      = params      [1].val;
    lrate      = params      [2].val;
    quarantine = params      [3].val;
  }

  bool Init (const double &rangeMinP  [],
             const double &rangeMaxP  [],
             const double &rangeStepP [],
             const int     epochsP = 0);

  void Moving   ();
  void Revision ();

  private: //—————————————————————————————————————————————————————————
  double srate;      // интенсивность эксплуатации
  double lrate;      // интенсивность исследования (Levy)
  double quarantine; // вероятность карантина

  int    epochs;
  int    currentEpoch;

  void   BoundaryControl (int idx);
  double LevyFlight      ();
  double RandGauss       ();
};
//————————————————————————————————————————————————————————————————————

Метод "Init" выполняет инициализацию алгоритма. Он вызывает стандартную инициализацию базового класса, передавая массивы границ поиска "rangeMin", "rangeMax" и шага дискретизации "rangeStep". Затем сохраняет общее число эпох и обнуляет счётчик текущей эпохи. Метод возвращает "true" при успешной инициализации.

//————————————————————————————————————————————————————————————————————
bool C_AO_EOSA::Init (const double &rangeMinP  [],
                      const double &rangeMaxP  [],
                      const double &rangeStepP [],
                      const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  epochs       = epochsP;
  currentEpoch = 0;

  return true;
}
//————————————————————————————————————————————————————————————————————

Метод "Moving" является ядром алгоритма и реализует логику перемещения агентов. На первой итерации, когда флаг "revision" равен "false", метод инициализирует популяцию случайными позициями в заданных границах и завершает работу. На последующих итерациях вычисляется адаптивный масштабный коэффициент "ρ", который линейно уменьшается от 1.0 до 0.5 по мере продвижения оптимизации, обеспечивая постепенный переход от интенсивного исследования к тонкой настройке решений. Для каждого агента сначала проверяется условие карантина — с вероятностью "quarantine" агент пропускает текущую итерацию, что предотвращает преждевременную сходимость и сохраняет разнообразие популяции.

Затем вычисляется параметр "neighborhood", определяющий стратегию поведения агента. Этот параметр представляет собой случайное значение, скорректированное с учётом качества текущего решения агента: лучшие агенты с большей вероятностью попадают в режим эксплуатации, а худшие — в режим исследования. При значении "neighborhood" меньше 0.5 агент выполняет эксплуатацию — локальный поиск вокруг лучших известных решений. Целевая точка формируется как взвешенная комбинация глобального лучшего решения "gBest" и персонального лучшего решения агента "pBest" с весом "w", случайно выбираемым из интервала [0.3, 0.7]. Движение к цели осуществляется с интенсивностью "srate", при этом добавляется гауссов шум для локального исследования окрестности.

При значении "neighborhood" не меньше 0.5 агент выполняет исследование — глобальный поиск с использованием одной из двух стратегий, выбираемых с равной вероятностью. Первая стратегия — полёт Леви, имитирующий дальние перемещения характерные для распространения эпидемий через путешествия и миграцию. Шаг Леви масштабируется коэффициентом "lrate" и добавляется слабое притяжение к глобальному лучшему решению. Вторая стратегия — передача через случайного агента, где текущий агент движется в направлении другого случайно выбранного агента популяции с добавлением притяжения к лучшему решению.

//————————————————————————————————————————————————————————————————————
void C_AO_EOSA::Moving ()
{
  //------------------------------------------------------------------
  // ПЕРВАЯ ИТЕРАЦИЯ: Инициализация популяции
  //------------------------------------------------------------------
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      for (int c = 0; c < coords; c++)
      {
        a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
        a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }

    revision = true;
    return;
  }

  //------------------------------------------------------------------
  currentEpoch++;

  // Адаптивный масштаб: уменьшается со временем
  double progress = (double)currentEpoch / (double)MathMax (epochs, 1);
  double rho = 1.0 - progress * 0.5;  // 1.0 → 0.5

  //------------------------------------------------------------------
  // Обновление позиций всех агентов
  //------------------------------------------------------------------
  for (int i = 0; i < popSize; i++)
  {
    //================================================================
    // КАРАНТИН: агент в изоляции пропускает итерацию
    // Эпидемиологический смысл: изоляция предотвращает распространение
    //================================================================
    if (u.RNDprobab () < quarantine)
    {
      continue;
    }

    //================================================================
    // Определить neighborhood (близость к "эпицентру" - лучшему решению)
    // Используем случайный выбор с bias на основе fitness
    //================================================================
    double neighborhood = u.RNDprobab ();

    // Bias: лучшие агенты чаще делают exploitation
    double fitnessRatio = 0.5;
    if (fB > -DBL_MAX + 1e10 && a [i].f > -DBL_MAX + 1e10)
    {
      // Нормализуем fitness для bias
      double range_f = MathMax (fB - a [i].f, 1e-10);
      fitnessRatio = MathExp (-range_f / MathMax (MathAbs (fB), 1e-10));
    }

    // Комбинируем случайность с fitness bias
    neighborhood = neighborhood * (1.0 - fitnessRatio * 0.5);

    //================================================================
    // ВЫБОР СТРАТЕГИИ ОБНОВЛЕНИЯ
    //================================================================
    if (neighborhood < 0.5)
    {
      //==============================================================
      // EXPLOITATION: Короткодистанционная передача (Eq.2)
      // Эпидемиология: близкий контакт → локальное распространение
      // Оптимизация: интенсивный поиск вокруг лучших решений
      //==============================================================

      // Выбрать целевую точку: смесь глобального и личного лучшего
      double w = u.RNDfromCI (0.3, 0.7);

      for (int c = 0; c < coords; c++)
      {
        double target;

        // Целевая точка: взвешенная комбинация gBest и pBest
        if (a [i].fB > -DBL_MAX + 1e10)
        {
          target = w * cB [c] + (1.0 - w) * a [i].cB [c];
        }
        else
        {
          target = cB [c];
        }

        // Eq.2 интерпретация: M(I) = srate * rand * direction + noise
        double r1 = u.RNDfromCI (0.0, 1.0);
        double direction = target - a [i].c [c];

        // Движение к цели + локальная случайность
        double M = srate * r1 * direction;

        // Добавить гауссов шум для локального исследования
        double range = rangeMax [c] - rangeMin [c];
        M += RandGauss () * range * 0.05 * rho;

        // Eq.1: обновление позиции
        a [i].c [c] = a [i].c [c] + rho * M;
      }
    }
    else
    {
      //==============================================================
      // EXPLORATION: Длиннодистанционная передача (Eq.3)
      // Эпидемиология: дальний контакт → глобальное распространение
      // Оптимизация: Levy flights для глобального поиска
      //==============================================================

      // Выбрать случайного агента j ≠ i (передатчик)
      int j = u.RNDminusOne (popSize);
      if (j == i) j = (i + 1) % popSize;

      // Решить: Levy flight или движение через передатчика
      if (u.RNDprobab () < 0.5)
      {
        //------------------------------------------------------------
        // LEVY FLIGHT: имитация дальних перемещений
        // В эпидемиологии: путешествия, миграция
        //------------------------------------------------------------
        for (int c = 0; c < coords; c++)
        {
          double range = rangeMax [c] - rangeMin [c];
          double levy = LevyFlight ();  // β = 1.5

          // Levy step от текущей позиции
          double step = lrate * levy * range * 0.1;

          // Слабое притяжение к лучшему
          double attraction = 0.1 * u.RNDfromCI (0.0, 1.0) * (cB [c] - a [i].c [c]);

          a [i].c [c] = a [i].c [c] + step + attraction * rho;
        }
      }
      else
      {
        //------------------------------------------------------------
        // ПЕРЕДАЧА ЧЕРЕЗ АГЕНТА: движение в направлении другого агента
        // В эпидемиологии: заражение через контакт
        //------------------------------------------------------------
        for (int c = 0; c < coords; c++)
        {
          double r1 = u.RNDfromCI (0.0, 1.0);
          double r2 = u.RNDfromCI (0.0, 1.0);

          // Eq.3: M(s) = lrate * rand * (a[j] - a[i]) + attraction_to_best
          double M = lrate * r1 * (a [j].c [c] - a [i].c [c]);

          // Слабое притяжение к лучшему (не доминирует)
          M += 0.3 * srate * r2 * (cB [c] - a [i].c [c]);

          a [i].c [c] = a [i].c [c] + rho * M;
        }
      }
    }

    BoundaryControl (i);
  }
}
//————————————————————————————————————————————————————————————————————

Метод "LevyFlight" генерирует случайный шаг согласно распределению Леви по алгоритму Мантеньи. Используется фиксированное значение параметра β = 1.5, для которого предвычислена константа σ_u = 0.6966. Метод генерирует два гауссовых случайных числа и вычисляет шаг по формуле u/|v|^0.6667, ограничивая результат интервалом [-3, 3] для предотвращения экстремальных выбросов.

//————————————————————————————————————————————————————————————————————
double C_AO_EOSA::LevyFlight ()
{
  // Mantegna's algorithm for Levy distribution
  // beta = 1.5 (fixed)

  // Предвычисленное sigma_u для beta = 1.5:
  // sigma_u = [Γ(2.5)×sin(3π/4) / (Γ(1.25)×1.5×2^0.25)]^(2/3)
  //         = [1.3293×0.7071 / (0.9064×1.5×1.1892)]^0.6667
  //         ≈ 0.6966
  double sigma_u = 0.6966;

  double u_val = RandGauss () * sigma_u;
  double v_val = RandGauss ();

  // Защита от деления на ноль
  if (MathAbs (v_val) < 1e-10) v_val = 1e-10;

  // step = u / |v|^(1/beta) = u / |v|^0.6667 для beta=1.5
  double step = u_val / MathPow (MathAbs (v_val), 0.6667);

  // Ограничить экстремальные значения
  if (step > 3.0)  step = 3.0;
  if (step < -3.0) step = -3.0;

  return step;
}
//————————————————————————————————————————————————————————————————————

Метод "RandGauss" реализует генерацию гауссовой случайной величины с нулевым средним и единичной дисперсией по методу Бокса-Мюллера. Два равномерно распределённых числа преобразуются в нормально распределённое значение через логарифмическое и тригонометрическое преобразования.

//————————————————————————————————————————————————————————————————————
double C_AO_EOSA::RandGauss ()
{
  // Box-Muller transform
  double u1 = u.RNDfromCI (1e-10, 1.0);
  double u2 = u.RNDfromCI (0.0, 1.0);

  return MathSqrt (-2.0 * MathLog (u1)) * MathCos (2.0 * M_PI * u2);
}
//————————————————————————————————————————————————————————————————————
Метод "BoundaryControl" обеспечивает контроль границ поиска методом отражения. В отличие от простого обрезания, отражение сохраняет импульс движения агента: при выходе за нижнюю границу позиция отражается вверх, при выходе за верхнюю — вниз. Если после нескольких отражений позиция остаётся за пределами допустимой области, применяется случайная реинициализация координаты.
//————————————————————————————————————————————————————————————————————
void C_AO_EOSA::BoundaryControl (int idx)
{
  for (int c = 0; c < coords; c++)
  {
    // Отражение от границ вместо обрезки
    double val = a [idx].c [c];
    double min = rangeMin [c];
    double max = rangeMax [c];
    double range = max - min;

    // Отражение
    while (val < min || val > max)
    {
      if (val < min) val = min + (min - val);
      if (val > max) val = max - (val - max);

      // Защита от бесконечного цикла
      if (val < min - range || val > max + range)
      {
        val = min + u.RNDfromCI (0.0, 1.0) * range;
        break;
      }
    }

    a [idx].c [c] = u.SeInDiSp (val, min, max, rangeStep [c]);
  }
}
//————————————————————————————————————————————————————————————————————

Метод "Revision" обновляет лучшие найденные решения. Для каждого агента проверяется, превосходит ли его текущий фитнес персональный рекорд, и при необходимости обновляется "pBest". Одновременно отслеживается лучший агент популяции, и если его фитнес превышает глобальный рекорд, обновляется "gBest". Такая схема обеспечивает сохранение информации о перспективных областях пространства поиска.

//————————————————————————————————————————————————————————————————————
void C_AO_EOSA::Revision ()
{
  //------------------------------------------------------------------
  // Обновить личные и глобального лучших
  //------------------------------------------------------------------
  int    bestIdx = 0;
  double bestFit = -DBL_MAX;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }

    if (a [i].f > bestFit)
    {
      bestFit = a [i].f;
      bestIdx = i;
    }
  }

  //------------------------------------------------------------------
  // Обновить глобального лучшего (Eq.5)
  //------------------------------------------------------------------
  if (bestFit > fB)
  {
    fB = bestFit;
    ArrayCopy (cB, a [bestIdx].c, 0, 0, coords);
  }
}
//————————————————————————————————————————————————————————————————————


Результаты тестов

Теперь можем посмотреть на результаты, не выдающиеся, но с возможностью работы над задачами. Алгоритм EOSA был протестирован на стандартном наборе тестовых функций: Hilly (гладкие многоэкстремальные), Forest (острые пики) и Megacity (дискретные ступенчатые). Тестирование проводилось при размерностях 5, 25 и 500 измерений с ограничением в 10 000 вычислений целевой функции.

Общий результат составил около 38,5% от максимально возможного, что примерно на 10% ниже порога вхождения в таблицу лучших популяционных методов оптимизации. 

EOSA|Ebola Optimization Search Algorithm|50.0|3.0|2.0|0.01|
=============================
5 Hilly's; Func runs: 10000; result: 0.710222611733512
25 Hilly's; Func runs: 10000; result: 0.4632808052522758
500 Hilly's; Func runs: 10000; result: 0.29182585953654855
=============================
5 Forest's; Func runs: 10000; result: 0.6237714734740634
25 Forest's; Func runs: 10000; result: 0.38209042951228955
500 Forest's; Func runs: 10000; result: 0.20125437332932763
=============================
5 Megacity's; Func runs: 10000; result: 0.4676923076923078
25 Megacity's; Func runs: 10000; result: 0.2132307692307692
500 Megacity's; Func runs: 10000; result: 0.11516923076923188
=============================
All score: 3.46854 (38.54%)

На визуализации можно заметить, что алгоритм труднее справляется с дискретными функциями типа Megacity, за конечное число итераций. Ступенчатый ландшафт с плоскими плато не даёт градиентной информации, а механизм движения к лучшему решению становится неэффективным, когда множество точек имеют одинаковый фитнес.

Hilly

EOSA на тестовой функции Hilly

Forest

EOSA на тестовой функции Forest

Megacity

EOSA на тестовой функции Megacity

Так же можно посмотреть визуализацию работы алгоритма с более простыми стандартными функциями, на параболической функции тоже есть некоторые проблемы с большой размерностью задачи, сходимость не самая лучшая.

GoldsteinPrice

EOSA на тестовой функции GoldsteinPrice

Paraboloid

EOSA на тестовой функции Paraboloid

В рейтинговой таблице лучших популяционных методов оптимизации алгоритм EOSA представлен ознакомительно.

AO Description Hilly Hilly
Final
Forest Forest
Final
Megacity (discrete) Megacity
Final
Final
Result
% of
MAX
10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)
1DOAdingomdingo_optimization_algorithm_M0,479680,453670,463691,397040,941450,879090,914542,735080,786150,860610,848052,494816,62773,63
2ANSacross neighbourhood search0,949480,847760,438572,235811,000000,923340,399882,323230,709230,634770,230911,574916,13468,15
3CLAcode lock algorithm (joo)0,953450,871070,375902,200420,989420,917090,316422,222940,796920,693850,193031,683806,10767,86
4AMOmanimal migration ptimization M0,903580,843170,462842,209590,990010,924360,465982,380340,567690,591320,237731,396755,98766,52
5(P+O)ES(P+O) evolution strategies0,922560,881010,400212,203790,977500,874900,319452,171850,673850,629850,186341,490035,86665,17
6CTAcomet tail algorithm (joo)0,953460,863190,277702,094350,997940,857400,339492,194840,887690,564310,105121,557125,84664,96
7TETAtime evolution travel algorithm (joo)0,913620,823490,319902,057010,970960,895320,293242,159520,734620,685690,160211,580525,79764,41
8SDSmstochastic diffusion search M0,930660,854450,394762,179880,999830,892440,196192,088460,723330,611000,106701,441035,70963,44
9BOAmbilliards optimization algorithm M0,957570,825990,252352,035901,000000,900360,305022,205380,735380,525230,095631,356255,59862,19
10AAmarchery algorithm M0,917440,708760,421602,047800,925270,758020,353282,036570,673850,552000,237381,463235,54861,64
11ESGevolution of social groups (joo)0,999060,796540,350562,146161,000000,828630,131021,959650,823330,553000,047251,423585,52961,44
12SIAsimulated isotropic annealing (joo)0,957840,842640,414652,215130,982390,795860,205071,983320,686670,493000,090531,270205,46960,76
13EOmextremal_optimization_M0,761660,772420,317471,851550,999990,767510,235272,002770,747690,539690,142491,429875,28458,71
14BBObiogeography based optimization0,949120,694560,350311,993990,938200,673650,256821,868670,746150,482770,173691,402615,26558,50
15ACSartificial cooperative search0,755470,747440,304071,806981,000000,888610,224132,112740,690770,481850,133221,305835,22658,06
16DAdialectical algorithm0,861830,700330,337241,899400,981630,727720,287181,996530,703080,452920,163671,319675,21657,95
17BHAmblack hole algorithm M0,752360,766750,345831,864930,935930,801520,271772,009230,650770,516460,154721,321955,19657,73
18ASOanarchy society optimization0,848720,746460,314651,909830,961480,791500,238031,991010,570770,540620,166141,277525,17857,54
19RFOroyal flush optimization (joo)0,833610,737420,346291,917330,894240,738240,240981,873460,631540,502920,164211,298675,08956,55
20AOSmatomic orbital search M0,802320,704490,310211,817020,856600,694510,219961,771070,746150,528620,143581,418355,00655,63
21TSEAturtle shell evolution algorithm (joo)0,967980,644800,296721,909490,994490,619810,227081,841390,690770,426460,135981,253225,00455,60
22BSAbacktracking_search_algorithm0,973090,545340,290981,809410,999990,585430,217471,802890,847690,369530,129781,347004,95955,10
23DEdifferential evolution0,950440,616740,303081,870260,953170,788960,166521,908650,786670,360330,029531,176534,95555,06
24SRAsuccessful restaurateur algorithm (joo)0,968830,634550,292171,895550,946370,555060,191241,692670,749230,440310,125261,314804,90354,48
25BObonobo_optimizer0,775650,638050,329081,742780,880880,763440,255731,900050,610770,498460,142461,251694,89554,38
26CROchemical reaction optimisation0,946290,661120,298531,905930,879060,584220,211461,674730,758460,426460,126861,311784,89254,36
27BIOblood inheritance optimization (joo)0,815680,653360,308771,777810,899370,653190,217601,770160,678460,476310,139021,293784,84253,80
28DOAdream_optimization_algorithm0,855560,700850,372801,929210,734210,489050,241471,464730,772310,473540,185611,431464,82553,62
29BSAbird swarm algorithm0,893060,649000,262501,804550,924200,711210,249391,884790,693850,326150,100121,120124,80953,44
30DEAdolphin_echolocation_algorithm0,759950,675720,341711,777380,895820,642230,239411,777460,615380,440310,151151,206844,76252,91
31HSharmony search0,865090,687820,325271,878180,999990,680020,095901,775920,620000,422670,054581,097254,75152,79
32SSGsaplings sowing and growing0,778390,649250,395431,823080,859730,624670,174291,658690,646670,441330,105981,193984,67651,95
33BCOmbacterial chemotaxis optimization M0,759530,622680,314831,697040,893780,613390,225421,732590,653850,420920,144351,219124,64951,65
34ABOafrican buffalo optimization0,833370,622470,299641,755480,921700,586180,197231,705110,610000,431540,132251,173784,63451,49
35(PO)ES(PO) evolution strategies0,790250,626470,429351,846060,876160,609430,195911,681510,590000,379330,113221,082554,61051,22
36FBAfractal-based Algorithm0,790000,651340,289651,730990,871580,568230,188771,628580,610770,460620,123981,195374,55550,61
37TSmtabu search M0,877950,614310,291041,783300,928850,518440,190541,637830,610770,382150,121571,114494,53650,40
38BSObrain storm optimization0,937360,576160,296881,810410,931310,558660,235371,725340,552310,290770,119140,962224,49849,98
39WOAmwale optimization algorithm M0,845210,562980,262631,670810,931000,522780,163651,617430,663080,411380,113571,188034,47649,74
40AEFAartificial electric field algorithm0,877000,617530,252351,746880,927290,726980,180641,834900,666150,116310,095080,877544,45949,55
41AEOartificial ecosystem-based optimization algorithm0,913800,467130,264701,645630,902230,437050,214001,553270,661540,308000,285631,255174,45449,49
42CAmcamel algorithm M0,786840,560420,351331,698590,827720,560410,243361,631490,648460,330920,134181,113564,44449,37
43ACOmant colony optimization M0,881900,661270,303771,846930,858730,586800,150511,596040,596670,373330,024720,994724,43849,31
44CMAEScovariance_matrix_adaptation_evolution_strategy0,762580,720890,000001,483470,820560,796160,000001,616720,758460,490770,000001,249234,34948,33
45DA_duelistduelist_algorithm0,927820,537780,277921,743520,869570,475360,181931,526860,621530,335690,117151,074374,34548,28
EOSAebola_optimization_search_algorithm0,710220,463280,291821,465320,623770,382090,201251,207110,467690,213230,115160,796083,46938,54
RWrandom walk0,487540,321590,257811,066940,375540,219440,158770,753750,279690,149170,098470,527342,34826,09


Выводы

Оригинальная статья предлагает сложную эпидемиологическую модель с восемью состояниями агентов и шестнадцатью параметрами. Однако математический аппарат дифференциальных уравнений, описывающий распространение болезни, не транслируется напрямую в эффективные операторы поиска. Попытка буквальной реализации приводит к вымиранию активной популяции и заморозке большинства агентов в неактивных состояниях. Моя упрощённая реализация сохранила ключевые метафоры — короткую и длинную передачу, карантин, однако потеряла часть потенциального разнообразия механизмов. Возможно, именно в сложных переходах между состояниями скрывался потенциал для более эффективного поиска, который не удалось раскрыть.

Сама концепция "распространения инфекции" как метафоры оптимизации имеет фундаментальное ограничение: в реальной эпидемиологии цель — распространить вирус максимально широко, тогда как в оптимизации цель — сконцентрироваться на лучшем решении. Эти две задачи противоположны по своей природе. Несмотря на скромные практические результаты, алгоритм EOSA представляет значительную ценность как учебный материал. Он наглядно демонстрирует процесс трансформации биологической метафоры в вычислительный алгоритм со всеми сопутствующими трудностями и компромиссами.

На примере EOSA хорошо видна типичная проблема "метафорических" алгоритмов: красивая аналогия не гарантирует эффективности. Сложность исходной модели может оказаться балластом, а не преимуществом. Процесс упрощения и адаптации — неотъемлемая часть разработки метаэвристик.

EOSA — работоспособный алгоритм оптимизации. Его производительность недостаточна для включения в число лучших методов, особенно на дискретных и высокоразмерных задачах. Эпидемиологическая метафора, положенная в основу, оказалась не самой удачной для целей оптимизации, а сложная математическая модель оригинальной статьи потребовала существенного упрощения для получения работающего алгоритма.

Тем не менее EOSA представляет интерес как учебный пример, демонстрирующий весь путь от биологической идеи до программной реализации: анализ исходной концепции, выявление проблем буквальной интерпретации, поиск компромиссов, интеграция проверенных техник и объективная оценка результатов. Этот опыт ценен для понимания природы метаэвристических алгоритмов и развития навыков их разработки.

Tab

Рисунок 3. Цветовая градация алгоритмов по соответствующим тестам

chart

Рисунок 3. Гистограмма результатов тестирования алгоритмов (по шкале от 0 до 100, чем больше, тем лучше, где 100 — максимально возможный теоретический результат, в архиве скрипт для расчета рейтинговой таблицы)

Плюсы и минусы алгоритма EOSA:

Плюсы:

  1. Справляется с некоторым несложным типом задач.

Минусы:

  1. Труднее всего справляется с дискретными функциями.

К статье прикреплён архив с актуальными версиями кодов алгоритмов. Автор статьи не несёт ответственности за абсолютную точность в описании канонических алгоритмов, во многие из них были внесены изменения для улучшения поисковых возможностей. Выводы и суждения, представленные в статьях, основываются на результатах проведённых экспериментов.



Программы, используемые в статье

#ИмяТипОписание
1#C_AO.mqh
Включаемый файл
Родительский класс популяционных алгоритмов оптимизации
2#C_AO_enum.mqh
Включаемый файл
Перечисление популяционных алгоритмов оптимизации
3TestFunctions.mqh
Включаемый файл
Библиотека тестовых функций
4
TestStandFunctions.mqh
Включаемый файл
Библиотека функций тестового стенда
5
Utilities.mqh
Включаемый файл
Библиотека вспомогательных функций
6
CalculationTestResults.mqh
Включаемый файл
Скрипт для расчета результатов в сравнительную таблицу
7
Testing AOs.mq5
СкриптЕдиный испытательный стенд для всех популяционных алгоритмов оптимизации
8
Simple use of population optimization algorithms.mq5
Скрипт
Простой пример использования популяционных алгоритмов оптимизации без визуализации
9
Test_AO_EOSA.mq5
СкриптИспытательный стенд для EOSA
Прикрепленные файлы |
EOSA.zip (393.85 KB)
Математика волатильности: Почему индикатор GRI достоин возвращения в ваш торговый терминал Математика волатильности: Почему индикатор GRI достоин возвращения в ваш торговый терминал
Статья посвящена индикатору Gopalakrishnan Range Index (GRI/ROCI), который количественно оценивает "хаотичность" рынка через логарифм диапазона цен закрытия за заданный период. Показано, как реализовать GRI в MetaTrader 5, устранить проблему отрицательных значений с помощью сдвинутого логарифма и привести шкалу к удобным "пунктам" через нормировку на Point. Далее рассматриваются практические сценарии применения GRI как фильтра волатильности и рыночных фаз.
Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Основные компоненты) Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Основные компоненты)
В данной статье представлен практический подход к адаптации современного фреймворка для анализа финансовых потоков средствами MQL5. Рассмотрены ключевые компоненты модели — Depth-Wise свёртки с остаточными связями, конусные Super Kernel Block и модуль глобальной агрегации движения (GMA).
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Машинное обучение и Data Science (Часть 35): NumPy в MQL5 – искусство создания сложных алгоритмов с меньшим объемом кода Машинное обучение и Data Science (Часть 35): NumPy в MQL5 – искусство создания сложных алгоритмов с меньшим объемом кода
Библиотека NumPy лежит в основе практически всех алгоритмов машинного обучения на языке программирования Python. В этой статье мы собираемся реализовать аналогичный модуль, содержащий набор всего сложного кода, который поможет нам создавать сложные модели и алгоритмы любого типа.