preview
Алгоритм оптимизации центральной силы — Central Force Optimization (CFO)

Алгоритм оптимизации центральной силы — Central Force Optimization (CFO)

MetaTrader 5Трейдинг |
526 0
Andrey Dik
Andrey Dik


Содержание

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


Введение

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

Алгоритм оптимизации центральной силы (Central Force Optimization, CFO) представляет собой попытку перенести принципы гравитационной кинематики в область численной оптимизации. Этот метаэвристический алгоритм, основанный на детерминированных законах движения, моделирует взаимодействие виртуальных частиц в многомерном пространстве решений, где каждая частица стремится к областям с лучшими значениями целевой функции под действием аналога гравитационной силы.

В основе CFO лежит простая и интуитивно понятная метафора: представьте себе множество пробных точек (проб), распределенных в пространстве возможных решений. Каждая проба обладает "массой", пропорциональной качеству представляемого ею решения  значению целевой функции. Подобно тому, как массивные небесные тела притягивают менее массивные, пробы с лучшими решениями создают виртуальное гравитационное поле, притягивающее другие пробы.

Движение каждой пробы подчиняется законам, аналогичным законам Ньютона, ускорение определяется суммарной силой притяжения со стороны других проб, а перемещение происходит согласно кинематическим уравнениям движения. Важной особенностью алгоритма является его детерминированная природа  в отличие от многих других метаэвристических методов, CFO не использует случайные величины в своем основном цикле работы.


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

История алгоритма CFO началась с того момента, когда исследователи задумались: что если применить законы физики к поиску оптимальных решений? Представьте себе обширную холмистую местность, где высота каждой точки соответствует качеству решения. Чем выше холм, тем лучше решение. Наша цель  найти самую высокую точку, но проблема в том, что мы не видим весь ландшафт сразу. Вместо этого у нас есть группа исследователей  назовем их пробами  которые могут перемещаться по этой местности и сообщать о высоте своего текущего положения.

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

Сила притяжения рассчитывается по правилам, похожим на закон всемирного тяготения Ньютона. Она зависит от разницы в "весе" между пробами (разницы в качестве решений) и расстояния между ними. Проба с высоким значением фитнес-функции сильно притягивает близлежащие пробы с низкими значениями, но слабо влияет на далекие пробы. Под действием этих сил каждая проба получает ускорение и начинает двигаться. Маленькие, "легкие" пробы устремляются в сторону более "тяжелых", как если бы шарики катились по склонам холмов к вершинам. С каждым шагом алгоритма пробы пересчитывают силы притяжения и корректируют свое движение. Если проба пытается выйти за установленные границы области поиска, срабатывает механизм отражения – представьте, что на краю территории стоит стена, от которой проба отскакивает обратно в допустимую область.

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

Особенность CFO в том, что он в своей основе является детерминированным алгоритмом – если запустить его дважды с одинаковым начальным распределением проб, он даст одинаковый результат. Это отличает его от многих других метаэвристических алгоритмов, которые полагаются на случайность. 

cfo-algorithm

Рисунок 1. Схема работы алгоритма оптимизации центральной силы 

На рисунке 1 показан принцип работы алгоритма оптимизации центральной силы (CFO) в пространстве поиска. Представлен ландшафт целевой функции, с цветовым градиентом от синего (низкие значения) до желтого (высокие значения). Глобальный максимум (высшая точка) и локальный максимум (низшая вершина). Три пробы (поисковые агенты), обозначенные как Проба 1, Проба 2 и Проба 3. Силы притяжения (красная стрелка), показывает, как более высокие точки притягивают пробы. Это центральная концепция CFO  лучшие решения притягивают худшие, но не наоборот. Пунктирные линии показывают траекторию проб к максимумам. Математическая формула этого процесса включает:

Расчет силы: для любых двух проб i и j, если значение функции (масса) j больше, чем у i, то j оказывает силу на i в соответствии с: F = g × [(m_j - m_i)^α / d^β] × направление, где:
  • g — гравитационная постоянная
  • m_j и m_i — значения функций (массы) проб j и i
  • α — массовый показатель (обычно 2)
  • d — расстояние между пробами
  • β — показатель расстояния (обычно 2)
  • направление — единичный вектор, направленный от пробы i к пробе j
Расчет ускорения: ускорение пробы i рассчитывается как сумма всех сил, действующих на нее.
Обновление позиции: позиция каждого зонда обновляется в соответствии с: x_new = x_old + 0,5 × a, где:
  • x_new — это новая позиция
  • x_old — текущая позиция
  • а  ускорение
