preview
Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS)

Алгоритм искусственной коронарной циркуляции — Artificial Coronary Circulation System (ACCS)

MetaTrader 5Торговые системы |
329 0
Andrey Dik
Andrey Dik

Содержание

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


Введение

Алгоритм Artificial Coronary Circulation System (ACCS) — это биовдохновлённый метаэвристический метод оптимизации. ACCS имитирует рост коронарных артерий в человеческом сердце. Идея заключается в том, что каждая артерия или капилляр представляет собой кандидатное решение, а весь процесс роста сосудистой системы — это аналог поиска оптимума в сложном пространстве решений. Алгоритм был предложен A. Kaveh и M. Kooshkbaghi в 2019г. в статье Artificial coronary circulation system: A new bio-inspired metaheuristic algorithm.


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

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

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

Память сердца. Чтобы не забыть, какие ветви были самыми сильными, ACCS использует Heart Memory (HM) — это дневник, в который записываются лучшие решения. Эти записи являются ориентиром для роста, помогают новым капиллярам расти в правильном направлении.

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

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

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

coronary_system

Рисунок 1. Коронарная система

На изображении показаны основные элементы: схематическое изображение сердца с коронарными артериями. Левая коронарная артерия (LCA) с её ветвями: LAD и Circumflex, правая коронарная артерия (RCA), мелкие капилляры для демонстрации локального поиска, паттерн бифуркации (разветвления) справа в верхней части.

Концепции алгоритма. В нижней части показано, как биологические элементы соответствуют компонентам алгоритма:

Главные артерии → Глобальный поиск
Бифуркация → Фактор разветвления
Капилляры → Локальный поиск
Кровоток → CGF (фактор роста)

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

  1. Инициализация

    • Задать параметры: размер популяции (popSize), размер памяти сердца (heartMemorySize)

    • Создать начальную популяцию капилляров (агентов) со случайными позициями в пространстве решений

    • Инициализировать память сердца (Heart Memory) для хранения лучших решений

  2. Основной цикл (до достижения критерия остановки)

    • Вычислить фактор роста коронарных сосудов (CGF) для каждого агента на основе их фитнес-значений

    • Выполнить глобальный поиск (рост артерий) — обновить позиции агентов, двигаясь к центру популяции или от него, в зависимости от CGF

    • Проверить границы и обновить память сердца

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

    • Проверить границы и обновить память сердца

    • Увеличить счетчик итераций

  3. Вычисление CGF (Coronary Growth Factor)

    • Для каждого агента вычислить CGF как его фитнес, деленный на сумму фитнесов всех агентов (нормализация)

  4. Глобальный поиск (Global Search)

    • Вычислить центр популяции (среднее по всем координатам)

    • Для каждого агента:

      • выбрать случайного другого агента (кроме себя);

      • определить направление движения (dr): если CGF центра меньше CGF текущего агента, то dr = -1 (двигаться от центра), иначе dr = 1 (двигаться к центру);

      • обновить позицию: новая позиция = позиция случайного агента + dr * CGF текущего агента * (центр - позиция случайного агента).

  5. Локальный поиск (Local Search)

    • Вычислить фактор ангиогенеза (alpha) как angiogenesisPower * sqrt(текущая_итерация / максимальная_итерация)

    • Найти лучшего и худшего агента в текущей популяции

    • Для каждого агента обновить позицию: новая позиция = текущая позиция + alpha * случайное_число * (позиция_лучшего - позиция_худшего)

  6. Обновление памяти сердца (Heart Memory)

    • Отсортировать агентов по фитнес-значению (убывание)

    • Сохранить лучших агентов (в количестве heartMemorySize) в память сердца

  7. Критерий остановки

    • Достигнуто максимальное число итераций

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

ACCS

Рисунок 2. Блок-схема алгоритма ACCS

