preview
Алгоритм дифференциального поиска — Differential Search Algorithm (DSA)

Алгоритм дифференциального поиска — Differential Search Algorithm (DSA)

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

Содержание

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


Введение

В данной статье рассмотрим Differential Search Algorithm — алгоритм оптимизации, имитирующий миграцию суперорганизма (стаи) в поисках лучших условий. DSA был предложен Pinar Civicioglu в 2012 году как альтернатива классическим алгоритмам, таким как PSO (Particle Swarm Optimization) и DE (Differential Evolution). Он моделирует поведение популяции организмов, которые перемещаются в пространстве решений, имитируя естественную миграцию с элементами случайности и направленного поиска. Вот одна из публикаций этого алгоритма. 


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

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

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

1. Суперорганизм (Популяция). Это набор особей (решений). Каждая особь — это точка в пространстве поиска.

Пример: Ищем оптимум функции f(x, y). Популяция из 5 особей:

Особь 0: (2.5, 3.1) фитнес = 0.65
Особь 1: (1.2, 4.0) фитнес = 0.42
Особь 2: (3.8, 2.5) фитнес = 0.78 ← лучшая
Особь 3: (0.5, 1.0) фитнес = 0.31
Особь 4: (2.0, 2.0) фитнес = 0.55

2. Направление миграции (Direction). Куда будет двигаться каждая особь? Есть 4 стратегии:

B-DSA (Bijective) — Случайная перестановка. Каждая особь движется к случайной другой особи.

Пример: Перестановка индексов: [0,1,2,3,4] → [3,0,4,2,1]

Особь 0 → движется к особи 3: направление = (0.5, 1.0)
Особь 1 → движется к особи 0: направление = (2.5, 3.1)
Особь 2 → движется к особи 4: направление = (2.0, 2.0)
Особь 3 → движется к особи 2: направление = (3.8, 2.5)
Особь 4 → движется к особи 1: направление = (1.2, 4.0)

S-DSA (Surjective) — К случайным лучшим. Каждая особь движется к случайному решению из топ-N лучших.

Пример: Сортировка по фитнесу: [2, 0, 4, 1, 3] (от лучшего к худшему)

Для особи 0: случайно выбираем из топ-3 → выбрали особь 2
Для особи 1: случайно выбираем из топ-2 → выбрали особь 0 и т.д.