Обработка границ: если проба выходит за пределы поиска, она будет возвращена обратно.

    Алгоритм итеративно применяет эти вычисления ко всем пробам, постепенно перемещая их к оптимумам в пространстве поиска. Со временем пробы имеют тенденцию группироваться вокруг глобальных и локальных максимумов, причем наибольшая концентрация обычно наблюдается вокруг глобального оптимума.

    Особенность CFO обусловлена ​​его детерминированной природой и односторонним механизмом притяжения, который направляет исследования на лучшие решения без участия случайности в его базовой форме.  Псевдокод алгоритма CFO:

    1. Инициализация:
      • Определяем границы пространства поиска.
      • Устанавливаем параметры: число проб, гравитационную постоянную, показатели степени для массы и расстояния, фактор репозиционирования.
      • Создаем заданное число проб и случайно размещаем их в пространстве поиска.
      • Для каждой пробы вычисляем начальное значение целевой функции.
    2. Основной цикл (повторяем заданное число эпох):
      • Для каждой пробы:
        • Обнуляем вектор ускорения.
        • Рассматриваем влияние других проб:
          • Если другая проба имеет лучшее значение целевой функции (большую "массу"), она создает силу притяжения.
          • Вычисляем расстояние между пробами.
          • Сила притяжения пропорциональна разнице "масс" в степени alpha и обратно пропорциональна расстоянию в степени beta.
          • Направление силы - от текущей пробы к более "тяжелой".
          • Суммируем влияния всех проб с лучшими значениями функции.
      • После расчета всех ускорений, обновляем позиции проб:
        • Новая позиция = старая позиция + половина ускорения.
        • Если проба выходит за границы пространства поиска, применяем репозиционирование:
          • При выходе за нижнюю границу: отражаем пробу внутрь пространства с учетом фактора репозиционирования.
          • При выходе за верхнюю границу: аналогично, но с другой стороны.
      • Пересчитываем значения целевой функции для всех проб в их новых позициях.
      • Обновляем информацию о лучшем найденном решении.
    3. Завершение:
      • Возвращаем лучшее найденное решение (координаты пробы с максимальным значением целевой функции).

    Перейдем к написанию кода алгоритма, определим структуру "S_CFO_Agent", которая наследует от "S_AO_Agent" и означает, что "S_CFO_Agent" получает все свойства и методы, определенные в "S_AO_Agent". 

    Поля структуры: a[]  это динамический массив, который предназначен для хранения значений ускорения. Метод "Init()" используется для инициализации структуры, изменяет размер массива "c" в зависимости от переданного параметра "coords" и изменяет размер массива ускорения "a" в зависимости от "coords". Это позволяет динамически задавать размер массива в зависимости от количества координат, инициализирует все элементы массива ускорения "a" значением "0.0", обнулением значений перед их использованием, устанавливает значение переменной "f" в минимально возможное значение для типа "double",  для инициализации фитнес-функции, чтобы обеспечить, что любое другое рассчитанное значение будет больше этого.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Структура для пробы CFO
    struct S_CFO_Agent : public S_AO_Agent
    {
        double a []; // вектор ускорения
    
        void Init (int coords)
        {
          ArrayResize (c, coords);   // координаты
          ArrayResize (a, coords);   // ускорение
          ArrayInitialize (a, 0.0);  // обнуляем ускорения
          f = -DBL_MAX;              // значение фитнес-функции
        }
    };
    //——————————————————————————————————————————————————————————————————————————————

    Класс "C_AO_CFO" наследуется от класса "C_AO" и определяет алгоритм CFO. Конструктор и деструктор. В данном случае деструктор не выполняет никаких специфических действий. C_AO_CFO ()  это конструктор, который инициализирует параметры алгоритма. Он задает значения различных переменных, таких как:

    • popSize, g, alpha, beta, initialFrep, finalFrep, noiseFactor - это параметры, которые связаны с алгоритмом и его функциями оптимизации.
    • frep  текущий фактор репозиционирования, инициализируется значением "initialFrep".
    • инициализирует массив "params", который будет содержать параметры алгоритма, после чего их значения копируются в массив с соответствующими именами.

    Методы класса:

    • SetParams ()  устанавливает параметры алгоритма, беря значения из массива "params". Он также устанавливает текущий фактор репозиционирования в "initialFrep".
    • Init ()  отвечает за инициализацию алгоритма с минимальными и максимальными значениями параметров, а также шагами, которые используются для их изменения. Параметр "epochsP" задает количество эпох для выполнения алгоритма.
    • Moving ()  отвечает за процесс перемещения проб (агентов) в алгоритме.
    • Revision ()  может использоваться для проведения ревизии или обновления состояния агентов.

    Поля класса:

    • S_CFO_Agent probe []  это массив проб (агентов), которые будут использоваться в процессе оптимизации.
    • epochs, epochNow  приватные поля, общее количество эпох и текущая эпоха.

    Дополнительные приватные методы:

    • InitialDistribution ()  используется для инициализации распределения агентов в пространстве поиска.
    • UpdateRepFactor ()  отвечает за обновление фактора репозиционирования в зависимости от текущего состояния системы.
    • CalculateAccelerations ()  используется для вычисления ускорений агентов на основе их положений и взаимодействий.
    • UpdatePositions ()  используется для обновления позиций агентов на основе их ускорений.
    • CalculateDistanceSquared ()  метод для вычисления расстояния между двумя точками в пространстве и используется для оценки взаимодействия между агентами.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Основной класс алгоритма CFO
    class C_AO_CFO : public C_AO
    {
      public: //--------------------------------------------------------------------
      ~C_AO_CFO () { }
      C_AO_CFO ()
      {
        ao_name = "CFO";
        ao_desc = "Central Force Optimization";
        ao_link = "https://www.mql5.com/ru/articles/17167";
    
        popSize     = 30;          // число проб
        g           = 1.0;         // гравитационная постоянная
        alpha       = 0.1;         // степень для массы
        beta        = 0.1;         // степень для расстояния
        initialFrep = 0.9;         // начальный фактор репозиционирования
        finalFrep   = 0.1;         // конечный фактор репозиционирования
        noiseFactor = 1.0;         // фактор случайного шума
    
        frep        = initialFrep; // текущий фактор репозиционирования
    
        ArrayResize (params, 7);
        params [0].name = "popSize";     params [0].val = popSize;
        params [1].name = "g";           params [1].val = g;
        params [2].name = "alpha";       params [2].val = alpha;
        params [3].name = "beta";        params [3].val = beta;
        params [4].name = "initialFrep"; params [4].val = initialFrep;
        params [5].name = "finalFrep";   params [5].val = finalFrep;
        params [6].name = "noiseFactor"; params [6].val = noiseFactor;
      }
    
      void SetParams ()
      {
        popSize     = (int)MathMax (1, params [0].val);
        g           = params [1].val;
        alpha       = params [2].val;
        beta        = params [3].val;
        initialFrep = params [4].val;
        finalFrep   = params [5].val;
        noiseFactor = params [6].val;
    
        frep        = initialFrep;
      }
    
      bool Init (const double &rangeMinP  [],  // минимальные значения
                 const double &rangeMaxP  [],  // максимальные значения
                 const double &rangeStepP [],  // шаг изменения
                 const int     epochsP = 0);   // количество эпох
    
      void Moving   ();
      void Revision ();
    
      //----------------------------------------------------------------------------
      double g;              // гравитационная постоянная
      double alpha;          // степень для массы
      double beta;           // степень для расстояния
      double initialFrep;    // начальный фактор репозиционирования
      double finalFrep;      // конечный фактор репозиционирования
      double noiseFactor;    // фактор случайного шума
    
      S_CFO_Agent probe [];  // массив проб
    
      private: //-------------------------------------------------------------------
      int    epochs;         // общее число эпох
      int    epochNow;       // текущая эпоха
      double frep;           // фактор репозиционирования
    
      void   InitialDistribution      ();
      void   UpdateRepFactor          ();
      void   CalculateAccelerations   ();
      void   UpdatePositions          ();
      double CalculateDistanceSquared (const double &x1 [], const double &x2 []);
    };
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Init ()" класса "C_AO_CFO" отвечает за инициализацию алгоритма CFO, принимает массивы минимальных и максимальных значений параметров, шаг изменения этих значений и количество эпох (по умолчанию 0). Вызывает метод "StandardInit" для проверки корректности переданных диапазонов значений. Если он не проходит проверку, метод возвращает "false". Устанавливает общее число эпох и текущую эпоху (0). Изменяет размер массива проб (агентов) на размер популяции (popSize). Инициализирует каждого агент в массиве "probe", вызывая метод "Init" для каждого из них с передачей координат. Если инициализация прошла успешно, метод возвращает "true". Таким образом, метод "Init" настраивает начальные параметры и условия для работы алгоритма.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Инициализация
    bool C_AO_CFO::Init (const double &rangeMinP  [], // минимальные значения
                         const double &rangeMaxP  [], // максимальные значения
                         const double &rangeStepP [], // шаг изменения
                         const int     epochsP = 0)
    {
      if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;
    
      //----------------------------------------------------------------------------
      epochs   = epochsP;
      epochNow = 0;
    
      // Инициализация проб
      ArrayResize (probe, popSize);
      for (int i = 0; i < popSize; i++) probe [i].Init (coords);
    
      return true;
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Moving ()" класса "C_AO_CFO" отвечает за основной шаг алгоритма CFO, метод начинает работать с увеличения счётчика текущей эпохи, указывая на очередной шаг алгоритма, если метод вызывается впервые, когда "revision" равен "false", он выполняет начальную инициализацию, после чего завершает выполнение. Это необходимо для установки начальных значений и состояния. Далее происходит копирование значений фитнес-функций из массива агентов во временный массив, чтобы сохранить их актуальность для дальнейших вычислений.

    Метод обновляет параметр, отвечающий за репозиционирование агентов в пространстве поиска, что важно для адаптивности алгоритма, затем метод рассчитывает ускорения агентов на основе текущего состояния и обновляет их позиции, что обеспечивает движение агентов в поисковом пространстве. В конце метод синхронизирует новые позиции агентов с основным массивом, фиксируя изменения и обеспечивая согласованность данных. Метод  "Moving ()" обеспечивает систематическое обновление состояния агентов, основанное на их фитнес-функциях и текущих позициях, в процессе работы алгоритма.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Основной шаг алгоритма
    void C_AO_CFO::Moving ()
    {
      epochNow++;
    
      // Начальная инициализация
      if (!revision)
      {
        InitialDistribution ();
        revision = true;
        return;
      }
    
      //----------------------------------------------------------------------------
      // Копируем значения фитнес-функции из базового класса
      for (int p = 0; p < popSize; p++)
      {
        probe [p].f = a [p].f;
      }
    
      // Обновляем параметр репозиционирования
      UpdateRepFactor ();
    
      // Основной цикл CFO
      CalculateAccelerations ();
      UpdatePositions ();
    
      // Синхронизируем позиции с базовым классом
      for (int p = 0; p < popSize; p++)
      {
        ArrayCopy (a [p].c, probe [p].c);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "InitialDistribution" класса "C_AO_CFO" отвечает за начальное распределение проб (агентов) в пространстве поиска. Метод проходит по каждому агенту в популяции "popSize". Для каждого агента инициализируются значения, задавая массив "a" равным нулю и устанавливая фитнес-функцию  "f" в минимальное значение. Для каждой координаты агента выполняется случайное распределение значений в заданных границах (rangeMin  и  rangeMax). После генерации случайного значения происходит его обработка с использованием метода "SeInDiSp", который приводит значение к определённому диапазону и шагу "rangeStep". Наконец, координаты агента копируются из временного массива "c" в основной массив "a", фиксируя начальное состояние для каждого агента.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Начальное распределение проб
    void C_AO_CFO::InitialDistribution ()
    {
      for (int p = 0; p < popSize; p++)
      {
        ArrayInitialize (probe [p].a, 0.0);
        probe [p].f = -DBL_MAX;
    
        // Случайное распределение
        for (int c = 0; c < coords; c++)
        {
          probe [p].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
          probe [p].c [c] = u.SeInDiSp (probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
    
        ArrayCopy (a [p].c, probe [p].c);
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "UpdateRepFactor" класса "C_AO_CFO" отвечает за обновление фактора репозиционирования (или репрессивного фактора) в процессе работы алгоритма. Метод начинается с проверки, если количество эпох "epochs" больше нуля, то вычисляется новое значение фактора репозиционирования "frep" путем линейного уменьшения от начального "initialFrep" к конечному значению "finalFrep". Это происходит на основе текущей эпохи "epochNow" в пределах общего числа эпох. Если количество эпох равно нулю, значение "frep" остается равным начальному значению "initialFrep". Это обеспечивает стабильность фактора, если алгоритм находится на начальном этапе. В конце метода происходит ограничение значения "frep" в диапазоне от 0 до 1 с помощью функций "MathMax" и "MathMin". Это гарантирует, что фактор репозиционирования не выйдет за установленные пределы, что имеет важное значение для стабильности и корректности работы алгоритма.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Обновление фактора репозиционирования
    void C_AO_CFO::UpdateRepFactor ()
    {
      // Линейное уменьшение frep от начального к конечному значению
      if (epochs > 0) frep = initialFrep - (initialFrep - finalFrep) * epochNow / epochs;
      else frep = initialFrep;
    
      // Ограничение значения
      frep = MathMax (0.0, MathMin (1.0, frep));
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "CalculateAccelerations" класса "C_AO_CFO" предназначен для вычисления ускорений для каждого агента (или пробы) в популяции на основе влияния всех других агентов. Ниже описаны основные шаги и логика работы метода. Для каждого агента в популяции (от 0 до popSize) значения ускорения "probe[p].a" обнуляются. Это делается для того, чтобы начать вычисление с нуля и накопить ускорения на основе взаимодействия с другими агентами. Для каждого агента внутренний цикл перебирает всех других агентов (от 0 до popSize). Если индекс текущего агента "p" совпадает с индексом другого агента "k", итерация пропускается через команду "continue". Вычисляется разница между фитнес-значениями двух агентов (massDiff = probe[k].f - probe[p].f). Это значение используется для определения, насколько "тяжелее" (или лучше) один агент в сравнении с другим. Если "massDiff"  больше нуля, это значит, что второй агент с индексом "k" имеет больший фитнес, чем текущий агент с индексом "p". При этом выполняется следующее:

    1. Расчет расстояния: вычисляется квадрат расстояния между текущими координатами двух агентов с помощью функции "CalculateDistanceSquared". Если это расстояние слишком мало (меньше самой малой положительной величины), итерация пропускается.

    2. Формирование направления силы: если расстояние больше "DBL_EPSILON", рассчитывается фактическое расстояние, и для каждой координаты "c" определяется направление силы, направленной от текущего агента к агенту с большим фитнесом.

    3. Формула ускорения: ускорение для текущего агента обновляется на основе формулы: probe [p].a [c] += g * MathPow (massDiff, alpha) * direction / MathPow (distance, beta), где учитываются разница масс (фитнес-значений), расстояние между пробами и определённые коэффициенты (g, alpha, beta), влияющие на силу взаимодействия.

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

    //——————————————————————————————————————————————————————————————————————————————
    //--- Вычисление ускорений
    void C_AO_CFO::CalculateAccelerations ()
    {
      for (int p = 0; p < popSize; p++)
      {
        // Обнуляем ускорение для текущей пробы
        ArrayInitialize (probe [p].a, 0.0);
    
        // Суммируем влияние всех других проб
        for (int k = 0; k < popSize; k++)
        {
          if (k == p) continue;
    
          // Разница масс (фитнес-значений)
          double massDiff = probe [k].f - probe [p].f;
    
          // Проверяем условие единичной функции U(...)
          if (massDiff > 0) // Строгое условие для единичной функции
          {
            // Вычисляем расстояние между пробами
            double distSquared = CalculateDistanceSquared (probe [k].c, probe [p].c);
            if (distSquared < DBL_EPSILON) continue;
    
            double distance = MathSqrt (distSquared);
    
            for (int c = 0; c < coords; c++)
            {
              // Направление силы
              double direction = (probe [k].c [c] - probe [p].c [c]) / distance;
    
              // Формула ускорения
              probe [p].a [c] += g * MathPow (massDiff, alpha) * direction / MathPow (distance, beta);
            }
          }
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "UpdatePositions" класса "C_AO_CFO" служит для обновления позиций агентов (или проб) в пространстве решений с учетом их ускорений, случайных факторов и ограничений на границы. В этом методе выполняются несколько шагов:

    Уменьшение коэффициента случайного шума:
    • Анализируется текущее значение коэффициента случайного шума "currentNoiseFactor", который инициализируется равным "noiseFactor".
    • Если количество эпох "epochs" больше нуля, коэффициент уменьшается пропорционально текущей эпохе "epochNow", это значит, что при увеличении числа эпох шум будет уменьшаться, что позволяет алгоритму постепенно более точно находить оптимальное решение.

    Метод проходит по всем агентам в популяции (от 0 до popSize) и для каждого агента метод проходит по всем его координатам (от 0 до coords). Позиция агента обновляется по формуле, используя текущее ускорение "probe[p].a[c]". В этом случае используется простая формула, в которой новая позиция равняется старой позиции плюс половина текущего ускорения. 

    В обновленную позицию добавляется небольшое случайное смещение, которое зависит от текущего коэффициента шума, значения гравитации "g" и случайного числа в диапазоне от -1 до 1.  Оригинальный алгоритм строго детерминирован и я решил добавить элемент случайности, об этом расскажу ниже. Это смещение добавляет элемент случайности и помогает избегать застревания в локальных минимумах. Если новая позиция превышает заданные границы (минимальные и максимальные значения, хранящиеся в rangeMin и rangeMax), позиция корректируется так, чтобы она оставалась в допустимом диапазоне и если задан шаг дискретизации, позиция агента дополнительно корректируется с использованием метода "SeInDiSp", который приводит позицию к ближайшему значению, кратному шагу. 

    //——————————————————————————————————————————————————————————————————————————————
    //--- Обновление позиций
    void C_AO_CFO::UpdatePositions ()
    {
      // Коэффициент случайного шума, уменьшается с ростом номера эпохи
      double currentNoiseFactor = noiseFactor;
      if (epochs > 0) currentNoiseFactor *= (1.0 - (double)epochNow / epochs);
    
      for (int p = 0; p < popSize; p++)
      {
        for (int c = 0; c < coords; c++)
        {
          // Обновление позиции по формуле 
          probe [p].c [c] += 0.5 * probe [p].a [c];
    
          // Добавление небольшого случайного смещения непосредственно к позиции
          probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI (-1.0, 1.0);
    
          // Репозиционирование при выходе за границы
          if (probe [p].c [c] < rangeMin [c]) probe [p].c [c] = rangeMin [c];
          if (probe [p].c [c] > rangeMax [c]) probe [p].c [c] = rangeMax [c];
    
          // Дискретизация если задан шаг
          probe [p].c [c] = u.SeInDiSp (probe [p].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "CalculateDistanceSquared" класса "C_AO_CFO" отвечает за вычисление квадрата расстояния между двумя точками в многомерном пространстве. Метод используется для оптимизации, поскольку возвращение значения квадрата расстояния устраняет необходимость в вычислении квадратного корня, что позволяет существенно сократить вычислительные затраты. Метод принимает два параметра: x1 и x2. Это массивы (const double &x1[]  и  const double &x2[]), представляющие координаты двух точек в пространстве, размерность которых равна количеству координат "coords". В начале метода создается переменная "sum", инициализированная нулем. Эта переменная используется для накопления суммы квадратов разностей координат. Метод проходит в цикле по всем координатам (от 0 до coords-1) и для каждой координаты:

    • Вычисляется разность между соответствующими элементами массивов x1 и x2 (diff = x1[i] - x2[i]).
    • Рассчитывается квадрат этой разности (diff * diff).
    • Квадрат разности добавляется к переменной "sum".
    После завершения цикла метод возвращает сумму квадратов разностей "sum", что и является квадратом расстояния между двумя точками.
    //——————————————————————————————————————————————————————————————————————————————
    //--- Вычисление расстояния (возвращает квадрат расстояния для оптимизации)
    double C_AO_CFO::CalculateDistanceSquared (const double &x1 [], const double &x2 [])
    {
      double sum = 0.0;
      for (int i = 0; i < coords; i++)
      {
        double diff = x1 [i] - x2 [i];
        sum += diff * diff;
      }
      return sum;
    }
    //——————————————————————————————————————————————————————————————————————————————

    Метод "Revision ()" класса "C_AO_CFO" отвечает за обновление лучшего найденного решения в процессе оптимизации. Метод проходит по всем агентам (или пробам) в популяции "popSize". Для каждого агента проверяется, если его функция приспособленности "a[p].f" больше текущего лучшего значения "fB". Если это так, обновляется значение "fB", устанавливая его равным функции приспособленности данного агента, далее происходит копирование координат "cB" агента, если его решение является лучшим. Таким образом, метод  Revision  находит и сохраняет наилучшее решение среди всех агентов, обновляя глобальные параметры, как только оказывается, что агент улучшил результат.

    //——————————————————————————————————————————————————————————————————————————————
    //--- Обновление лучшего решения
    void C_AO_CFO::Revision ()
    {
      for (int p = 0; p < popSize; p++)
      {
        if (a [p].f > fB)
        {
          fB = a [p].f;
          ArrayCopy (cB, a [p].c, 0, 0, WHOLE_ARRAY);
        }
      }
    }
    //——————————————————————————————————————————————————————————————————————————————
    


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

    Оригинальный, строго детерминированный алгоритм в его базовой версии показывает следующие результаты: 

    CFO|Central Force Optimization|30.0|1.0|0.1|0.1|0.9|0.1|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.34508431921321436
    25 Hilly's; Func runs: 10000; result: 0.2826594689557952
    500 Hilly's; Func runs: 10000; result: 0.25174636412054047
    =============================
    5 Forest's; Func runs: 10000; result: 0.26234538930351947
    25 Forest's; Func runs: 10000; result: 0.1852230195779629
    500 Forest's; Func runs: 10000; result: 0.15353213276989314
    =============================
    5 Megacity's; Func runs: 10000; result: 0.24923076923076923
    25 Megacity's; Func runs: 10000; result: 0.1261538461538462
    500 Megacity's; Func runs: 10000; result: 0.09492307692307768
    =============================
    All score: 1.95090 (21.68%)

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

    // Добавление небольшого случайного смещения непосредственно к позиции
    probe [p].c [c] += currentNoiseFactor * g * u.RNDfromCI (-1.0, 1.0);

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

    CFO|Central Force Optimization|30.0|1.0|0.1|0.1|0.9|0.1|1.0|
    =============================
    5 Hilly's; Func runs: 10000; result: 0.6096110105488222
    25 Hilly's; Func runs: 10000; result: 0.5495761567207647
    500 Hilly's; Func runs: 10000; result: 0.27830861578120414
    =============================
    5 Forest's; Func runs: 10000; result: 0.6341793648294705
    25 Forest's; Func runs: 10000; result: 0.4683296629644541
    500 Forest's; Func runs: 10000; result: 0.22540930020804817
    =============================
    5 Megacity's; Func runs: 10000; result: 0.5723076923076923
    25 Megacity's; Func runs: 10000; result: 0.2347692307692307
    500 Megacity's; Func runs: 10000; result: 0.09586153846153929
    =============================
    All score: 3.66835 (40.76%)

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

    Hilly

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

    Forest

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

    Megacity 

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

    По результатам тестирования, алгоритм входит в топ самых лучших популяционных алгоритмов оптимизации и занимает 42 место.

    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)
    1 ANS across neighbourhood search 0,94948 0,84776 0,43857 2,23581 1,00000 0,92334 0,39988 2,32323 0,70923 0,63477 0,23091 1,57491 6,134 68,15
    2 CLA code lock algorithm (joo) 0,95345 0,87107 0,37590 2,20042 0,98942 0,91709 0,31642 2,22294 0,79692 0,69385 0,19303 1,68380 6,107 67,86
    3 AMOm animal migration ptimization M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5,987 66,52
    4 (P+O)ES (P+O) evolution strategies 0,92256 0,88101 0,40021 2,20379 0,97750 0,87490 0,31945 2,17185 0,67385 0,62985 0,18634 1,49003 5,866 65,17
    5 CTA comet tail algorithm (joo) 0,95346 0,86319 0,27770 2,09435 0,99794 0,85740 0,33949 2,19484 0,88769 0,56431 0,10512 1,55712 5,846 64,96
    6 TETA time evolution travel algorithm (joo) 0,91362 0,82349 0,31990 2,05701 0,97096 0,89532 0,29324 2,15952 0,73462 0,68569 0,16021 1,58052 5,797 64,41
    7 SDSm stochastic diffusion search M 0,93066 0,85445 0,39476 2,17988 0,99983 0,89244 0,19619 2,08846 0,72333 0,61100 0,10670 1,44103 5,709 63,44
    8 BOAm billiards optimization algorithm M 0,95757 0,82599 0,25235 2,03590 1,00000 0,90036 0,30502 2,20538 0,73538 0,52523 0,09563 1,35625 5,598 62,19
    9 AAm archery algorithm M 0,91744 0,70876 0,42160 2,04780 0,92527 0,75802 0,35328 2,03657 0,67385 0,55200 0,23738 1,46323 5,548 61,64
    10 ESG evolution of social groups (joo) 0,99906 0,79654 0,35056 2,14616 1,00000 0,82863 0,13102 1,95965 0,82333 0,55300 0,04725 1,42358 5,529 61,44
    11 SIA simulated isotropic annealing (joo) 0,95784 0,84264 0,41465 2,21513 0,98239 0,79586 0,20507 1,98332 0,68667 0,49300 0,09053 1,27020 5,469 60,76
    12 ACS artificial cooperative search 0,75547 0,74744 0,30407 1,80698 1,00000 0,88861 0,22413 2,11274 0,69077 0,48185 0,13322 1,30583 5,226 58,06
    13 DA dialectical algorithm 0,86183 0,70033 0,33724 1,89940 0,98163 0,72772 0,28718 1,99653 0,70308 0,45292 0,16367 1,31967 5,216 57,95
    14 BHAm black hole algorithm M 0,75236 0,76675 0,34583 1,86493 0,93593 0,80152 0,27177 2,00923 0,65077 0,51646 0,15472 1,32195 5,196 57,73
    15 ASO anarchy society optimization 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5,178 57,54
    16 RFO royal flush optimization (joo) 0,83361 0,73742 0,34629 1,91733 0,89424 0,73824 0,24098 1,87346 0,63154 0,50292 0,16421 1,29867 5,089 56,55
    17 AOSm atomic orbital search M 0,80232 0,70449 0,31021 1,81702 0,85660 0,69451 0,21996 1,77107 0,74615 0,52862 0,14358 1,41835 5,006 55,63
    18 TSEA turtle shell evolution algorithm (joo) 0,96798 0,64480 0,29672 1,90949 0,99449 0,61981 0,22708 1,84139 0,69077 0,42646 0,13598 1,25322 5,004 55,60
    19 DE differential evolution 0,95044 0,61674 0,30308 1,87026 0,95317 0,78896 0,16652 1,90865 0,78667 0,36033 0,02953 1,17653 4,955 55,06
    20 SRA successful restaurateur algorithm (joo) 0,96883 0,63455 0,29217 1,89555 0,94637 0,55506 0,19124 1,69267 0,74923 0,44031 0,12526 1,31480 4,903 54,48
    21 CRO chemical reaction optimisation 0,94629 0,66112 0,29853 1,90593 0,87906 0,58422 0,21146 1,67473 0,75846 0,42646 0,12686 1,31178 4,892 54,36
    22 BIO blood inheritance optimization (joo) 0,81568 0,65336 0,30877 1,77781 0,89937 0,65319 0,21760 1,77016 0,67846 0,47631 0,13902 1,29378 4,842 53,80
    23 BSA bird swarm algorithm 0,89306 0,64900 0,26250 1,80455 0,92420 0,71121 0,24939 1,88479 0,69385 0,32615 0,10012 1,12012 4,809 53,44
    24 HS harmony search 0,86509 0,68782 0,32527 1,87818 0,99999 0,68002 0,09590 1,77592 0,62000 0,42267 0,05458 1,09725 4,751 52,79
    25 SSG saplings sowing and growing 0,77839 0,64925 0,39543 1,82308 0,85973 0,62467 0,17429 1,65869 0,64667 0,44133 0,10598 1,19398 4,676 51,95
    26 BCOm bacterial chemotaxis optimization M 0,75953 0,62268 0,31483 1,69704 0,89378 0,61339 0,22542 1,73259 0,65385 0,42092 0,14435 1,21912 4,649 51,65
    27 ABO african buffalo optimization 0,83337 0,62247 0,29964 1,75548 0,92170 0,58618 0,19723 1,70511 0,61000 0,43154 0,13225 1,17378 4,634 51,49
    28 (PO)ES (PO) evolution strategies 0,79025 0,62647 0,42935 1,84606 0,87616 0,60943 0,19591 1,68151 0,59000 0,37933 0,11322 1,08255 4,610 51,22
    29 TSm tabu search M 0,87795 0,61431 0,29104 1,78330 0,92885 0,51844 0,19054 1,63783 0,61077 0,38215 0,12157 1,11449 4,536 50,40
    30 BSO brain storm optimization 0,93736 0,57616 0,29688 1,81041 0,93131 0,55866 0,23537 1,72534 0,55231 0,29077 0,11914 0,96222 4,498 49,98
    31 WOAm wale optimization algorithm M 0,84521 0,56298 0,26263 1,67081 0,93100 0,52278 0,16365 1,61743 0,66308 0,41138 0,11357 1,18803 4,476 49,74
    32 AEFA artificial electric field algorithm 0,87700 0,61753 0,25235 1,74688 0,92729 0,72698 0,18064 1,83490 0,66615 0,11631 0,09508 0,87754 4,459 49,55
    33 AEO artificial ecosystem-based optimization algorithm 0,91380 0,46713 0,26470 1,64563 0,90223 0,43705 0,21400 1,55327 0,66154 0,30800 0,28563 1,25517 4,454 49,49
    34 ACOm ant colony optimization M 0,88190 0,66127 0,30377 1,84693 0,85873 0,58680 0,15051 1,59604 0,59667 0,37333 0,02472 0,99472 4,438 49,31
    35 BFO-GA bacterial foraging optimization - ga 0,89150 0,55111 0,31529 1,75790 0,96982 0,39612 0,06305 1,42899 0,72667 0,27500 0,03525 1,03692 4,224 46,93
    36 SOA simple optimization algorithm 0,91520 0,46976 0,27089 1,65585 0,89675 0,37401 0,16984 1,44060 0,69538 0,28031 0,10852 1,08422 4,181 46,45
    37 ABHA artificial bee hive algorithm 0,84131 0,54227 0,26304 1,64663 0,87858 0,47779 0,17181 1,52818 0,50923 0,33877 0,10397 0,95197 4,127 45,85
    38 ACMO atmospheric cloud model optimization 0,90321 0,48546 0,30403 1,69270 0,80268 0,37857 0,19178 1,37303 0,62308 0,24400 0,10795 0,97503 4,041 44,90
    39 ADAMm adaptive moment estimation M 0,88635 0,44766 0,26613 1,60014 0,84497 0,38493 0,16889 1,39880 0,66154 0,27046 0,10594 1,03794 4,037 44,85
    40 CGO chaos game optimization 0,57256 0,37158 0,32018 1,26432 0,61176 0,61931 0,62161 1,85267 0,37538 0,21923 0,19028 0,78490 3,902 43,35
    41 ATAm artificial tribe algorithm M 0,71771 0,55304 0,25235 1,52310 0,82491 0,55904 0,20473 1,58867 0,44000 0,18615 0,09411 0,72026 3,832 42,58
    42 CFO central force optimization 0,60961 0,54958 0,27831 1,43750 0,63418 0,46833 0,22541 1,32792 0,57231 0,23477 0,09586 0,90294 3,668 40,76
    43 ASHA artificial showering algorithm 0,89686 0,40433 0,25617 1,55737 0,80360 0,35526 0,19160 1,35046 0,47692 0,18123 0,09774 0,75589 3,664 40,71
    44 ASBO adaptive social behavior optimization 0,76331 0,49253 0,32619 1,58202 0,79546 0,40035 0,26097 1,45677 0,26462 0,17169 0,18200 0,61831 3,657 40,63
    45 MEC mind evolutionary computation 0,69533 0,53376 0,32661 1,55569 0,72464 0,33036 0,07198 1,12698 0,52500 0,22000 0,04198 0,78698 3,470 38,55
    RW random walk 0,48754 0,32159 0,25781 1,06694 0,37554 0,21944 0,15877 0,75375 0,27969 0,14917 0,09847 0,52734 2,348 26,09


    Выводы

    Алгоритм CFO, основанный на принципах гравитационного притяжения между объектами, представляет собой интересную попытку применить законы физики к решению сложных оптимизационных задач. После тщательного тестирования на нашем стандартном наборе функций, CFO продемонстрировал эффективность чуть более 40% от теоретически возможного максимума, что позволило ему занять 42-е место среди 45 лучших популяционных алгоритмов оптимизации. Необходимо отметить, что даже этот скромный результат был достигнут только благодаря модификации оригинального алгоритма путем введения случайного компонента в его изначально детерминированную природу.

    Несмотря на относительно невысокие показатели эффективности, CFO обладает рядом привлекательных особенностей. Прежде всего, это алгоритм с чрезвычайно ясной физической интерпретацией, что делает его интуитивно понятным. Простота основной концепции – пробы, представляющие потенциальные решения, притягиваются к лучшим решениям подобно тому, как материальные тела притягиваются к более массивным объектам – обеспечивает прозрачность работы алгоритма и легкость его реализации.

    Однако тестирование выявило и существенные ограничения CFO, которые требуют пересмотра или улучшения. Чрезмерная зависимость от начального распределения проб является одной из ключевых проблем – из-за детерминированного механизма движения, начальные позиции проб во многом предопределяют конечный результат поиска. 

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

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

    chart

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

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

    Плюсы:

    1. Хорошо работает на функциях средней размерности.

    Минусы:

    1. Недостаточно хорошо работает на функциях малой и высокой размерности.
    2. Слабые поисковые способности на дискретных функциях.

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


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

    # Имя Тип Описание
    1 #C_AO.mqh
    Включаемый файл
    Родительский класс популяционных алгоритмов
    оптимизации
    2 #C_AO_enum.mqh
    Включаемый файл
    Перечисление популяционных алгоритмов оптимизации
    3 TestFunctions.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_CFO.mq5
    Скрипт Испытательный стенд для CFO
    Прикрепленные файлы |
    CFO.ZIP (350.99 KB)
    Арбитражный трейдинг Forex: Анализ движений синтетических валют и их возврат к среднему Арбитражный трейдинг Forex: Анализ движений синтетических валют и их возврат к среднему
    В статье попробуем рассмотреть движения синтетических валют на связке Python + MQL5 и понять, насколько реален арбитраж на Форекс сегодня. А также: готовый код Python для анализа синтетических валют и подробней о том, что такое синтетические валюты на Форекс.
    Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (Окончание) Нейросети в трейдинге: Адаптивное обнаружение рыночных аномалий (Окончание)
    Продолжаем построение алгоритмов, заложенные в основу фреймворка DADA — передового инструмента для обнаружения аномалий во временных рядах. Этот подход позволяет эффективно отличать случайные флуктуации от значимых отклонений. В отличие от классических методов, DADA динамически адаптируется к разным типам данных, выбирая оптимальный уровень сжатия в каждом конкретном случае.
    Разработка системы репликации (Часть 73): Неожиданный способ оповещений (II) Разработка системы репликации (Часть 73): Неожиданный способ оповещений (II)
    В этой статье мы рассмотрим, как передавать информацию в режиме реального времени между индикатором и сервисом, а также разберемся, почему могут возникнуть проблемы при изменении таймфрейма и как их решать. В качестве бонуса вы получите доступ к последней версии приложения репликации/моделирования.
    Возможности Мастера MQL5, которые вам нужно знать (Часть 37): Регрессия гауссовских процессов с линейными ядрами и ядрами Матерна Возможности Мастера MQL5, которые вам нужно знать (Часть 37): Регрессия гауссовских процессов с линейными ядрами и ядрами Матерна
    Линейные ядра — простейшая матрица, используемая в машинном обучении для линейной регрессии и опорных векторных машин. Ядро Матерна (Matérn) представляет собой более универсальную версию радиальной базисной функции (Radial Basis Function, RBF), которую мы рассматривали в одной из предыдущих статей, и оно отлично подходит для отображения функций, которые не настолько гладкие, как предполагает RBF. Создадим специальный класс сигналов, который использует оба ядра для прогнозирования условий на покупку и продажу.