Шаги алгоритма, отображенные на блок-схеме:

  • инициализация популяции капилляров (случайные позиции),
  • вычисление фитнес-функции для каждого капилляра,
  • вычисление Coronary Growth Factor (CGF),
  • глобальный поиск (движение к центру или от центра),
  • локальный поиск (движение к лучшему и от худшего),
  • обрезка (если новое положение хуже, возвращаемся),
  • обновление памяти сердца,
  • проверка критерия останова.

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

Первая структура, называемая "Память сердца" (Heart Memory), предназначена для временного запоминания и оценки наилучших найденных решений и содержит:

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

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

  • Координаты — аналогично "позиции" в первой структуре, это набор числовых значений, определяющих конкретное решение.
  • Фитнес — числовое значение, которое будет назначено этому решению после его оценки.
  • Инициализация — подобно первой структуре, этот компонент позволяет определить размер (количество измерений) для координат и установить начальное значение фитнеса как крайне низкое, пока решение не будет полностью обработано.
//————————————————————————————————————————————————————————————————————
// Структура для хранения памяти сердца (Heart Memory)
struct S_HeartMemory
{
    double position [];  // позиция в памяти
    double fitness;      // фитнес позиции

    void Init (int dimensions)
    {
      ArrayResize (position, dimensions);
      fitness = -DBL_MAX;
    }
};

// Структура для временного хранения новых позиций
struct S_TempPosition
{
    double coords [];   // координаты
    double fitness;     // фитнес
    
    void Init (int dimensions)
    {
      ArrayResize (coords, dimensions);
      fitness = -DBL_MAX;
    }
};
//————————————————————————————————————————————————————————————————————

Класс "C_AO_ACCS" реализует алгоритм оптимизации, названный "Искусственная система кровообращения коронарных сосудов" (Artificial Coronary Circulation System, ACCS). Этот класс наследуется от базового класса "AO".

Общедоступные (public) поля класса:

  • Деструктор — специальная функция, которая выполняется при уничтожении объекта этого класса, обеспечивая очистку ресурсов.
  • Конструктор — функция вызывается при создании нового объекта и инициализирует основные параметры алгоритма:
    • Устанавливает размер популяции (количество "капилляров") по умолчанию – 25.
    • Устанавливает коэффициент бифуркации по умолчанию – 0.5.
    • Создает структуру для хранения изменяемых параметров и записывает туда начальные значения "popSize" и "bifurcationRate".
  • SetParams () — функция для установки или обновления параметров алгоритма. Она считывает текущие значения из перечисленных параметров, а также определяет размер "памяти сердца" как 25% от размера популяции, но не менее 1.
  • Init () — функция предназначена для начальной инициализации алгоритма. Она принимает информацию о диапазонах поиска, шагах и количестве эпох (итераций).
  • Moving () — функция отвечает за перемещение или обновление агентов (капилляров) в пространстве поиска.
  • Revision () — функция занимается пересмотром и корректировкой решений на основе логики алгоритма.
  • bifurcationRate — коэффициент бифуркации, который влияет на процесс создания новых решений и ветвления поиска.

Закрытые (private) поля класса:

  • CalculateCGF () — внутренняя функция для расчета показателя, называемого "CGF", для каждого агента.
  • CalculateCenterPosition () — внутренняя функция для определения центрального положения популяции.
  • GlobalSearch () — внутренняя функция, отвечающая за глобальный поиск — исследование широкой области пространства решений.
  • LocalSearch () — внутренняя функция, отвечающая за локальный поиск —более тщательное исследование области вокруг текущих решений.
  • SelectionPhase () — внутренняя функция, реализующая фазу отбора — выбор лучших решений для дальнейшего развития.
  • UpdateHeartMemory () — внутренняя функция для обновления "памяти сердца" лучшими найденными решениями.
  • heartMemorySize — число, определяющее максимальное количество сохраняемых решений в "памяти сердца".
  • heartMemory — массив объектов, представляющих собой "память сердца", где хранятся лучшие найденные решения.
  • tempPos — массив временных объектов, используемых для хранения новых, еще не оцененных позиций, в основном для глобального поиска.
  • cgf — массив, хранящий значения "CGF" для каждого агента в популяции.
  • cgfCenter — скалярное значение, представляющее "CGF" центра популяции.
  • centerCoords — массив, хранящий координаты центра популяции.
  • centerFitness — числовое значение, представляющее качество (фитнес) центральной позиции.
  • alpha — переменная, представляет собой коэффициент, связанный с процессом "ангиогенеза" (роста и развития).
  • currentIteration — переменная, отслеживающая номер текущей итерации (шага) алгоритма.
  • maxIterations — переменная, задающая максимальное количество итераций, которое может выполнить алгоритм.
  • firstIteration — логический флаг, указывающий, является ли текущая итерация первой.