E1-DSA (Elitist #1) — Все особи двигаются к одному случайному лидеру. Выбирается один случайный лидер из топа, и все перемещаются в его сторону.

Случайно выбрали из топ-2: особь 0; все особи движутся к (2.5, 3.1)

Пример: Лучшая особь: #2 с координатами (3.8, 2.5). Все особи движутся к (3.8, 2.5)

E2-DSA (Elitist #2) — Все особи движутся к самому лучшему решению.

Пример: Лучшая особь: #2 с координатами (3.8, 2.5) Все особи движутся к (3.8, 2.5)

3. Фактор масштаба (Scale Factor). Определяет размер шага при движении. Вычисляется по формуле: Scale = Gamma(2·rand) × (rand - rand)

Пример вычисления: rand1 = 0.7 → shape = 2 × 0.7 = 1.4 Gamma(1.4) ≈ 0.89; rand2 = 0.8 rand3 = 0.3 Scale = 0.89 × (0.8 - 0.3) = 0.89 × 0.5 = 0.445

Scale может быть отрицательным, что позволяет двигаться в противоположном направлении. rand2 = 0.2, rand3 = 0.9; Scale = 0.89 × (0.2 - 0.9) = 0.89 × (-0.7) = -0.623

4. Вычисление Stopover (промежуточной позиции). Формула движения: stopover = current + scale × (direction - current)

Пример для особи 0: current = (2.5, 3.1); direction = (0.5, 1.0) ← из B-DSA; scale = 0.445

stopover_x = 2.5 + 0.445 × (0.5 - 2.5) = 2.5 + 0.445 × (-2.0) = 2.5 - 0.89 = 1.61
stopover_y = 3.1 + 0.445 × (1.0 - 3.1) = 3.1 + 0.445 × (-2.1) = 3.1 - 0.93 = 2.17 stopover = (1.61, 2.17)

С отрицательным scale: scale = -0.623

stopover_x = 2.5 + (-0.623) × (0.5 - 2.5) = 2.5 + (-0.623) × (-2.0) = 2.5 + 1.25 = 3.75
stopover_y = 3.1 + (-0.623) × (1.0 - 3.1) = 3.1 + (-0.623) × (-2.1) = 3.1 + 1.31 = 4.41
stopover = (3.75, 4.41) ← движение в противоположную сторону!

5. Карта мутации (Map). Определяет, какие координаты изменить, а какие оставить

map[i] = 0 → координата i БУДЕТ изменена
map[i] = 1 → координата i ОСТАНЕТСЯ прежней

Стратегия 1: Random-mutation #1. Каждая координата независимо решает: меняться или нет

Пример (2D): Особь 0: map = [0, 1] → x меняется, y остается; Особь 1: map = [1, 0] → x остается, y меняется; Особь 2: map = [0, 0] → обе координаты меняются; Особь 3: map = [1, 1] → обе остаются (нет изменений)

Стратегия 2: Differential-mutation. Меняется только одна случайная координата

Пример (5D): Особь 0: случайный индекс = 2 → map = [1, 1, 0, 1, 1]; Особь 1: случайный индекс = 0 → map = [0, 1, 1, 1, 1]; Особь 2: случайный индекс = 4 → map = [1, 1, 1, 1, 0]

Стратегия 3: Random-mutation #2. Меняется несколько случайных координат (количество зависит от p2)

Пример (5D, p2=0.3, numModify = ceil(0.15 × 5) = 1). Особь 0: случайные индексы = [2] → map = [1, 1, 0, 1, 1]; Особь 1: случайные индексы = [0, 3] → map = [0, 1, 1, 0, 1]

6. Применение карты

Пример: current = (2.5, 3.1); stopover = (1.61, 2.17); map = [0, 1]

Результат: x: map[0]=0 → берем stopover_x = 1.61 y: map[1]=1 → берем current_y = 3.1 Финальная позиция = (1.61, 3.1)

7. Селекция. После вычисления фитнеса новой позиции, решаем, принять её или нет

Особь 0: старая позиция: (2.5, 3.1), фитнес = 0.65 новая позиция: (1.61, 3.1), фитнес = 0.72 0.72 > 0.65 → ПРИНИМАЕМ новую позицию
Особь 1: старая позиция: (1.2, 4.0), фитнес = 0.42 новая позиция: (0.8, 3.5), фитнес = 0.38 0.38 < 0.42 → ОТКЛОНЯЕМ, остаемся на старой позиции

DSA

Рисунок 1. Схема работы алгоритма DSA

На иллюстрации показано 6 шагов одной итерации: исходная популяция — это точки в пространстве поиска, лучшая отмечена зелёным цветом, генерация направления (E2-DSA), когда все особи движутся к лучшей, показаны стрелки направлений, Scale Factor — график распределения размера шага: много мелких шагов, редкие большие прыжки, вычисление Stopover — формула и визуализация движения точек к новым позициям, применение Map, показано, как маска определяет какие координаты менять (0=изменить, 1=оставить), селекция — примеры принятия и отклонения новых позиций.

Так же дополнительно показан цикл алгоритма — последовательность вызовов: Init → Moving → CalcFitness → Revision → повтор и в конце, 4 стратегии направления — краткое описание методов B-DSA, S-DSA, E1-DSA, E2-DSA.

Переходим к написанию псевдокода алгоритма.

1. ИНИЦИАЛИЗАЦИЯ
   ДЛЯ каждой особи i = 1..popSize
     x[i] = случайная точка в пределах bounds
     f[i] = вычислить фитнес(x[i])
   КОНЕЦ ДЛЯ
   xBest = лучшая особь
   fBest = лучший фитнес

2. ГЛАВНЫЙ ЦИКЛ (iter = 1..maxIter)
   
   2.1. Сохранить текущее состояние
        xPrev = x
        fPrev = f
   
   2.2. ДЛЯ каждой особи i
   
        a) Выбрать направление (зависит от method):
           - B-DSA (1): direction = случайная другая особь
           - S-DSA (2): direction = случайная из топ-лучших
           - E1-DSA (3): direction = один случайный лидер (для всех одинаковый)
           - E2-DSA (4): direction = самая лучшая особь (для всех одинаковый)
        
        b) Вычислить масштаб:
           scale = Gamma(2·rand) × (rand - rand)
        
        c) Вычислить промежуточную позицию:
           stopover[d] = xPrev[i][d] + scale × (direction[d] - xPrev[i][d])
           для всех координат d = 1..dim
        
        d) Сгенерировать карту мутации:
           prob = случайное число [0, 1]
           ЕСЛИ prob < p1 ТОГДА
             // Random-mutation #1: каждая координата ~50% меняется
             map[d] = случайно 0 или 1
           ИНАЧЕ ЕСЛИ prob > (1 - p1) ТОГДА
             // Differential-mutation: только ОДНА координата
             randomCoord = случайный индекс
             map[d] = 1 для всех d, кроме randomCoord
             map[randomCoord] = 0
           ИНАЧЕ
             // Random-mutation #2: несколько координат
             numModify = ceil(rand × p2 × dim)
             map = все 1
             выбрать numModify случайных координат и поставить 0
           КОНЕЦ ЕСЛИ
        
        e) Применить карту:
           ДЛЯ d = 1..dim
             ЕСЛИ map[d] == 0 ТОГДА
               x[i][d] = stopover[d]  // изменить
             ИНАЧЕ
               x[i][d] = xPrev[i][d]  // оставить старое
             КОНЕЦ ЕСЛИ
           КОНЕЦ ДЛЯ
        
        f) Проверить границы:
           ДЛЯ d = 1..dim
             ЕСЛИ x[i][d] выходит за bounds ТОГДА
               ЕСЛИ rand < 0.5 ТОГДА
                 x[i][d] = случайное в пределах bounds
               ИНАЧЕ
                 x[i][d] = ближайшая граница
               КОНЕЦ ЕСЛИ
             КОНЕЦ ЕСЛИ
           КОНЕЦ ДЛЯ
   
   КОНЕЦ ДЛЯ
   
   2.3. Вычислить фитнес для всех новых позиций
        ДЛЯ i = 1..popSize
          f[i] = фитнес(x[i])
        КОНЕЦ ДЛЯ
   
   2.4. Селекция (жадный отбор)
        ДЛЯ i = 1..popSize
          ЕСЛИ f[i] > fPrev[i] ТОГДА
            // принять новую позицию (уже в x[i])
          ИНАЧЕ
            // откатиться к старой
            x[i] = xPrev[i]
            f[i] = fPrev[i]
          КОНЕЦ ЕСЛИ
        КОНЕЦ ДЛЯ
   
   2.5. Обновить глобальное лучшее
        ЕСЛИ max(f) > fBest ТОГДА
          xBest = x[индекс max(f)]
          fBest = max(f)
        КОНЕЦ ЕСЛИ

КОНЕЦ ЦИКЛА

ВЕРНУТЬ xBest, fBest

Теперь можем приступить к самой реализации в коде. Структура данных под названием "S_DSA_Map" служит для управления информацией об "активных" объектах, связанных с определенным количеством "координат". Основной частью этой структуры является массив, который называется "active". Размер этого массива не фиксирован при объявлении, что позволяет ему адаптироваться. По сути, каждый элемент этого массива будет соответствовать одной "координате". Информация, хранящаяся в каждом элементе "active", изначально равна нулю, что подразумевает неактивное состояние по умолчанию.

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

//—————————————————————————————————————— ——————————————————————————————
struct S_DSA_Map
{
    int active []; // map of active individuals for each coordinate

    void Init (int coords)
    {
      ArrayResize (active, coords);
      ArrayInitialize (active, 0);
    }
};
//————————————————————————————————————————————————————————————————————

Класс "C_AO_DSA" является производным от базового класса "C_AO" и предназначен для реализации алгоритма дифференциального поиска. 