//————————————————————————————————————————————————————————————————————
class C_AO_ACCS : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_ACCS () { }
  C_AO_ACCS ()
  {
    ao_name = "ACCS";
    ao_desc = "Artificial Coronary Circulation System";
    ao_link = "https://www.mql5.com/ru/articles/19861";

    popSize         = 50;    // размер популяции (количество капилляров)
    bifurcationRate = 0.5;   // коэффициент бифуркации

    ArrayResize (params, 2);

    params [0].name = "popSize";         params [0].val = popSize;
    params [1].name = "bifurcationRate"; params [1].val = bifurcationRate;
  }

  void SetParams ()
  {
    popSize         = (int)params [0].val;
    bifurcationRate = params      [1].val;
    
    // Размер памяти сердца - 25% от популяции
    heartMemorySize = MathMax (1, (int)(popSize * 0.25));
  }

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

  void Moving   ();
  void Revision ();

  private:
  void CalculateCGF ();
  void CalculateCenterPosition ();
  void GlobalSearch ();
  void LocalSearch ();
  void SelectionPhase ();
  void UpdateHeartMemory ();
  
  //------------------------------------------------------------------
  public:
  double bifurcationRate;   // коэффициент бифуркации

  private: //---------------------------------------------------------
  int    heartMemorySize;       // размер памяти сердца
  S_HeartMemory heartMemory []; // память сердца
  S_TempPosition tempPos [];    // временные позиции для глобального поиска
  double cgf [];                // массив CGF для каждого агента
  double cgfCenter;             // CGF центра
  double centerCoords [];       // координаты центра популяции
  double centerFitness;         // фитнес центра
  double alpha;                 // текущий фактор ангиогенеза
  int    currentIteration;      // текущая итерация
  int    maxIterations;         // максимальное количество итераций
  bool   firstIteration;        // флаг первой итерации
};
//————————————————————————————————————————————————————————————————————

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

Далее определяется размер "памяти сердца" как 25% от размера популяции, но не менее одного элемента. На основе этого создается массив структур "Память сердца", и каждому элементу присваиваются начальные параметры в виде "координат" (обозначенных как coords). Затем создается массив временных позиций, размером равным размеру популяции, для хранения новых решений, которые будут генерироваться на следующем этапе. Каждому элементу этого массива также задаются начальные параметры.

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

//————————————————————————————————————————————————————————————————————
//--- Инициализация
bool C_AO_ACCS::Init (const double &rangeMinP  [],
                      const double &rangeMaxP  [],
                      const double &rangeStepP [],
                      const int     epochsP)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Размер памяти сердца - 25% от популяции
  heartMemorySize = MathMax (1, (int)(popSize * 0.25));
  
  // Инициализация памяти сердца
  ArrayResize (heartMemory, heartMemorySize);
  for (int i = 0; i < heartMemorySize; i++)
  {
    heartMemory [i].Init (coords);
  }

  // Инициализация временных позиций
  ArrayResize (tempPos, popSize);
  for (int i = 0; i < popSize; i++)
  {
    tempPos [i].Init (coords);
  }

  // Инициализация массивов
  ArrayResize (cgf, popSize);
  ArrayResize (centerCoords, coords);
  
  currentIteration = 0;
  maxIterations = epochsP;
  firstIteration = true;
  alpha = 0.625;

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

Метод "CalculateCGF" предназначен для вычисления коэффициентов роста (CGF) для каждого агента в популяции с целью оценки их вклада в развитие системы.

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

Этот метод обеспечивает взвешенное распределение влияния каждого решения на последующие этапы алгоритма в зависимости от их качества.

//————————————————————————————————————————————————————————————————————
//--- Вычисление CGF (Coronary Growth Factor) согласно Law 2
void C_AO_ACCS::CalculateCGF ()
{
  // Для максимизации: CGFi = fiti / Σfiti
  
  double minFit = DBL_MAX;
  double maxFit = -DBL_MAX;
  
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < minFit) minFit = a [i].f;
    if (a [i].f > maxFit) maxFit = a [i].f;
  }
  
  double sumFitness = 0.0;
  
  // Если все фитнесы одинаковые
  if (MathAbs (maxFit - minFit) < 1e-10)
  {
    for (int i = 0; i < popSize; i++)
    {
      cgf [i] = 1.0 / popSize;
    }
    cgfCenter = 1.0 / popSize;
    return;
  }
  
  // Нормализуем фитнесы и вычисляем сумму
  for (int i = 0; i < popSize; i++)
  {
    double normalizedFit = (a [i].f - minFit) / (maxFit - minFit) + 0.1;
    sumFitness += normalizedFit;
  }
  
  // Вычисляем CGF для каждого капилляра
  for (int i = 0; i < popSize; i++)
  {
    double normalizedFit = (a [i].f - minFit) / (maxFit - minFit) + 0.1;
    cgf [i] = normalizedFit / sumFitness;
  }
  
  // CGF для центра
  double normalizedCenterFit = (centerFitness - minFit) / (maxFit - minFit) + 0.1;
  cgfCenter = normalizedCenterFit / sumFitness;
}
//————————————————————————————————————————————————————————————————————

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

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

//————————————————————————————————————————————————————————————————————
//--- Вычисление центральной позиции (Law 3)
void C_AO_ACCS::CalculateCenterPosition ()
{
  // Xc = mean(X), fitc = mean(fit)
  ArrayInitialize (centerCoords, 0.0);
  centerFitness = 0.0;
  
  for (int i = 0; i < popSize; i++)
  {
    for (int j = 0; j < coords; j++)
    {
      centerCoords [j] += a [i].c [j];
    }
    centerFitness += a [i].f;
  }
  
  for (int j = 0; j < coords; j++)
  {
    centerCoords [j] /= popSize;
  }
  centerFitness /= popSize;
}
//————————————————————————————————————————————————————————————————————

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

Он начинается с прохода по всей популяции, для каждого решения выбирается случайный агент (за исключением текущего). Затем рассчитывается фактор бифуркации, пропорциональный его "CGF", который влияет на величину изменения. Направление поиска определяется сравнением "CGF" текущего агента с центром популяции: если "CGF" текущего агента больше центра, направление выбирается в одну сторону, иначе — в противоположную. Для каждого координатного параметра вычисляется новое значение по формуле, которая зависит от выбранного направления, фактора бифуркации и случайного числа, что обеспечивает добавление элемента случайности и разнообразия. После этого новое значение координаты проверяется и, при необходимости, корректируется, чтобы не выходить за допустимые диапазоны с помощью метода "u.SeInDiSp".

Этот метод позволяет решению «вырасти» и переместиться в пространстве поиска, стимулируя исследование новых областей.