Параметры алгоритма:

  • popSize — размер популяции.
  • method — выбор используемого метода дифференциального поиска. По умолчанию установлен в 1, что соответствует методу "B-DSA". Возможны значения: 1 (B-DSA), 2 (S-DSA), 3 (E1-DSA), 4 (E2-DSA).
  • p1 — контрольный параметр для стратегии мутации.
  • p2 — второй контрольный параметр для стратегии мутации.

Конструктор класса инициализирует внутреннюю структуру "params", в которую записываются имена и текущие значения "popSize", "method", "p1" и "p2". Метод "SetParams" отвечает за обновление значений параметров выше данных на основе структуры "params". После получения новых значений, метод производит проверку и корректировку параметров, чтобы они находились в допустимых пределах.

Основные методы:

  • Init— для инициализации алгоритма. Он принимает диапазоны и шаг поиска, а также количество эпох (итераций),
  • Moving — отвечает за перемещение и обновление объектов в рамках алгоритма,
  • Revision — используется для пересмотра или коррекции решений.

Приватные данные и методы:

  • direction— массив, хранящий информацию об "агентах" (элементах популяции) и их направлениях,
  • mapArray— массив структур "S_DSA_Map", которые, как описано в предыдущем разделе, предназначены для управления состоянием активных элементов по координатам,
  • InitializePopulation— метод для создания начальной популяции,
  • GenerateDirection— метод для генерации направлений движения агентов, зависящий от выбранного "methodType",
  • GenerateMap— метод для генерации карты  "S_DSA_Map",
  • GenerateScaleFactor — метод для генерации масштабирующего фактора, который используется в мутациях,
  • GammaRandom— метод для генерации случайных чисел из гамма-распределения с заданными параметрами формы и масштаба,
  • BoundaryControl— метод для контроля выхода объектов за установленные границы, с применением указанного индекса.

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

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

  C_AO_DSA ()
  {
    ao_name = "DSA";
    ao_desc = "Differential Search Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/20346";

    popSize = 50;
    method  = 1;    // 1-B-DSA, 2-S-DSA, 3-E1-DSA, 4-E2-DSA
    p1      = 0.3;  // mutation strategy control [0.0, 0.3]
    p2      = 0.3;  // mutation strategy control [0.0, 0.3]

    ArrayResize (params, 4);
    params [0].name = "popSize";  params [0].val = popSize;
    params [1].name = "method";   params [1].val = method;
    params [2].name = "p1";       params [2].val = p1;
    params [3].name = "p2";       params [3].val = p2;
  }

  void SetParams ()
  {
    popSize = (int)params [0].val;
    method  = (int)params [1].val;
    p1      = params [2].val;
    p2      = params [3].val;

    if (method < 1) method = 1;
    if (method > 4) method = 4;
    if (p1 < 0.0) p1 = 0.0;
    if (p1 > 0.3) p1 = 0.3;
    if (p2 < 0.0) p2 = 0.0;
    if (p2 > 0.3) p2 = 0.3;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    method;
  double p1;
  double p2;

  private: //---------------------------------------------------------
  S_AO_Agent direction [];
  S_DSA_Map  mapArray  [];

  void   InitializePopulation  ();
  void   GenerateDirection     (int methodType);
  void   GenerateMap           ();
  double GenerateScaleFactor   ();
  double GammaRandom           (double shape, double scale);
  void   BoundaryControl       (int idx);
};
//————————————————————————————————————————————————————————————————————

Функция инициализации "Init" для класса "C_AO_DSA". Сначала вызывается функция "StandardInit" которая, выполняет общие шаги инициализации, необходимые для всех алгоритмов, основанных на "C_AO". Если эта базовая инициализация завершается неудачно, функция класса немедленно прекращает работу и возвращает ложное значение.

Инициализация массива направлений. Создается массив "direction", размер которого определяется переменной "popSize" (размер популяции). Затем, для каждого элемента в этом массиве "direction", вызывается его собственный метод "Init". При этом каждому элементу передается значение "coords", которое представляет количество "координат", используемых в алгоритме.

Инициализация массива карт. Создается массив "mapArray", также имеющий размер, равный "popSize". Аналогично массиву "direction", для каждого элемента в "mapArray" вызывается его метод "Init". И в этом случае передается значение "coords", что позволяет правильно настроить структуру "S_DSA_Map" для каждого элемента. Если все предыдущие шаги успешно выполнены, функция "Init" возвращает истинное значение.

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

  //------------------------------------------------------------------
  ArrayResize (direction, popSize);
  for (int i = 0; i < popSize; i++) direction [i].Init (coords);

  ArrayResize (mapArray, popSize);
  for (int i = 0; i < popSize; i++) mapArray [i].Init (coords);

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

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

Инициализация популяции (при первой итерации)Если флаг "revision" установлен в ложное значение (что означает, что это первая итерация), то вызывается метод "InitializePopulation" для создания начальной популяции агентов. После этого флаг "revision" устанавливается в истинное значение, и выполнение метода завершается до следующей итерации.

Сохранение текущего состояния. На каждой последующей итерации (когда "revision" истинно) происходит сохранение текущих координат "c" и их значений функции приспособленности "f" для каждого агента. Сохраненные координаты помещаются в массив "cP" (предыдущие координаты), а значения приспособленности — в "fP".

Генерация направления и масштабирующего коэффициента. Вызывается метод "GenerateDirection", он определяет "направление" для каждого агента. Выбор конкретной стратегии генерации направления осуществляется на основе параметра "method". Затем вызывается метод "GenerateScaleFactor" для получения масштабирующего коэффициента. Этот коэффициент влияет на величину шага смещения.

Вычисление новой позиции (предварительное). Для каждого агента и каждой координаты происходит вычисление новой предварительной позиции. Новая координата рассчитывается как: (предыдущая координата) + (масштабирующий коэффициент) * (разница между направлением и предыдущей координатой). Этот этап фактически формирует "кандидатскую" новую позицию.

Генерация карты активных/пассивных координат. Вызывается метод "GenerateMap". Этот метод, как было упомянуто ранее, создает структуру "mapArray", которая для каждого агента указывает, какие координаты являются "активными" (требуют обновления) и какие "пассивными" (должны оставаться неизменными).