//————————————————————————————————————————————————————————————————————
//--- Глобальный поиск - рост главных артерий (Law 4)
void C_AO_ACCS::GlobalSearch ()
{
  for (int i = 0; i < popSize; i++)
  {
    // Выбираем случайный капилляр r (кроме текущего)
    int r = i;
    while (r == i && popSize > 1)
    {
      r = (int)MathFloor (u.RNDfromCI (0.0, popSize - 0.001));
      if (r >= popSize) r = popSize - 1;
      if (r < 0) r = 0;
    }
    
    // Фактор бифуркации Bf = CGFi
    double Bf = cgf [i] * bifurcationRate;
    
    // Определяем направление на основе сравнения CGF
    double dir;
    if (cgfCenter < cgf [i])  // Согласно документу
    {
      dir = -1.0;
    }
    else
    {
      dir = 1.0;
    }
    
    // Применяем формулу Law 4
    for (int j = 0; j < coords; j++)
    {
      double rand_val = u.RNDfromCI (0.0, 1.0);
      
      // X^(t+1)_i,j = X^t_r,j + dir × Bf × (X^t_c,j - rand × X^t_r,j)
      tempPos [i].coords [j] = a [r].c [j] + dir * Bf * (centerCoords [j] - rand_val * a [r].c [j]);
      
      // Проверка границ
      tempPos [i].coords [j] = u.SeInDiSp (tempPos [i].coords [j], rangeMin [j], rangeMax [j], rangeStep [j]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

Метод "LocalSearsh" реализует локальный поиск, направленный на развитие капилляров в рамках алгоритма. Начинается с обновления фактора роста капилляров, который зависит от текущей итерации и максимального числа итераций: чем больше прошедших итераций, тем выше значение фактора. Затем определяется наиболее подходящее и наименее подходящее решение в текущей популяции, чтобы на их основе провести локальную корректировку.

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

//————————————————————————————————————————————————————————————————————
//--- Локальный поиск - рост капилляров (Law 6)
void C_AO_ACCS::LocalSearch ()
{
  // Обновляем фактор ангиогенеза: α = 0.625 × √(itr/itrmax)
  if (maxIterations > 0 && currentIteration > 0)
  {
    alpha = 0.625 * MathSqrt ((double)currentIteration / (double)maxIterations);
  }
  else
  {
    alpha = 0.625;
  }
  
  // Находим лучшую и худшую позиции в текущей популяции
  int bestIdx = 0, worstIdx = 0;
  
  for (int i = 1; i < popSize; i++)
  {
    if (a [i].f > a [bestIdx].f) bestIdx = i;
    if (a [i].f < a [worstIdx].f) worstIdx = i;
  }
  
  // Применяем локальный поиск (Law 6)
  for (int i = 0; i < popSize; i++)
  {
    for (int j = 0; j < coords; j++)
    {
      double rand_val = u.RNDfromCI (0.0, 1.0);
      
      // X^(t+1)_i,j = X^t_i,j + α × rand × (X^t_b,j - X^t_w,j)
      a [i].c [j] = a [i].c [j] + alpha * rand_val * (a [bestIdx].c [j] - a [worstIdx].c [j]);
      
      // Проверка границ
      a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

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

//————————————————————————————————————————————————————————————————————
//--- Фаза селекции (Law 5)
void C_AO_ACCS::SelectionPhase ()
{
  // Применяем новые позиции из глобального поиска
  for (int i = 0; i < popSize; i++)
  {
    // Копируем новую позицию
    ArrayCopy (a [i].c, tempPos [i].coords, 0, 0, coords);
  }
}
//————————————————————————————————————————————————————————————————————

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

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

//————————————————————————————————————————————————————————————————————
//--- Обновление памяти сердца (Law 7)
void C_AO_ACCS::UpdateHeartMemory ()
{
  // Создаем временный массив для сортировки
  struct S_IndexedAgent
  {
    int index;
    double fitness;
  };
  
  S_IndexedAgent indexed [];
  ArrayResize (indexed, popSize);
  
  for (int i = 0; i < popSize; i++)
  {
    indexed [i].index = i;
    indexed [i].fitness = a [i].f;
  }
  
  // Сортировка пузырьком по убыванию фитнеса
  for (int i = 0; i < popSize - 1; i++)
  {
    for (int j = i + 1; j < popSize; j++)
    {
      if (indexed [j].fitness > indexed [i].fitness)
      {
        S_IndexedAgent temp = indexed [i];
        indexed [i] = indexed [j];
        indexed [j] = temp;
      }
    }
  }
  
  // Сохраняем лучшие решения в памяти сердца
  for (int i = 0; i < heartMemorySize; i++)
  {
    int idx = indexed [i].index;
    ArrayCopy (heartMemory [i].position, a [idx].c, 0, 0, coords);
    heartMemory [i].fitness = a [idx].f;
  }
}
//————————————————————————————————————————————————————————————————————

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

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

//————————————————————————————————————————————————————————————————————
//--- Основной шаг алгоритма
void C_AO_ACCS::Moving ()
{
  // Инициализация популяции (Law 1)
  if (!revision)
  {
    for (int i = 0; i < popSize; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        a [i].c [j] = u.RNDfromCI (rangeMin [j], rangeMax [j]);
        a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }
    
    revision = true;
    firstIteration = true;
    currentIteration = 0;
    return;
  }
  
  currentIteration++;
  
  // Вычисляем центральную позицию (Law 3)
  CalculateCenterPosition ();
  
  // Вычисляем CGF для всех капилляров (Law 2)
  CalculateCGF ();
  
  // Выполняем глобальный поиск (Law 4)
  GlobalSearch ();
  
  // Применяем селекцию (Law 5)
  SelectionPhase ();
}
//————————————————————————————————————————————————————————————————————

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

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

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

//————————————————————————————————————————————————————————————————————
//--- Проверка и обновление результатов
void C_AO_ACCS::Revision ()
{
  if (firstIteration)
  {
    firstIteration = false;
    // Инициализируем личные лучшие позиции
    for (int i = 0; i < popSize; i++)
    {
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }
    // Инициализируем память сердца
    UpdateHeartMemory ();
  }
  
  // Сохраняем предыдущие позиции и фитнесы
  for (int i = 0; i < popSize; i++)
  {
    ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);
    a [i].fP = a [i].f;
  }
  
  // После оценки фитнеса новых позиций из глобального поиска,
  // проверяем улучшение (Law 5 - селекция и обрезка)
  for (int i = 0; i < popSize; i++)
  {
    // Если новая позиция хуже предыдущей лучшей этого агента
    if (a [i].f < a [i].fB)
    {
      // Вероятность обрезки (pruning)
      double pruningProb = 0.2;
      if (u.RNDfromCI (0.0, 1.0) < pruningProb)
      {
        // Обрезка - возврат к личной лучшей позиции
        ArrayCopy (a [i].c, a [i].cB, 0, 0, coords);
        a [i].f = a [i].fB;
      }
    }
    else
    {
      // Обновляем личное лучшее решение агента
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }
  }
  
  // Выполняем локальный поиск (Law 6)
  LocalSearch ();
  
  // Обновляем память сердца (Law 7)
  UpdateHeartMemory ();
  
  // Обновляем глобальное лучшее решение
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy (cB, a [i].c, 0, 0, WHOLE_ARRAY);
    }
  }
}
//————————————————————————————————————————————————————————————————————


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

Результаты тестов, мягко говоря, слабые. Но есть одно большое "НО", и об этом ниже.

ACCS|Artificial Coronary Circulation System|50.0|0.5|
=============================
5 Hilly's; Func runs: 10000; result: 0.5388483416731469
25 Hilly's; Func runs: 10000; result: 0.40315510603699484
500 Hilly's; Func runs: 10000; result: 0.2750619992528315
=============================
5 Forest's; Func runs: 10000; result: 0.4373687177939665
25 Forest's; Func runs: 10000; result: 0.24807871438181
500 Forest's; Func runs: 10000; result: 0.17536977563388764
=============================
5 Megacity's; Func runs: 10000; result: 0.3692307692307693
25 Megacity's; Func runs: 10000; result: 0.2116923076923077
500 Megacity's; Func runs: 10000; result: 0.10640000000000094
=============================
All score: 2.76521 (30.72%)

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

Hilly

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

Forest

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

Megacity

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

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

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

Paraboloid

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

Acley

ACCS на стандартной тестовой функции Ackley

Rastrigin

ACCS на стандартной тестовой функции Rastrigin

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

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


Выводы

Алгоритм ACCS представляет собой интересную попытку перенести биологическую модель роста коронарных артерий на задачи оптимизации, однако его практическая реализация выявила существенные ограничения. Результат в 30% от максимально возможного указывает на фундаментальные проблемы в механике алгоритма, которые не позволяют ему конкурировать с лучшими методами оптимизации. Основная слабость заключается в чрезмерной зависимости от текущего распределения популяции через механизм "CGF", что приводит к преждевременной сходимости и недостаточному исследованию пространства поиска.

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

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

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

tab

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

chart

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

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

Плюсы:

  1. Мало внешних параметров.
  2. Чрезвычайно эффективен на некоторых типах задач (даже высоких размерностей).

Минусы:

  1. Слабая сходимость на сложных ландшафтах.

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


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

#ИмяТипОписание
1#C_AO.mqh
Включаемый файл
Родительский класс популяционных алгоритмов оптимизации
2#C_AO_enum.mqh
Включаемый файл
Перечисление популяционных алгоритмов оптимизации
3TestFunctions.mqh
Включаемый файл
Библиотека тестовых функций
4
TestStandFunctions.mqh
Включаемый файл
Библиотека функций тестового стенда
5
Utilities.mqh
Включаемый файл
Библиотека вспомогательных функций
6
CalculationTestResults.mqh
Включаемый файл
Скрипт для расчета результатов в сравнительную таблицу
7
Testing AOs.mq5
СкриптЕдиный испытательный стенд для всех популяционных алгоритмов оптимизации
8
Simple use of population optimization algorithms.mq5
Скрипт
Простой пример использования популяционных алгоритмов оптимизации без визуализации
9
Test_AO_ACCS.mq5
СкриптИспытательный стенд для ACCS

Прикрепленные файлы |
ACCS.zip (355.18 KB)
От начального до среднего уровня: Struct (V) От начального до среднего уровня: Struct (V)
В данной статье мы рассмотрим, как перегрузить структурный код. Я знаю, что сначала это довольно сложно для понимания, особенно если увидеть это впервые. Очень важно, чтобы вы усвоили эти понятия и хорошо поняли их, прежде чем пытаться вникать в более сложные и проработанные вещи.
Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами Автоматизация торговых стратегий на MQL5 (Часть 14): Стратегия каскадной торговли с MACD-RSI и статистическими методами
В настоящей статье мы представляем стратегию лейеринга, которая сочетает индикаторы MACD и RSI со статистическими методами для автоматизации динамической торговли на MQL5. Мы исследуем архитектуру этого каскадного подхода, подробно описываем его реализацию с помощью ключевых сегментов кода и даем рекомендации читателям по тестированию на истории для оптимизации эффективности. Наконец, в заключение мы подчеркиваем потенциал стратегии и закладываем основу для дальнейших усовершенствований в автоматической торговле.
Разработка инструментария для анализа движения цен (Часть 11): Советник Heikin Ashi Signal Разработка инструментария для анализа движения цен (Часть 11): Советник Heikin Ashi Signal
MQL5 предлагает безграничные возможности для разработки автоматизированных торговых систем, отвечающих вашим предпочтениям. Знаете ли вы, что он даже может выполнять сложные математические вычисления? В этой статье мы представим японский метод Heikin Ashi (Хейкен Аши) в виде автоматизированной торговой стратегии.
Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров Разрабатываем менеджер терминалов (Часть 2): Запуск нескольких экземпляров
Переходим к использованию сразу нескольких экземпляров терминала на сервере, организовав простую панель управления запуском и остановкой. Теперь пришло время расширять функциональность и переходить к следующим этапам — реализации более сложных возможностей, таких как управление несколькими экземплярами, хранение состояния, интеграция с MetaTrader5 API и веб-интерфейс с полной информацией о терминалах.