Корректировка позиции с учетом карты и границ. Для каждого агента происходит проверка карты активных/пассивных координат. Если координата помечена как "активная", то ее новая позиция устанавливается обратно в значение предыдущей координаты. То есть, эта координата не обновляется. Если координата "пассивная", то новое значение, вычисленное на шаге 4, сохраняется. После корректировки координат вызывается метод "BoundaryControl" для каждого агента, чтобы убедиться, что все координаты находятся в допустимых пределах поиска.

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

    //————————————————————————————————————————————————————————————————————
    void C_AO_DSA::Moving ()
    {
      //------------------------------------------------------------------
      // Первая итерация: инициализация популяции
      if (!revision)
      {
        InitializePopulation ();
        revision = true;
        return;
      }
    
      //------------------------------------------------------------------
      // Сохраняем текущие координаты и фитнес в cP и fP
      for (int i = 0; i < popSize; i++)
      {
        ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);
        a [i].fP = a [i].f;
      }
    
      //------------------------------------------------------------------
      // Генерация направления на основе cP (старых координат)
      GenerateDirection (method);
    
      // Генерация Scale-Factor
      double scale = GenerateScaleFactor ();
    
      //------------------------------------------------------------------
      // Вычисляем stopover site и записываем в c
      for (int i = 0; i < popSize; i++)
      {
        for (int c = 0; c < coords; c++)
        {
          double diff = direction [i].c [c] - a [i].cP [c];
          a [i].c [c] = a [i].cP [c] + scale * diff;
        }
      }
    
      //------------------------------------------------------------------
      // Генерация карты активных/пассивных координат
      GenerateMap ();
    
      //------------------------------------------------------------------
      // Восстанавливаем координаты где map > 0 (сохранить оригинальные)
      for (int i = 0; i < popSize; i++)
      {
        for (int c = 0; c < coords; c++)
        {
          if (mapArray [i].active [c] > 0)
          {
            a [i].c [c] = a [i].cP [c];
          }
        }
    
        // Контроль границ
        BoundaryControl (i);
      }
    }
    //————————————————————————————————————————————————————————————————————
    

    Метод "InitializePopulation" класса "C_AO_DSA". Его основная задача — создать и инициализировать начальное состояние популяции агентов. Метод проходит по каждому агенту в популяции, от первого до последнего (количество агентов определяется переменной "popSize"). Для каждого агента метод последовательно обрабатывает все его координаты. Для каждой координаты текущего агента генерируется случайное число в пределах заданного диапазона (от "rangeMin" до "rangeMax"). Это делается с помощью функции "u.RNDfromCI".

    Полученное случайное значение затем проходит дополнительную корректировку с помощью функции "u.SeInDiSp". Скорректированное значение присваивается соответствующей координате "c" текущего агента. Таким образом, "InitializePopulation" отвечает за генерацию начальной, случайной, но при этом корректной (в рамках заданных диапазонов и шагов) конфигурации каждого агента в популяции, готовя ее к дальнейшей работе алгоритма.

    //————————————————————————————————————————————————————————————————————
    void C_AO_DSA::InitializePopulation ()
    {
      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]);
        }
      }
    }
    //————————————————————————————————————————————————————————————————————

    Следующий метод "GenerateDirection" класса "C_AO_DSA" отвечает за определение вектора направления для каждого агента в популяции. Метод принимает параметр "methodType", который определяет, какой именно алгоритм поиска направления будет использован. Различные стратегии генерации направления (в зависимости от "methodType"):

    Тип 1: B-DSA (Bijective) - Случайная перестановка:
    • Создается массив индексов, соответствующий всем агентам.
    • Применяется алгоритм перемешивания Фишера-Йетса, чтобы случайно переупорядочить эти индексы.
    • Для каждого агента в популяции его вектор направления "direction" устанавливается равным координатам агента, чей индекс оказался на соответствующей позиции после случайной перестановки.
    • Цель: каждый агент "смотрит" в сторону другого случайного агента.
    Тип 2: S-DSA (Surjective) - К случайным лучшим:
    • Создается массив индексов агентов.
    • Агенты сортируются в порядке убывания их приспособленности "fP".
    • Для каждого агента случайным образом определяется число "лучших" агентов (topCount), из которых будет выбран кандидат. Из этой группы "лучших" случайным образом выбирается один агент. Вектор направления текущего агента устанавливается равным предыдущим координатам (cP) выбранного "лучшего" агента.
    • Цель: агенты "стремятся" к случайным выборам из более успешных агентов.
    Тип 3: E1-DSA (Elitist #1) - К одному случайному из лучших:
    • Аналогично типу 2, агенты сортируются по убыванию приспособленности.
    • Случайным образом определяется группа "лучших" агентов (topCount).
    • Из этой группы выбирается один случайный "лучший" агент.
    • Все агенты популяции устанавливают свой вектор направления равным предыдущим координатам этого единственного выбранного "лучшего" агента.
    • Цель: все агенты "ориентируются" на случайно выбранного, но все еще из лучших, агента.
    Тип 4: E2-DSA (Elitist #2) - К самому лучшему:
    • Находится агент с наилучшей приспособленностью (bestIdx).
    • Все агенты популяции устанавливают свой вектор направления равным предыдущим координатам этого абсолютно-лучшего агента.
    • Цель: все агенты "копируют" или "стремятся" к самому сильному представителю популяции.

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

      //————————————————————————————————————————————————————————————————————
      void C_AO_DSA::GenerateDirection (int methodType)
      {
        // Используем cP (старые координаты) для генерации направления
        switch (methodType)
        {
          case 1: // B-DSA (Bijective) - случайная перестановка
          {
            int indices [];
            ArrayResize (indices, popSize);
            for (int i = 0; i < popSize; i++) indices [i] = i;
      
            // Fisher-Yates shuffle
            for (int i = popSize - 1; i > 0; i--)
            {
              int j = u.RNDminusOne (i + 1);
              int temp = indices [i];
              indices [i] = indices [j];
              indices [j] = temp;
            }
      
            for (int i = 0; i < popSize; i++)
            {
              ArrayCopy (direction [i].c, a [indices [i]].cP, 0, 0, coords);
            }
            break;
          }
      
          case 2: // S-DSA (Surjective) - к случайным лучшим
          {
            int sortedIdx [];
            ArrayResize (sortedIdx, popSize);
            for (int i = 0; i < popSize; i++) sortedIdx [i] = i;
      
            // Сортировка по fP (старому фитнесу) по убыванию
            for (int i = 0; i < popSize - 1; i++)
            {
              for (int j = 0; j < popSize - i - 1; j++)
              {
                if (a [sortedIdx [j]].fP < a [sortedIdx [j + 1]].fP)
                {
                  int temp = sortedIdx [j];
                  sortedIdx [j] = sortedIdx [j + 1];
                  sortedIdx [j + 1] = temp;
                }
              }
            }
      
            for (int i = 0; i < popSize; i++)
            {
              int topCount = (int)MathCeil (u.RNDfromCI (0.0, 1.0) * popSize);
              if (topCount < 1) topCount = 1;
              int selectedIdx = sortedIdx [u.RNDminusOne (topCount)];
              ArrayCopy (direction [i].c, a [selectedIdx].cP, 0, 0, coords);
            }
            break;
          }
      
          case 3: // E1-DSA (Elitist #1) - к одному случайному из лучших
          {
            int sortedIdx [];
            ArrayResize (sortedIdx, popSize);
            for (int i = 0; i < popSize; i++) sortedIdx [i] = i;
      
            for (int i = 0; i < popSize - 1; i++)
            {
              for (int j = 0; j < popSize - i - 1; j++)
              {
                if (a [sortedIdx [j]].fP < a [sortedIdx [j + 1]].fP)
                {
                  int temp = sortedIdx [j];
                  sortedIdx [j] = sortedIdx [j + 1];
                  sortedIdx [j + 1] = temp;
                }
              }
            }
      
            int topCount = (int)MathCeil (u.RNDfromCI (0.0, 1.0) * popSize);
            if (topCount < 1) topCount = 1;
            int bestIdx = sortedIdx [u.RNDminusOne (topCount)];
      
            for (int i = 0; i < popSize; i++)
            {
              ArrayCopy (direction [i].c, a [bestIdx].cP, 0, 0, coords);
            }
            break;
          }
      
          case 4: // E2-DSA (Elitist #2) - к самому лучшему
          {
            int bestIdx = 0;
            double bestFit = a [0].fP;
            for (int i = 1; i < popSize; i++)
            {
              if (a [i].fP > bestFit)
              {
                bestFit = a [i].fP;
                bestIdx = i;
              }
            }
      
            for (int i = 0; i < popSize; i++)
            {
              ArrayCopy (direction [i].c, a [bestIdx].cP, 0, 0, coords);
            }
            break;
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

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

      Основное условие выбора стратегии мутации. Сравнивается два случайных числа, генерируемых в диапазоне [0.0, 1.0] одно с другим. Вероятность выполнения основного условия (т.е. "true") составляет 0.5, это означает, что выбор между двумя основными ветками алгоритма (первая с двумя под-ветками "Random-mutation #1" и "Differential-mutation", и вторая "Random-mutation #2") происходит случайным образом с вероятностью 50%.

      Случай 1: Random-mutation #1. Для каждого агента и каждой его координаты, генерируется еще одно случайное число. Если оно меньше другого случайного числа, то "mapArray [i].active [c]" устанавливается в "1". Опять же, сравнение двух случайных чисел означает, что активация каждой отдельной координаты происходит с вероятностью 0.5. Цель: случайным образом активировать (помечать для мутации) отдельные координаты у разных агентов.

      • Differential-mutation (иначе): Для каждого агента "mapArray [i].active" целиком инициализируется как "1" (все координаты активны для мутации). Затем случайным образом выбирается одна координата (modifyCoord) у каждого агента, и для этой конкретной координаты "mapArray [i].active [modifyCoord]"  устанавливается в "0" (отключается). Цель: у каждого агента мутирует только одна, случайно выбранная координата.

      Случай 2: Random-mutation #2. Определяется количество координат (numModify) для мутации для каждого агента. Оно зависит от "p2_iter" и общего количества координат. Количество ограничивается в разумных пределах (от "1" до "coords"). Для каждого агента "mapArray [i].active" целиком инициализируется как "1". Затем "numModify" раз случайно выбираются координаты, и для них "mapArray [i].active [modifyCoord]" устанавливается в "0". Цель: у каждого агента мутирует фиксированное (но случайное) количество координат.

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

      //————————————————————————————————————————————————————————————————————
      void C_AO_DSA::GenerateMap ()
      {
        double p1_iter = u.RNDfromCI (0.0, p1);
        double p2_iter = u.RNDfromCI (0.0, p2);
      
        for (int i = 0; i < popSize; i++)
        {
          ArrayInitialize (mapArray [i].active, 0);
        }
      
        if (u.RNDfromCI (0.0, 1.0) < u.RNDfromCI (0.0, 1.0))
        {
          if (u.RNDfromCI (0.0, 1.0) < p1_iter)
          {
            // Random-mutation #1
            for (int i = 0; i < popSize; i++)
            {
              for (int c = 0; c < coords; c++)
              {
                if (u.RNDfromCI (0.0, 1.0) < u.RNDfromCI (0.0, 1.0))
                {
                  mapArray [i].active [c] = 1;
                }
              }
            }
          }
          else
          {
            // Differential-mutation
            for (int i = 0; i < popSize; i++)
            {
              ArrayInitialize (mapArray [i].active, 1);
              int modifyCoord = u.RNDminusOne (coords);
              mapArray [i].active [modifyCoord] = 0;
            }
          }
        }
        else
        {
          // Random-mutation #2
          int numModify = (int)MathCeil (p2_iter * coords);
          if (numModify < 1) numModify = 1;
          if (numModify > coords) numModify = coords;
      
          for (int i = 0; i < popSize; i++)
          {
            ArrayInitialize (mapArray [i].active, 1);
      
            for (int k = 0; k < numModify; k++)
            {
              int modifyCoord = u.RNDminusOne (coords);
              mapArray [i].active [modifyCoord] = 0;
            }
          }
        }
      }
      //————————————————————————————————————————————————————————————————————

      Функция "GenerateScaleFactor" класса "C_AO_DSA" генерирует масштабный коэффициент (scale factor). Этот метод генерирует масштабирующий множитель, который, используется для контроля "длины" шага, совершаемого агентами. Использование распределения Гамма и случайных чисел позволяет ввести разнообразие в величину и направление этого шага, что может помочь алгоритму более эффективно исследовать пространство поиска, избегая локальных минимумов и находить глобальный оптимум.

      • rand1 — влияет на "форму" распределения Гамма, делая его более "широким" или "узким" в зависимости от его значения.
      • gamma_val — само по себе является случайным положительным числом, распределение которого зависит от "rand1".
      • rand2 - rand3 — определяет знак и относительную величину масштабирующего коэффициента. Результат может быть положительным (если rand2 > rand3) или отрицательным (если rand2 < rand3).

      В итоге "GenerateScaleFactor" выдает "случайный по величине и направлению" множитель, который применяется к вектору направления для формирования вектора шага.

      //————————————————————————————————————————————————————————————————————
      double C_AO_DSA::GenerateScaleFactor ()
      {
        double rand1 = u.RNDfromCI (0.0, 1.0);
        double rand2 = u.RNDfromCI (0.0, 1.0);
        double rand3 = u.RNDfromCI (0.0, 1.0);
      
        double shape = 2.0 * rand1;
        double gamma_val = GammaRandom (shape, 1.0);
      
        return gamma_val * (rand2 - rand3);
      }
      //————————————————————————————————————————————————————————————————————

      Далее идет реализация функции "GammaRandom", которая генерирует случайные числа, распределенные по гамма-распределению (Gamma distribution). Распределение Гамма является важным распределением вероятностей, оно моделирует время ожидания до завершения некоторого числа независимых событий, каждое из которых происходит с некоторой средней скоростью. Оно имеет два параметра:

      • shape (форма) — определяет форму распределения,
      • scale (масштаб) — определяет, насколько "растянуто" распределение.

      В DSA используется для создания псевдо-стабильного блуждания — специального паттерна движения, где часто встречаются маленькие шаги и редко — большие прыжки. Функция "GammaRandom" реализует Алгоритм реализации (Marsaglia-Tsang, 2000). Метод отбора-отклонения (Rejection Sampling). 

      Назначение "GammaRandom"— эта функция является вспомогательной для алгоритма DSA, которая требует генерации чисел из распределения Гамма. Как было сказано ранее, в DSA распределение Гамма может использоваться при генерации масштабного коэффициента, что позволяет контролировать степень и направление шага агентов в пространстве поиска.

      Рекурсивно вызываем функцию "с" (shape+1), затем умножаем на случайное число в степени (1/shape). Это уменьшает результат и смещает распределение к нулю. Случай 2: shape ≥ 1. Метод отбора-отклонения: вычисляем вспомогательные константы "d" и "c" из "shape", генерируем кандидата "x" из нормального распределения N(0,1), вычисляем v = (1 + c·x)³. Проверяем, можем ли принять кандидата. Быстрый тест: сравниваем случайное число с простым выражением. Точный тест: сравниваем логарифмы. Если принят — возвращаем (d·v·scale). Если отклонён — повторяем с шага 2. Защиты:

      • минимальный shape = 0.01 (избегаем деления на ноль)
      • проверка v > 0 перед возведением в куб
      • защита от log(0)
      • максимум 100 итераций (на случай невезения)
      Получаем в результате положительное число, где маленькие значения выпадают часто, большие значения выпадают редко, и среднее значение ≈ shape × scale.

      //————————————————————————————————————————————————————————————————————
      double C_AO_DSA::GammaRandom (double shape, double scale)
      {
        if (shape <= 0.0) return 1.0;
        if (shape < 0.01) shape = 0.01;
      
        if (shape < 1.0)
        {
          double gamma = GammaRandom (1.0 + shape, scale);
          double u_rand = u.RNDfromCI (1e-10, 1.0);
          return gamma * MathPow (u_rand, 1.0 / shape);
        }
      
        double d = shape - 1.0 / 3.0;
        double c = 1.0 / MathSqrt (9.0 * d);
      
        int maxIter = 100;
        int iter = 0;
      
        while (iter < maxIter)
        {
          iter++;
          double x, v;
      
          do
          {
            x = u.GaussDistribution (0.0, 1.0, -10.0, 10.0);
            v = 1.0 + c * x;
          }
          while (v <= 0.0);
      
          v = v * v * v;
          double u_rand = u.RNDfromCI (1e-10, 1.0);
          double x_sq = x * x;
      
          if (u_rand < 1.0 - 0.0331 * x_sq * x_sq)
          {
            return scale * d * v;
          }
      
          if (MathLog (u_rand) < 0.5 * x_sq + d * (1.0 - v + MathLog (v)))
          {
            return scale * d * v;
          }
        }
      
        return scale * d;
      }
      //————————————————————————————————————————————————————————————————————
      

      Функция "BoundaryControl" предназначена для контроля границ (boundary control) для конкретного агента. Ее цель — убедиться, что координаты каждого агента остаются в пределах допустимого диапазона.

      Зачем нужна такая обработка границ?

      В алгоритмах оптимизации, особенно основанных на популяциях (как DSA), агенты перемещаются в пространстве поиска. Нередко при вычислении нового положения агента, его координаты могут выйти за пределы допустимого диапазона, который определяется постановкой задачи. Функция "BoundaryControl" гарантирует, что:

      • агенты остаются в физически или логически допустимой области;
      • предотвращаются некорректные вычисления в последующих итерациях, которые могут быть вызваны выходом за пределы;
      • наличие вероятностной подстановки (u.RNDfromCI (rangeMin [c], rangeMax [c])) вместо простого "отражения" или "срезания" может помочь избежать образования "кластеров" агентов именно на границах, способствуя более полному исследованию пространства.
      //————————————————————————————————————————————————————————————————————
      void C_AO_DSA::BoundaryControl (int idx)
      {
        for (int c = 0; c < coords; c++)
        {
          if (a [idx].c [c] < rangeMin [c])
          {
            if (u.RNDprobab () < 0.5)
            {
              a [idx].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            }
            else
            {
              a [idx].c [c] = rangeMin [c];
            }
          }
      
          if (a [idx].c [c] > rangeMax [c])
          {
            if (u.RNDprobab () < 0.5)
            {
              a [idx].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]);
            }
            else
            {
              a [idx].c [c] = rangeMax [c];
            }
          }
      
          a [idx].c [c] = u.SeInDiSp (a [idx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
        }
      }
      //————————————————————————————————————————————————————————————————————

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

      Шаг 1: Селекция (Оценка новых решений). Для каждого агента в популяции сравнивается значение его новой позиции (насколько она "хороша" или "приспособлена") со значением его предыдущей позиции. Если новая позиция оказалась не лучше (то есть, либо равна по качеству, либо хуже) предыдущей, то агент возвращается к своему старому, более выгодному положению. Это означает, что сохраняются только те шаги, которые привели к улучшению. Если у агента не было валидного предыдущего положения, это условие не применяется.

      Шаг 2: Обновление глобального лучшего. После того как все агенты определились, какое положение им сохранить, функция ищет среди всех текущих позиций агентов ту, которая обладает наилучшим качеством (наивысшим показателем приспособленности). Затем это качество сравнивается с наилучшим качеством, которое было зафиксировано за всё время работы алгоритма. Если текущая лучшая позиция в популяции превосходит ранее найденное абсолютное лучшее решение, оно становится новым глобальным лучшим решением, и местоположение этого лучшего решения запоминается.

      //————————————————————————————————————————————————————————————————————
      void C_AO_DSA::Revision ()
      {
        //------------------------------------------------------------------
        // Селекция: принять только если новый фитнес лучше старого
        for (int i = 0; i < popSize; i++)
        {
          // Если новый фитнес (f) НЕ лучше старого (fP) - восстанавливаем
          if (a [i].f <= a [i].fP && a [i].fP != -DBL_MAX)
          {
            a [i].f = a [i].fP;
            ArrayCopy (a [i].c, a [i].cP, 0, 0, coords);
          }
        }
      
        //------------------------------------------------------------------
        // Обновление глобального лучшего
        int bestIdx = 0;
        double bestFit = a [0].f;
      
        for (int i = 1; i < popSize; i++)
        {
          if (a [i].f > bestFit)
          {
            bestFit = a [i].f;
            bestIdx = i;
          }
        }
      
        if (bestFit > fB)
        {
          fB = bestFit;
          ArrayCopy (cB, a [bestIdx].c, 0, 0, coords);
        }
      }
      //————————————————————————————————————————————————————————————————————


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

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

      DSA|Differential Search Algorithm|50.0|1.0|0.3|0.3|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.6982999549366771
      25 Hilly's; Func runs: 10000; result: 0.4112441157981344
      500 Hilly's; Func runs: 10000; result: 0.26639110224055546
      =============================
      5 Forest's; Func runs: 10000; result: 0.7546811566849957
      25 Forest's; Func runs: 10000; result: 0.33583122216029415
      500 Forest's; Func runs: 10000; result: 0.18324446089620824
      =============================
      5 Megacity's; Func runs: 10000; result: 0.5538461538461539
      25 Megacity's; Func runs: 10000; result: 0.2156923076923077
      500 Megacity's; Func runs: 10000; result: 0.10643076923077019
      =============================
      All score: 3.52566 (39.17%)

      1 (B-DSA)

      DSA|Differential Search Algorithm|50.0|2.0|0.3|0.3|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.75651834013058
      25 Hilly's; Func runs: 10000; result: 0.420539560652641
      500 Hilly's; Func runs: 10000; result: 0.2673297720453783
      =============================
      5 Forest's; Func runs: 10000; result: 0.8226119802254519
      25 Forest's; Func runs: 10000; result: 0.3408987112971432
      500 Forest's; Func runs: 10000; result: 0.18471282959506144
      =============================
      5 Megacity's; Func runs: 10000; result: 0.5323076923076923
      25 Megacity's; Func runs: 10000; result: 0.22153846153846152
      500 Megacity's; Func runs: 10000; result: 0.10521538461538557
      =============================
      All score: 3.65167 (40.57%)

      2 (S-DSA)

      DSA|Differential Search Algorithm|50.0|3.0|0.3|0.3|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.704394194121553
      25 Hilly's; Func runs: 10000; result: 0.415864841547213
      500 Hilly's; Func runs: 10000; result: 0.26675670523279044
      =============================
      5 Forest's; Func runs: 10000; result: 0.8200531047825427
      25 Forest's; Func runs: 10000; result: 0.3345381783754753
      500 Forest's; Func runs: 10000; result: 0.18518322851491886
      =============================
      5 Megacity's; Func runs: 10000; result: 0.49846153846153846
      25 Megacity's; Func runs: 10000; result: 0.21846153846153848
      500 Megacity's; Func runs: 10000; result: 0.1064307692307701
      =============================
      All score: 3.55014 (39.45%)

      3 (E1-DSA)

      DSA|Differential Search Algorithm|50.0|4.0|0.3|0.3|
      =============================
      5 Hilly's; Func runs: 10000; result: 0.7184577016736993
      25 Hilly's; Func runs: 10000; result: 0.4114617093550586
      500 Hilly's; Func runs: 10000; result: 0.2650947599954659
      =============================
      5 Forest's; Func runs: 10000; result: 0.8159647894070122
      25 Forest's; Func runs: 10000; result: 0.3307842077432657
      500 Forest's; Func runs: 10000; result: 0.18482144990330598
      =============================
      5 Megacity's; Func runs: 10000; result: 0.4800000000000001
      25 Megacity's; Func runs: 10000; result: 0.20769230769230776
      500 Megacity's; Func runs: 10000; result: 0.10500000000000091
      =============================
      All score: 3.51928 (39.10%)

      4 (E2-DSA)

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

      Hilly

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

      Forest

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

      Megacity

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

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

      Paraboloid

      DSA на стандартной тестовой функции Paraboloid

      GoldsteinPrice

      DSA на стандартной тестовой функции GoldsteinPrice

      В рейтинговой таблице алгоритм DSA представлен ознакомительно.

      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)
      dingo_optimization_algorithm_M 0,47968 0,45367 0,46369 1,39704 0,94145 0,87909 0,91454 2,73508 0,78615 0,86061 0,84805 2,49481 6,627 73,63
      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
      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
      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
      (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
      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
      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
      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
      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
      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
      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
      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
      extremal_optimization_M 0,76166 0,77242 0,31747 1,85155 0,99999 0,76751 0,23527 2,00277 0,74769 0,53969 0,14249 1,42987 5,284 58,71
      biogeography based optimization 0,94912 0,69456 0,35031 1,99399 0,93820 0,67365 0,25682 1,86867 0,74615 0,48277 0,17369 1,40261 5,265 58,50
      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
      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
      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
      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
      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
      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
      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
      backtracking_search_algorithm 0,97309 0,54534 0,29098 1,80941 0,99999 0,58543 0,21747 1,80289 0,84769 0,36953 0,12978 1,34700 4,959 55,10
      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
      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
      bonobo_optimizer 0,77565 0,63805 0,32908 1,74278 0,88088 0,76344 0,25573 1,90005 0,61077 0,49846 0,14246 1,25169 4,895 54,38
      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
      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
      dream_optimization_algorithm 0,85556 0,70085 0,37280 1,92921 0,73421 0,48905 0,24147 1,46473 0,77231 0,47354 0,18561 1,43146 4,825 53,62
      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
      dolphin_echolocation_algorithm 0,75995 0,67572 0,34171 1,77738 0,89582 0,64223 0,23941 1,77746 0,61538 0,44031 0,15115 1,20684 4,762 52,91
      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
      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
      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
      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
      (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
      fractal-based Algorithm 0,79000 0,65134 0,28965 1,73099 0,87158 0,56823 0,18877 1,62858 0,61077 0,46062 0,12398 1,19537 4,555 50,61
      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
      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
      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
      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
      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
      camel algorithm M 0,78684 0,56042 0,35133 1,69859 0,82772 0,56041 0,24336 1,63149 0,64846 0,33092 0,13418 1,11356 4,444 49,37
      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
      covariance_matrix_adaptation_evolution_strategy 0,76258 0,72089 0,00000 1,48347 0,82056 0,79616 0,00000 1,61672 0,75846 0,49077 0,00000 1,24923 4,349 48,33
      duelist_algorithm 0,92782 0,53778 0,27792 1,74352 0,86957 0,47536 0,18193 1,52686 0,62153 0,33569 0,11715 1,07437 4,345 48,28
      differential _search_algorithm 0,75651 0,42054 0,26733 1,44438 0,82261 0,34090 0,18471 1,34822 0,53231 0,22154 0,10521 0,85906 3,652 40,57
      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



      Выводы

      Алгоритм дифференциального поиска DSA демонстрирует среднюю результативность на тестовых задачах оптимизации. При использовании стратегии S-DSA, где каждая особь движется к случайному решению из топ-N лучших, алгоритм набирает чуть более 40% производительности относительно эталонных значений. Данная стратегия обеспечивает наилучший баланс между исследованием пространства поиска и эксплуатацией найденных решений. Остальные три стратегии генерации направления показывают незначительно худшие результаты с отставанием в пределах 1.5%, что говорит об относительной устойчивости алгоритма к выбору параметров.

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

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

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

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

      tab

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

      chart

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

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

      Плюсы:

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

      Минусы:

      1. Более слабые результаты на функциях малой размерности.

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


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

      # Имя Тип Описание
      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_DSA.mq5
      Скрипт Испытательный стенд для DSA
      Прикрепленные файлы |
      DSA.zip (373.81 KB)
      Знакомство с языком MQL5 (Часть 15): Руководство для начинающих по созданию пользовательских индикаторов (IV) Знакомство с языком MQL5 (Часть 15): Руководство для начинающих по созданию пользовательских индикаторов (IV)
      В этой статье вы узнаете, как создать индикатор ценового действия на языке MQL5, сосредоточив внимание на ключевых точках, таких как минимум (L), максимум (H), более высокий минимум (HL), более высокий максимум (HH), более низкий минимум (LL) и более низкий максимум (LH) для анализа трендов. Вы также изучите, как выявлять зоны премии и дисконта, отмечать уровень коррекции 50% и использовать соотношение риска и вознаграждения для расчета целевых уровней прибыли. В статье также рассмотрено определение точек входа, уровней стоп-лосса (SL) и тейк-профита (TP) на основе структуры тренда.
      Знакомство с языком MQL5 (Часть 14): Руководство для начинающих по созданию пользовательских индикаторов (III) Знакомство с языком MQL5 (Часть 14): Руководство для начинающих по созданию пользовательских индикаторов (III)
      Научитесь создавать индикатор Harmonic Pattern на языке MQL5 с использованием графических объектов. Узнайте, как обнаруживать точки свинга, применять уровни Фибоначчи и автоматизировать распознавание паттернов.
      Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (STSSM-блок) Нейросети в трейдинге: Пространственно-временная модель состояния для анализа финансовых данных (STSSM-блок)
      В статье раскрывается внутренняя механика STSSM-блока и показано, как современные SSM-подходы можно адаптировать под событийную логику спайковых моделей, сохранив высокую скорость и выразительность представлений. Мы шаг за шагом поднимаемся по архитектуре, превращая строгую теорию авторского решения в практичный инструмент для анализа финансовых временных рядов.
      Моделирование рынка (Часть 16): Сокеты (X) Моделирование рынка (Часть 16): Сокеты (X)
      Мы близки к завершению данного испытания. Однако, прежде чем приступить, я хочу, чтобы вы попытались понять эти две статьи, данную и предыдущую. Так вы действительно поймете следующую статью, в которой я рассмотрю исключительно ту часть, которая касается программирования на MQL5. Но я также постараюсь сделать её понятной. Если вы не понимаете эти две последние статьи, то вам будет тяжело понять и следующую, потому что материалы накапливаются. Чем больше вещей нужно сделать, тем больше нужно создать и понять для достижения цели.