English Русский
preview
Algoritmo de estrutura cristalina, Crystal Structure Algorithm (CryStAl)

Algoritmo de estrutura cristalina, Crystal Structure Algorithm (CryStAl)

MetaTrader 5Sistemas de negociação |
17 0
Andrey Dik
Andrey Dik

Sumário

  1. Introdução
  2. Implementação do algoritmo
  3. Resultados dos testes


Introdução

Durante uma análise, tive a oportunidade de estudar um dos algoritmos de otimização cuja implementação revelou algumas "armadilhas" de uma ideia promissora. Apresento o Crystal Structure Algorithm (CryStAl), um algoritmo meta-heurístico de otimização inspirado no processo físico de formação de estruturas cristalinas. Ele foi proposto em 2021 pelos pesquisadores A. Kaveha e M. Kooshkebaghi e publicado no artigo de Siamak Talatahari. O CryStAl modela a formação de cristais, em que as soluções representam átomos e a busca pelo ótimo corresponde ao ordenamento desses átomos em uma estrutura estável. O algoritmo utiliza uma base e um retículo cristalino como analogia para a distribuição espacial das soluções, emprega simetria e regularidade para formar uma população equilibrada e aplica critérios energéticos para descartar soluções instáveis.


Implementação do algoritmo

Vamos examinar o funcionamento do algoritmo CryStAl por meio de um pseudocódigo detalhado de todas as suas etapas:

Parâmetros de entrada do algoritmo

Tamanho da população de cristais, quantidade de cristais na população (normalmente 30)
Quantidade de cristais aleatórios, número de cristais selecionados aleatoriamente para calcular Fc (normalmente 3)
Dimensionalidade do problema, número de variáveis a serem otimizadas
Número máximo de iterações, quantidade de vezes que o algoritmo repetirá o laço de otimização
Limites de busca, valores mínimo e máximo de cada variável

ETAPA 1: INICIALIZAÇÃO DA POPULAÇÃO

Criação dos cristais iniciais

Para cada cristal da população e para cada coordenada (variável) desse cristal:

  • Fórmula 3, inicialização aleatória: Nova coordenada = Limite mínimo + Número aleatório × (Limite máximo - Limite mínimo), em que Número aleatório segue uma distribuição uniforme de 0 a 1
Aplicar a discretização à nova coordenada (caso seja necessário respeitar um passo)
Calcular o valor da função de fitness desse cristal e registrar seus recordes individuais

Inicialização dos recordes globais

Encontrar o cristal com o maior valor da função de fitness
Encontrar o cristal com o maior valor da função de fitness

ETAPA 2: LAÇO PRINCIPAL DE OTIMIZAÇÃO

Repetir as etapas a seguir da primeira até a última iteração:

PASSO 1: Cálculo da posição média de todos os cristais (Cr_main)

Para cada coordenada: inicializar a soma dessa coordenada com zero
Para cada cristal da população: adicionar à soma o valor dessa coordenada no cristal
Dividir a soma pela quantidade de cristais para obter o valor médio
Resultado: obtém-se um vetor com os valores médios de todas as coordenadas

PASSO 2: Determinação do melhor cristal (Cr_b)

Definir o índice do melhor cristal como o índice do primeiro cristal
Para cada cristal seguinte, do segundo ao último:
Se o fitness desse cristal for maior que o fitness do melhor cristal atual: atualizar o índice do melhor cristal com o índice desse cristal
Resultado: obtém-se o índice do cristal com o melhor valor da função de fitness

PASSO 3: Atualização das posições de todos os cristais

Para cada cristal da população:

Etapa 3.1: Armazenamento do estado atual

Salvar a posição atual do cristal como posição anterior

Etapa 3.2: Seleção da estratégia de atualização

Selecionar aleatoriamente uma das quatro estratégias de atualização:

  • Estratégia 0: Atualização cúbica simples
  • Estratégia 1: Atualização cúbica com o melhor cristal
  • Estratégia 2: Atualização cúbica com a média de cristais aleatórios
  • Estratégia 3: Atualização combinada com o melhor cristal e a média

Etapa 3.3: Aplicação da estratégia selecionada

SE a ESTRATÉGIA 0 for selecionada, Atualização cúbica simples:

Para cada coordenada do cristal: gerar um número aleatório r de 0 a 1

  • Fórmula 4, Nova coordenada = Coordenada anterior + r × Coordenada média de todos os cristais
Interpretação: o cristal se desloca na direção da posição média da população com um coeficiente aleatório.

CASO CONTRÁRIO, SE a ESTRATÉGIA 1 for selecionada, Com o melhor cristal:

Para cada coordenada do cristal: gerar dois números aleatórios r1 e r2 de 0 a 1

  • Fórmula 5, Nova coordenada = Coordenada anterior + r1 × Coordenada média de todos os cristais + r2 × Coordenada do melhor cristal
Interpretação: o cristal se desloca simultaneamente em direção à posição média e ao melhor cristal. Isso intensifica a busca nas boas regiões já encontradas.

CASO CONTRÁRIO, SE a ESTRATÉGIA 2 for selecionada, Com a média de cristais aleatórios:

Subetapa 2.1: Cálculo da média de cristais aleatórios (Fc)

Para cada coordenada: inicializar a soma dessa coordenada com zero

Repetir o número especificado de vezes (normalmente 3): selecionar aleatoriamente um cristal da população, excluindo o cristal que está sendo processado
Para cada coordenada: adicionar à soma o valor dessa coordenada no cristal selecionado aleatoriamente
Para cada coordenada: dividir a soma pela quantidade de cristais aleatórios selecionados
Resultado: obtém-se a média de vários cristais selecionados aleatoriamente

Subetapa 2.2: Atualização da posição

Para cada coordenada do cristal: gerar dois números aleatórios r1 e r2 de 0 a 1

  • Fórmula 6, Nova coordenada = Coordenada anterior + r1 × Coordenada média de todos os cristais + r2 × Coordenada média dos cristais aleatórios
Interpretação: o cristal se desloca em direção à posição média da população e à média dos cristais aleatórios. Isso favorece a diversificação ao incorporar informações de vizinhos selecionados aleatoriamente.

CASO CONTRÁRIO, foi selecionada a ESTRATÉGIA 3, Combinada:

Subetapa 3.1: Cálculo da média de cristais aleatórios (Fc)

(Executado exatamente da mesma forma que na estratégia 2)

Subetapa 3.2: Atualização da posição

  • Para cada coordenada do cristal: gerar três números aleatórios r1, r2 e r3 de 0 a 1
  • Fórmula 7, Nova coordenada = Coordenada anterior + r1 × Coordenada média de todos os cristais + r2 × Coordenada do melhor cristal + r3 × Coordenada média dos cristais aleatórios
Interpretação: o cristal se desloca sob a influência simultânea de três fatores: a posição média global, a melhor solução encontrada e as informações provenientes de vizinhos aleatórios.


Etapa 3.4: Tratamento da ultrapassagem dos limites (Boundary Handling)

Para cada coordenada da nova posição do cristal: se a coordenada for menor que o limite mínimo OU maior que o limite máximo, o cristal ultrapassou os limites permitidos e precisa ser corrigido

  • Gerar um número aleatório de 0 a 1
  • Recalcular a coordenada dentro dos limites permitidos: Coordenada = Limite mínimo + Número aleatório × (Limite máximo - Limite mínimo)
Aplicar a discretização à coordenada, caso tenha sido definido um passo de grade: Coordenada = Arredondar(Coordenada / Passo) × Passo
Atualizar a posição do cristal com a nova posição calculada

PASSO 4: Avaliação das novas posições e atualização dos melhores e piores resultados

Para cada cristal da população:

Etapa 4.1: Cálculo do fitness

Calcular o valor da função de fitness para a nova posição do cristal

Etapa 4.2: Atualização dos melhores e piores resultados individuais do cristal

Se o novo valor de fitness for melhor que o melhor valor individual: atualizar o melhor valor individual de fitness com o valor atual e salvar a posição atual como a melhor posição individual
Se o novo valor de fitness for pior que o pior valor individual: atualizar o pior valor individual de fitness com o valor atual e salvar a posição atual como a pior posição individual

Etapa 4.3: Atualização dos melhores e piores resultados globais

Se o valor de fitness desse cristal for melhor que o melhor valor global: atualizar o melhor valor global com o fitness desse cristal e salvar a posição desse cristal como a melhor posição global
Se o valor de fitness desse cristal for pior que o pior valor global: atualizar o pior valor global com o fitness desse cristal e salvar a posição desse cristal como a pior posição global

PASSO 5: Passagem para a próxima iteração

Avançar para a próxima iteração do laço principal de otimização
Fim do laço principal de otimização

ETAPA 3: ENCERRAMENTO DA EXECUÇÃO DO ALGORITMO

Após concluir todas as iterações, retornar o resultado:

  • A melhor posição global (solução do problema de otimização)
  • O melhor valor global da função de fitness

O diagrama da figura abaixo apresenta o esquema do laço principal, a visualização das quatro estratégias e um exemplo do espaço de busca com os cristais.

CryStAl

Figura 1. Fluxograma de funcionamento do algoritmo CryStAl

O algoritmo CryStAl procura manter o equilíbrio entre dois aspectos importantes da otimização:

Diversificação (exploration), exploração de novas regiões do espaço de busca: é promovida pela estratégia 0 por meio do movimento em direção à posição média e reforçada pela estratégia 2 com informações de cristais aleatórios, ajudando a evitar a estagnação em ótimos locais.

Intensificação (exploitation), busca mais aprofundada nas proximidades de boas soluções: é implementada pela estratégia 1 por meio do movimento em direção ao melhor cristal, o que acelera a convergência para o ótimo e permite aproveitar de forma eficiente as informações já obtidas.

Abordagem combinada:

  • A estratégia 3 reúne os três componentes
  • A seleção aleatória das estratégias em cada iteração proporciona adaptabilidade
  • Cada estratégia tem a mesma probabilidade de seleção, 25%

Função dos principais componentes:

Posição média (Cr_main), representa o centro de massa da população, direciona a busca para as regiões centrais das áreas já visitadas do espaço de busca e é atualizada a cada iteração com base nas posições atuais de todos os cristais.
Melhor cristal (Cr_b), contém a melhor solução encontrada até o momento, atrai os demais cristais para uma região promissora e intensifica a busca em torno das boas soluções já encontradas.
Média dos cristais selecionados aleatoriamente (Fc), representa informações locais provenientes dos vizinhos, acrescenta estocasticidade e diversidade à busca e ajuda a evitar a convergência prematura.

Agora que examinamos detalhadamente os princípios de funcionamento do algoritmo, podemos passar à sua implementação em código. Vamos criar a classe "C_AO_CryStAl", derivada da classe "C_AO", para implementar o algoritmo "Crystal Structure Algorithm".

Construtor:
  • São inicializados dois parâmetros: "popSize" (tamanho da população de cristais, 30 por padrão) e "numRandomCrystals" (quantidade de cristais selecionados aleatoriamente usados em um dos cálculos de "Fc", 3 por padrão).
  • Esses parâmetros são adicionados à estrutura que armazena as informações sobre os parâmetros do algoritmo.
SetParams ():
  • Este método permite atualizar os valores dos parâmetros "popSize" e "numRandomCrystals" com os dados fornecidos por uma estrutura externa de parâmetros.
  • É feita uma validação para garantir que "numRandomCrystals" seja sempre pelo menos 1 e não ultrapasse "popSize" menos 1, evitando cálculos inválidos.
Métodos públicos:
  • Init (), método responsável pela inicialização do algoritmo, que recebe informações sobre os intervalos e passos das variáveis de entrada, além da quantidade de épocas;
  • Moving (), método que implementa a etapa de movimentação dos cristais no algoritmo;
  • Revision (), método responsável por revisar ou atualizar o estado da população.
Campos públicos da classe:
  • numRandomCrystals, quantidade de cristais aleatórios usada no cálculo de "Fc".
Campos privados da classe:
  • meanPosition, array que armazena a posição média de todos os cristais da população;
  • bestCrystalIdx, índice do melhor cristal da população atual;
  • isFirstIteration, flag booleana que indica se a iteração atual é a primeira após a inicialização;
  • CalculateMeanPosition (), método para calcular a posição média dos cristais;
  • CalculateFc (), método para calcular o valor de "Fc", um critério que depende dos cristais selecionados;
  • SelectRandomCrystal (), método para selecionar aleatoriamente um cristal da população, com a possibilidade de excluir um cristal específico.

De modo geral, a classe "C_AO_CryStAl" implementa um algoritmo de otimização inspirado no comportamento dos cristais. Ela opera sobre uma população de "cristais", cada um com sua própria posição. O algoritmo inclui etapas de movimentação dos cristais, avaliação de seus valores de fitness e determinação da melhor solução.

//————————————————————————————————————————————————————————————————————
class C_AO_CryStAl : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_CryStAl () { }
  C_AO_CryStAl ()
  {
    ao_name = "CryStAl";
    ao_desc = "Crystal Structure Algorithm";
    ao_link = "https://www.mql5.com/ru/articles/19899";

    popSize           = 30; // crystal population size
    numRandomCrystals = 3;  // number of random crystals for Fc

    ArrayResize (params, 2);
    params [0].name = "popSize";           params [0].val = popSize;
    params [1].name = "numRandomCrystals"; params [1].val = numRandomCrystals;
  }

  void SetParams ()
  {
    popSize           = (int)params [0].val;
    numRandomCrystals = (int)params [1].val;

    if (numRandomCrystals < 1) numRandomCrystals = 1;
    if (numRandomCrystals > popSize - 1) numRandomCrystals = popSize - 1;
  }

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

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  int    numRandomCrystals;   // number of random crystals for calculating Fc

  private: //---------------------------------------------------------
  double meanPosition [];     // the mean position of all crystals (Cr_main)
  int    bestCrystalIdx;      // best crystal index
  bool   isFirstIteration;    // flag for the first iteration after initialization

  void CalculateMeanPosition ();
  void CalculateFc           (int currentCrystal, double &fcArray []);
  int  SelectRandomCrystal   (int excludeCrystal);
};
//————————————————————————————————————————————————————————————————————

O método "Init" é responsável por inicializar o algoritmo "Crystal Structure Algorithm" antes do início de sua execução.

Primeiro, é chamado o método "StandardInit", que executa as operações básicas de preparação comuns a todos os algoritmos derivados de "C_AO". Se essa etapa básica falhar, o método "Init" também falhará. Em seguida, é criado o array "meanPosition" (posição média). O tamanho desse array é definido pela variável "coords", que armazena o número de dimensões. Todos os seus elementos são inicializados com zero. Isso significa que, no início da execução do algoritmo, a posição média de todos os cristais é considerada igual a zero.

Definição dos valores iniciais dos flags:
  • bestCrystalIdx (índice do melhor cristal) recebe o valor 0. Assim, no início, considera-se que o primeiro cristal da população é o melhor.
  • isFirstIteration (flag da primeira iteração) recebe o valor "true". Isso indica que a próxima iteração executada será a primeira após a inicialização completa.
O método retorna "true", indicando que a inicialização foi concluída com sucesso.
//————————————————————————————————————————————————————————————————————
//--- Initialization
bool C_AO_CryStAl::Init (const double &rangeMinP  [],
                         const double &rangeMaxP  [],
                         const double &rangeStepP [],
                         const int     epochsP)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Array initialization
  ArrayResize     (meanPosition, coords);
  ArrayInitialize (meanPosition, 0.0);

  bestCrystalIdx   = 0;
  isFirstIteration = true;

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

O método "CalculateMeanPosition" é responsável por calcular a posição média de todos os "cristais" da população. Primeiro, todos os elementos do array "meanPosition" (que armazena a média de cada coordenada) são reinicializados com zero. Dessa forma, antes de começar a somar as posições atuais dos cristais, o cálculo sempre parte do zero.

Em seguida, o método percorre cada cristal da população. Para cada cristal, percorre também todas as suas coordenadas, cujo número é definido por "coords". O valor de cada coordenada do cristal atual é adicionado ao elemento correspondente do array "meanPosition". Assim, "meanPosition" acumula gradualmente a soma dos valores de cada coordenada de todos os cristais.

Depois que a soma é concluída, o método percorre novamente todas as coordenadas. Cada valor acumulado em "meanPosition" é dividido pela quantidade total de cristais "popSize". Essa transformação converte a soma das coordenadas em seu valor médio. Ao final da execução do método, o array "meanPosition" contém o valor médio de cada coordenada considerando todos os cristais da população.

//————————————————————————————————————————————————————————————————————
//--- Calculating the mean position of all crystals (Cr_main)
void C_AO_CryStAl::CalculateMeanPosition ()
{
  // Reset the mean values to zero
  ArrayInitialize (meanPosition, 0.0);

  // Sum the current positions of all crystals
  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      meanPosition [c] += a [i].c [c];
    }
  }

  // Divide by the number of crystals to obtain the mean
  for (int c = 0; c < coords; c++)
  {
    meanPosition [c] /= (double)popSize;
  }
}
//————————————————————————————————————————————————————————————————————

O método "CalculateFc" é responsável por calcular a chamada "média dos cristais selecionados aleatoriamente". Ele recebe como entrada o índice do cristal atual "currentCrystal" e o array "fcArray", no qual o resultado será armazenado.

No início, todos os elementos do array "fcArray" são reinicializados com zero. Isso garante que, a cada novo cálculo de "Fc", a soma comece do zero. Em seguida, o método executa um laço que se repete "numRandomCrystals" vezes. Em cada iteração desse laço:

  • é selecionado aleatoriamente um índice de cristal "randomCrystal". É importante observar que a seleção exclui o próprio "currentCrystal", ou seja, o cristal para o qual "Fc" está sendo calculado naquele momento;
  • as coordenadas do cristal selecionado aleatoriamente são adicionadas aos elementos correspondentes do array "fcArray". Assim, esse array acumula a soma das coordenadas de todos os cristais selecionados aleatoriamente.
Depois que as posições dos "numRandomCrystals" cristais selecionados aleatoriamente são somadas, o método percorre cada coordenada. Cada soma acumulada em "fcArray" é dividida pela quantidade total de cristais selecionados aleatoriamente, "numRandomCrystals". Com isso, a soma é convertida em um valor médio.

Ao final, o array "fcArray" conterá as coordenadas do cristal "médio", obtido pela média das posições de vários cristais selecionados aleatoriamente da população, excluindo o cristal atual.

//————————————————————————————————————————————————————————————————————
//--- Calculation of Fc (average value of randomly selected crystals)
void C_AO_CryStAl::CalculateFc (int currentCrystal, double &fcArray [])
{
  // Initialize the array with zeros
  ArrayInitialize (fcArray, 0.0);

  // Select numRandomCrystals random crystals (excluding the current one)
  for (int i = 0; i < numRandomCrystals; i++)
  {
    int randomCrystal = SelectRandomCrystal (currentCrystal);

    // Sum the positions of the selected crystals
    for (int c = 0; c < coords; c++)
    {
      fcArray [c] += a [randomCrystal].c [c];
    }
  }

  // Calculate the mean value
  for (int c = 0; c < coords; c++)
  {
    fcArray [c] /= (double)numRandomCrystals;
  }
}
//————————————————————————————————————————————————————————————————————

O método "SelectRandomCrystal" é responsável por selecionar aleatoriamente um "cristal" de toda a população, com uma condição importante: o cristal indicado por "excludeCrystal" não pode ser selecionado.

O método inicia um laço infinito, que continua até que a condição de saída seja satisfeita. Dentro desse laço, é gerado aleatoriamente um índice no intervalo correspondente aos cristais da população, de 0 a "popSize" menos 1.

Depois que o índice aleatório é gerado, é feita a seguinte verificação:

  • se o índice gerado "selected" for igual ao índice do cristal que deve ser excluído, "excludeCrystal", o laço continua e um novo índice aleatório é gerado;
  • se o índice gerado for diferente de "excludeCrystal", a condição de saída do laço é satisfeita.
Assim que é encontrado um índice aleatório diferente de "excludeCrystal", esse índice é retornado como resultado do método.
//————————————————————————————————————————————————————————————————————
//--- Selecting a random crystal (excluding the specified one)
int C_AO_CryStAl::SelectRandomCrystal (int excludeCrystal)
{
  int selected;
  do selected = u.RNDminusOne (popSize);
  while (selected == excludeCrystal);

  return selected;
}
//————————————————————————————————————————————————————————————————————

O método "Moving" implementa o principal laço do algoritmo e é responsável pela evolução da população de "cristais" a cada iteração. Na primeira execução do algoritmo, quando "revision" é igual a "false", o método inicializa as posições de todos os cristais da população. Para cada cristal e cada uma de suas coordenadas, é gerado um valor aleatório entre 0 e 1. Esse valor é então escalonado para o intervalo entre rangeMin e rangeMax da respectiva coordenada. Em seguida, aplica-se a discretização para ajustar a posição ao passo permitido "rangeStep". Após a inicialização, o flag "revision" é definido como "true", para que as chamadas seguintes do método executem a atualização das posições em vez de uma nova inicialização.

Após a primeira iteração, calcula-se inicialmente a posição média de todos os cristais da população. O algoritmo identifica o cristal com o melhor valor da função de fitness "f" na iteração atual e armazena seu índice.

Para cada cristal da população, percorrendo os índices de 0 até "popSize" menos 1:

  • a posição atual do cristal é copiada para um armazenamento temporário, que será usado como "posição anterior" (Cr_old) nos cálculos;
  • uma das estratégias disponíveis para atualização da posição é selecionada aleatoriamente. Ao todo, são quatro estratégias, numeradas de 0 a 3;
  • em duas das quatro estratégias, as estratégias 2 e 3, é calculada a "média dos cristais selecionados aleatoriamente" (Fc), que também será usada na atualização;
  • de acordo com a estratégia selecionada, a nova posição do cristal "Cr_new" é calculada com base em sua posição anterior, na posição média da população, na posição do melhor cristal e em "Fc". Cada estratégia utiliza uma combinação própria desses componentes e de números aleatórios (r, r1, r2, r3) para atualizar a posição;
  • depois que a nova posição é calculada, verifica-se se cada coordenada permanece dentro dos intervalos definidos por rangeMin e rangeMax. Se uma coordenada ultrapassar os limites permitidos, ela é substituída por um novo valor aleatório gerado dentro desses limites;
  • por fim, a discretização é aplicada novamente a cada coordenada por meio da função u.SeInDiSp ().

O método "Moving" desloca iterativamente os "cristais" pelo espaço de busca, utilizando diferentes estratégias baseadas nas informações atuais da população, como a posição média e o melhor cristal, além de fatores aleatórios.

//————————————————————————————————————————————————————————————————————
//--- Main algorithm step
void C_AO_CryStAl::Moving ()
{
  // Initial population setup (formula 3)
  if (!revision)
  {
    // Random initialization of the crystal population
    for (int i = 0; i < popSize; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        double xi = u.RNDfromCI (0.0, 1.0);
        a [i].c [j] = rangeMin [j] + xi * (rangeMax [j] - rangeMin [j]);
        a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }

    revision = true;
    return;
  }

  // CRITICAL: Calculate the mean position and find the best crystal BEFORE updating
  CalculateMeanPosition ();

  // Find the best crystal for the current iteration
  bestCrystalIdx = 0;
  for (int i = 1; i < popSize; i++)
  {
    if (a [i].f > a [bestCrystalIdx].f)
    {
      bestCrystalIdx = i;
    }
  }

  // Main position update loop (formulas 4–7)
  for (int i = 0; i < popSize; i++)
  {
    // Save the current position as the old one (Cr_old)
    ArrayCopy (a [i].cP, a [i].c, 0, 0, coords);

    // Select a random strategy (0–3)
    int strategy = u.RNDminusOne (5);

    // Prepare the array for Fc (only for strategies 2 and 3)
    double fc [];
    ArrayResize (fc, coords);

    if (strategy == 2 || strategy == 3)
    {
      CalculateFc (i, fc);
    }

    // Apply the selected position update strategy
    switch (strategy)
    {
      case 0: // Strategy 1: Simple Cubic (formula 4)
        // Cr_new = Cr_old + r * Cr_main
        for (int c = 0; c < coords; c++)
        {
          double r = u.RNDfromCI (0.0, 1.0);
          a [i].c [c] = a [i].cP [c] + r * meanPosition [c];
        }
        break;

      case 1: // Strategy 2: Cubic with the best crystals (formula 5)
        // Cr_new = Cr_old + r1 * Cr_main + r2 * Cr_b
        for (int c = 0; c < coords; c++)
        {
          double r1 = u.RNDfromCI (0.0, 1.0);
          double r2 = u.RNDfromCI (0.0, 1.0);
          a [i].c [c] = a [i].cP [c] + r1 * meanPosition [c] + r2 * a [bestCrystalIdx].c [c];
        }
        break;

      case 2: // Strategy 3: Cubic update with the mean crystals (formula 6)
        // Cr_new = Cr_old + r1 * Cr_main + r2 * Fc
        for (int c = 0; c < coords; c++)
        {
          double r1 = u.RNDfromCI (0.0, 1.0);
          double r2 = u.RNDfromCI (0.0, 1.0);
          a [i].c [c] = a [i].cP [c] + r1 * meanPosition [c] + r2 * fc [c];
        }
        break;

      case 3: // Strategy 4: Cubic update with the best and mean crystals (formula 7)
        // Cr_new = Cr_old + r1 * Cr_main + r2 * Cr_b + r3 * Fc
        for (int c = 0; c < coords; c++)
        {
          double r1 = u.RNDfromCI (0.0, 1.0);
          double r2 = u.RNDfromCI (0.0, 1.0);
          double r3 = u.RNDfromCI (0.0, 1.0);
          a [i].c [c] = a [i].cP [c] + r1 * meanPosition [c] + r2 * a [bestCrystalIdx].c [c] + r3 * fc [c];
        }
        break;
    }

    // Checking and correcting bounds for a new position
    for (int c = 0; c < coords; c++)
    {
      // If the position went out of bounds, apply boundary handling
      if (a [i].c [c] < rangeMin [c] || a [i].c [c] > rangeMax [c])
      {
        // Generate a new random position within the valid range
        double xi = u.RNDfromCI (0.0, 1.0);
        a [i].c [c] = rangeMin [c] + xi * (rangeMax [c] - rangeMin [c]);
      }

      // Apply discretization
      a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

O método "Revision" é responsável por atualizar e armazenar as melhores soluções encontradas tanto no nível de cada "cristal" individual, isto é, sua melhor solução pessoal, quanto no nível de toda a população, ou seja, a melhor solução global.

O método percorre cada "cristal" da população para avaliar seu estado atual. Para cada cristal, seu valor atual de fitness "f" é comparado ao melhor valor de fitness já obtido por esse cristal, "fB". Se o valor atual for melhor que o melhor anterior, o valor de fitness pessoal "fB" é atualizado com o valor atual, e a posição do cristal correspondente a esse novo melhor resultado é copiada para sua melhor solução pessoal "cB".

Paralelamente à atualização das melhores soluções pessoais, o algoritmo acompanha a melhor solução encontrada por toda a população até o momento. O valor atual de fitness "f" do cristal também é comparado ao melhor valor global da população "fB". Se o valor atual do cristal superar o melhor valor global existente, o valor global "fB" é atualizado com o valor atual, e a posição do cristal correspondente a esse novo melhor resultado é copiada para a melhor solução global "cB".

Ao final da execução do método "Revision", as variáveis "fB" e "cB" armazenam, respectivamente, o maior valor da função de fitness e as coordenadas correspondentes encontradas durante toda a execução do algoritmo. Cada cristal também mantém sua própria melhor solução encontrada.

//————————————————————————————————————————————————————————————————————
//--- Updating and checking results
void C_AO_CryStAl::Revision ()
{
  // Updating the best and worst solutions for each crystal
  for (int i = 0; i < popSize; i++)
  {
    // Update the personal best solution
    if (a [i].f > a [i].fB)
    {
      a [i].fB = a [i].f;
      ArrayCopy (a [i].cB, a [i].c, 0, 0, coords);
    }

    // Update the global best solution
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ArrayCopy (cB, a [i].c, 0, 0, coords);
    }
  }
}
//————————————————————————————————————————————————————————————————————


Resultados dos testes

De acordo com os resultados dos testes, o algoritmo apresenta um desempenho bastante modesto. O próprio resultado, 26,33%, chama a atenção por se enquadrar no patamar do (random walk) em nossa tabela de classificação. Então, o que isso significa? Voltaremos a essa questão mais adiante.

CryStAl|Crystal Structure Algorithm|30.0|3.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.49130791883895686
25 Hilly's; Func runs: 10000; result: 0.3254668138312991
500 Hilly's; Func runs: 10000; result: 0.25762861219210875
=============================
5 Forest's; Func runs: 10000; result: 0.3770837459944563
25 Forest's; Func runs: 10000; result: 0.21650897859329796
500 Forest's; Func runs: 10000; result: 0.158890697466887
=============================
5 Megacity's; Func runs: 10000; result: 0.2938461538461538
25 Megacity's; Func runs: 10000; result: 0.1498461538461539
500 Megacity's; Func runs: 10000; result: 0.09889230769230854
=============================
All score: 2.36947 (26.33%)

Esses resultados me levaram a fazer algumas alterações para melhorar a convergência do algoritmo. Como se constatou, a lógica de interação entre seus componentes não é particularmente eficiente. Isso permitiu revisar algumas abordagens pouco coerentes sem abandonar a ideia geral. Vamos ver o resultado obtido na versão modificada.

Formulação da atualização das posições (Estratégias 0-3): a modificação utiliza uma abordagem diferente para atualizar as posições, baseada na adição de um deslocamento à posição atual a [i].c [c]. As fórmulas são reformuladas, embora preservem os mesmos conceitos de cada estratégia. A introdução de uma parcela da diferença entre a posição atual e a posição média representa uma alteração significativa na formulação matemática.

Perturbação aleatória adicional:é introduzida a condição adicional "if (u.RNDprobab () < 0.1)". Com probabilidade de 10%, essa condição é satisfeita e a coordenada do cristal é atualizada por meio de a[i].c[c] = u.PowerDistribution (cB[c], rangeMin[c], rangeMax[c], 20), reduzindo a probabilidade de o algoritmo ficar estagnado. As demais estratégias são aplicadas com probabilidade de 90%.

Em seguida, vem o armazenamento da posição anterior em "a[i].cP": a versão original salva a posição anterior em "a[i].cP" antes de calcular a nova posição. Na versão modificada, essa posição não é armazenada nem utilizada; a atualização é feita diretamente a partir da posição atual "a[i].c[c]".

Tratamento dos limites: na versão original, depois de aplicar a estratégia de atualização, o método percorre todas as coordenadas e, se a nova posição ultrapassar os limites, gera uma posição aleatória totalmente nova dentro do intervalo permitido. Em "C_AO_CryStAlm", primeiro é aplicada a estratégia de atualização e, em seguida, apenas a discretização por meio do método "u.SeInDiSp".

//————————————————————————————————————————————————————————————————————
//--- Main step of the algorithm
void C_AO_CryStAlm::Moving ()
{
  // Initial population initialization (formula 3)
  if (!revision)
  {
    // Random initialization of the crystal population
    for (int i = 0; i < popSize; i++)
    {
      for (int j = 0; j < coords; j++)
      {
        double xi = u.RNDfromCI (0.0, 1.0);
        a [i].c [j] = rangeMin [j] + xi * (rangeMax [j] - rangeMin [j]);
        a [i].c [j] = u.SeInDiSp (a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
      }
    }

    revision = true;
    return;
  }

  // CRITICAL: Calculate the mean position and find the best crystal BEFORE updating
  CalculateMeanPosition ();

  // Main position update loop (formulas 4–7)
  for (int i = 0; i < popSize; i++)
  {
    // Select a random strategy (0–3)
    int strategy = u.RNDminusOne (5);

    // Prepare the array for Fc (only for strategies 2 and 3)
    double fc [];
    ArrayResize (fc, coords);

    if (strategy == 2 || strategy == 3)
    {
      CalculateFc (i, fc);
    }

    for (int c = 0; c < coords; c++)
    {
      if (u.RNDprobab () < 0.1)
      {
        a [i].c [c] = u.PowerDistribution (cB [c], rangeMin [c], rangeMax [c], 20);
      }
      else
      {
        // Apply the selected position update strategy
        switch (strategy)
        {
          case 0: // Strategy 1: Simple cubic (formula 4)
          {
            // Cr_new = Cr_old + r * Cr_main
            double r = u.RNDfromCI (0.0, 1.0);

            a [i].c [c] = a [i].c [c] + r * (meanPosition [c] - a [i].c [c]);
          }
            break;

          case 1: // Strategy 2: Cubic with the best crystals (formula 5)
          {
            // Cr_new = Cr_old + r1 * Cr_main + r2 * Cr_b
            double r1 = u.RNDfromCI (0.0, 1.0);
            double r2 = u.RNDfromCI (0.0, 1.0);

            a [i].c [c] = a [i].c [c] + r1 * (meanPosition [c] - a [i].c [c]) + r2 * (cB [c] - a [i].c [c]);
          }
            break;

          case 2: // Strategy 3: Cubic with the mean crystals (formula 6)
          {
            // Cr_new = Cr_old + r1 * Cr_main + r2 * Fc
            double r1 = u.RNDfromCI (0.0, 1.0);
            double r2 = u.RNDfromCI (0.0, 1.0);

            a [i].c [c] = a [i].c [c] - r1 * (meanPosition [c] - a [i].c [c]) + r2 * (fc [c] - a [i].c [c]);
          }
            break;

          case 3: // Strategy 4: Cubic with the best and mean crystals (formula 7)
          {
            // Cr_new = Cr_old + r1 * Cr_main + r2 * Cr_b + r3 * Fc
            double r1 = u.RNDfromCI (0.0, 1.0);
            double r2 = u.RNDfromCI (0.0, 1.0);
            double r3 = u.RNDfromCI (0.0, 1.0);

            a [i].c [c] = a [i].c [c] + r1 * (meanPosition [c] - a [i].c [c]) + r2 * (cB [c] - a [i].c [c]) + r3 * (fc [c] - a [i].c [c]);
          }
            break;
        }
      }

      // Apply discretization
      a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

A inicialização não foi alterada. O método "CalculateMeanPosition" não apresenta nenhuma diferença entre as versões "C_AO_CryStAl" e "C_AO_CryStAlm"; o mesmo vale para os métodos "CalculateFc" e "SelectRandomCrystal". O método "Revision" também é implementado de forma absolutamente idêntica nas duas versões, C_AO_CryStAl e C_AO_CryStAlm.

Vamos analisar os novos resultados abaixo: eles confirmam a validade das minhas premissas teóricas. Agora veremos por que a versão original transforma uma busca direcionada em um random walk com deriva.

CryStAlm|Crystal Structure Algorithm M|30.0|3.0|

=============================
5 Hilly's; Func runs: 10000; result: 0.8065665859751701
25 Hilly's; Func runs: 10000; result: 0.5614723387106613
500 Hilly's; Func runs: 10000; result: 0.2909726108642525
=============================
5 Forest's; Func runs: 10000; result: 0.7522883470073202
25 Forest's; Func runs: 10000; result: 0.48674341114821484
500 Forest's; Func runs: 10000; result: 0.2101468006131327
=============================
5 Megacity's; Func runs: 10000; result: 0.48461538461538467
25 Megacity's; Func runs: 10000; result: 0.2701538461538461
500 Megacity's; Func runs: 10000; result: 0.11110769230769342
=============================
All score: 3.97407 (44.16%)

Quando comparamos as duas versões do Crystal Structure Algorithm, à primeira vista pode parecer que as alterações são mínimas. Ambas as versões inicializam a população da mesma forma, ambas calculam a posição média e ambas utilizam quatro estratégias de atualização. No entanto, por trás dessa semelhança aparente existem diferenças fundamentais que resultam em uma diferença de desempenho próxima de duas vezes. Vamos analisar cada alteração para entender por que a versão modificada é tão mais eficiente.

Erro fundamental: soma de coordenadas absolutas em vez de movimento direcionado.Para compreender o principal problema do algoritmo original, considere uma otimização unidimensional em que buscamos o máximo de uma função no intervalo de 0 a 100. A posição média da população, isto é, o ponto para o qual os cristais são atraídos, está em 60. Temos três cristais em posições diferentes: o primeiro em 10, o segundo em 60 e o terceiro em 90. O coeficiente aleatório r é o mesmo para todos, 0,5.

Fórmula original: x_new = x_old + r × mean

  • Cristal 1: 10 + 0,5 × 60 = 10 + 30 = 40 (deslocamento +30)
  • Cristal 2: 60 + 0,5 × 60 = 60 + 30 = 90 (deslocamento +30)
  • Cristal 3: 90 + 0,5 × 60 = 90 + 30 = 120 (deslocamento +30, FORA DOS LIMITES!)

Percebeu o problema? TODOS OS TRÊS CRISTAIS SE DESLOCARAM +30, independentemente da posição em que estavam! O primeiro cristal estava muito à esquerda do alvo e se moveu para a direita, o que faz sentido. O segundo JÁ ESTAVA no alvo e foi empurrado para a direita, o que é absurdo. O terceiro estava à direita do alvo, foi empurrado ainda mais para a direita e ultrapassou os limites, o que é um desastre.

Isso não é "movimento em direção ao alvo". É o mesmo empurrão para todos na mesma direção, como se um vento forte soprasse e deslocasse todos pela mesma distância. Além disso, a direção e a intensidade desse "vento" são determinadas pelos valores absolutos das coordenadas do alvo, e não pela posição relativa dos cristais.

Fórmula modificada: x_new = x_old + r × (mean - x_old)

  • Cristal 1: 10 + 0,5 × (60 - 10) = 10 + 25 = 35 (percorreu 50% do caminho até o alvo)
  • Cristal 2: 60 + 0,5 × (60 - 60) = 60 + 0 = 60 (já estava no alvo e permaneceu no lugar)
  • Cristal 3: 90 + 0,5 × (60 - 90) = 90 - 15 = 75 (percorreu 50% do caminho DE VOLTA ao alvo)

Percebe a diferença? Cada cristal se desloca EM DIREÇÃO ao seu alvo, proporcionalmente à distância até ele. Quem está longe dá um passo maior. Quem está perto dá um passo menor. Quem já chegou permanece no lugar. Quem ultrapassou o alvo volta. É exatamente esse o comportamento desejável em um algoritmo de otimização.

Problema 1: explosão com coordenadas de grande magnitude. Se as variáveis otimizadas tiverem, por exemplo, um intervalo de 0 a 1000 e a posição média da população estiver em torno de 600-700, então será acrescentado a CADA cristal um valor da ordem de 600-700, multiplicado por um coeficiente aleatório entre 0 e 1. Até mesmo um cristal situado na posição 650, praticamente no alvo, receberá um deslocamento de +300...+600 e será lançado para o limite 1000 ou além dele. O algoritmo acaba se transformando em um gerador de valores aleatórios nas bordas do espaço de busca.

Problema 2: dependência da escala das coordenadas. Se o problema tiver variáveis em escalas diferentes, por exemplo, x de 0 a 10 e y de 0 a 1000, os deslocamentos em y serão 100 vezes maiores que os deslocamentos em x simplesmente por causa da diferença de escala! Isso não tem qualquer relação com a distância do cristal até o ótimo em cada coordenada.

Problema 3: todos os cristais se deslocam na mesma direção. Quando o mesmo vetor é adicionado a todos os cristais, ainda que com coeficientes aleatórios diferentes, toda a população se desloca como um todo na mesma direção. Isso não é uma "busca pelo ótimo", mas uma "deriva da população". Imagine um bando de aves que deveria procurar alimento, mas, em vez disso, todas voam sincronizadamente para nordeste, com apenas pequenos desvios aleatórios. Isso não é busca, é migração.

Problema 4: reinicializações constantes. Devido aos enormes deslocamentos, os cristais ultrapassam os limites com frequência. O algoritmo precisa então reinicializá-los em posições aleatórias. Isso significa que toda a informação acumulada sobre a direção do movimento é perdida. O cristal estava se deslocando em direção a uma região promissora, ultrapassou os limites e foi "teletransportado" para um ponto aleatório, apagando todo o histórico de seu movimento. Na prática, a cada poucas iterações o algoritmo volta a se comportar como uma busca aleatória.

Memória da melhor solução: local versus global.A segunda alteração importante está relacionada a qual "melhor solução" é usada nas fórmulas. A versão modificada mantém um registro global dos melhores resultados. Quando alguém encontra uma altitude de 3000 metros, essa informação é registrada e nunca mais esquecida. Mesmo que depois todos os pesquisadores desçam, o algoritmo continua se lembrando: "O melhor lugar que já encontramos foi ali, a 3000 metros de altitude". É justamente para esse ponto que os demais exploradores são atraídos. Isso é especialmente importante em problemas de otimização, nos quais a população pode sofrer uma piora temporária. Por exemplo, após aplicar uma mutação, todas as soluções podem ficar piores. Na versão original, o algoritmo passará a seguir essas soluções inferiores. Na versão modificada, ele continuará orientado para a melhor solução efetivamente encontrada durante toda a execução.

Adição de intensificação local. A terceira alteração fundamental diz respeito a um novo mecanismo que não existe na versão original. Na versão modificada, com probabilidade de 10%, cada coordenada do cristal é gerada de maneira especial nas proximidades da melhor solução global por meio da função "PowerDistribution", com o parâmetro 20. O algoritmo original funciona como uma meta-heurística pura: os cristais se deslocam por todo o espaço de busca de acordo com suas estratégias. Já a versão modificada acrescenta um elemento de busca local: cada coordenada tem 10% de probabilidade de ser gerada nas proximidades da melhor solução encontrada. O parâmetro 20 em PowerDistribution produz uma concentração muito forte, algo semelhante a procurar as chaves em um raio de meio metro do local onde você se lembra de tê-las visto pela última vez. Não se trata de escolher um ponto aleatório qualquer nessa região, mas de gerar um ponto com probabilidade muito alta de ficar bem próximo do centro.

Simplificação do tratamento dos limites. A quarta alteração diz respeito ao que acontece quando um cristal tenta ultrapassar os limites permitidos do espaço de busca. Na versão modificada, a coordenada é simplesmente passada para a função "u.SeInDiSp", que limita o valor ao intervalo permitido e aplica a discretização. Por que essa mudança se tornou possível? Porque, graças às fórmulas corretas de movimentação, os cristais da versão modificada raramente ultrapassam os limites.

Na versão original, devido à soma direta de vetores de coordenadas absolutas, os cristais ultrapassam os limites constantemente, tornando a reinicialização necessária. Mas essa reinicialização tem um custo elevado: primeiro, exige a geração de números aleatórios e cálculos adicionais; segundo, transforma um movimento direcionado em um deslocamento aleatório. O cristal podia estar se movendo em uma direção promissora, ultrapassar o limite e acabar em uma posição completamente aleatória, perdendo toda a trajetória acumulada.

Otimização do uso da memória. A quinta alteração pode parecer tecnicamente pequena, mas mostra uma compreensão mais precisa do que o algoritmo realmente precisa para funcionar. Na versão original, antes de atualizar a posição do cristal, sua posição atual é copiada para um array especial cP, que armazena a posição anterior, e essa cópia passa a ser usada nas fórmulas. Por exemplo, a nova coordenada é calculada como a coordenada anterior armazenada em cP mais algum termo adicional. Na versão modificada, essa cópia deixa de existir. A fórmula simplesmente utiliza o valor atual da coordenada, calcula a alteração e grava o resultado de volta na mesma variável.

Por que a versão original precisava dessa cópia? Porque a fórmula tinha a forma "x_new = x_old + algo". Para calcular o lado direito, era necessário preservar x_old antes de iniciar as alterações. Na versão modificada, a fórmula foi reorganizada de modo que seja possível trabalhar diretamente com o valor atual: "x = x + alteração". Para uma população de 30 cristais em um espaço de 10 dimensões, isso elimina 300 operações de cópia de dados a cada iteração. Pode parecer pouco, mas, quando o algoritmo executa milhares de iterações, esses pequenos custos se acumulam.

Na visualização, podemos acompanhar como a versão modificada forma "formas cristalinas" durante a execução. Também é possível observar a dispersão dos valores nas funções de baixa e média dimensionalidade.

Hilly

CryStAlm na função de teste Hilly

Forest

CryStAlm na função de teste Forest

Megacity

CryStAlm na função de teste Forest

Ackley

CryStAlm na função de teste padrão Ackley

Na tabela de classificação, o algoritmo CryStAlm é apresentado apenas para referência, pois faltou muito pouco para que obtivesse uma posição no ranking.

AO Description Hilly Hilly
Final
Forest Forest
Final
Megacity (discrete) Megacity
Final
Final
Result
% of
MAX
10 p (5 F) 50 p (25 F) 1.000 p (500 F) 10 p (5 F) 50 p (25 F) 1.000 p (500 F) 10 p (5 F) 50 p (25 F) 1.000 p (500 F)
1 DOAdingom 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
2 ANS across neighbourhood search 0,94948 0,84776 0,43857 2,23581 1,00000 0,92334 0,39988 2,32323 0,70923 0,63477 0,23091 1,57491 6,134 68,15
3 CLA code lock algorithm (joo) 0,95345 0,87107 0,37590 2,20042 0,98942 0,91709 0,31642 2,22294 0,79692 0,69385 0,19303 1,68380 6,107 67,86
4 AMOm animal migration ptimization M 0,90358 0,84317 0,46284 2,20959 0,99001 0,92436 0,46598 2,38034 0,56769 0,59132 0,23773 1,39675 5,987 66,52
5 (P+O)ES (P+O) evolution strategies 0,92256 0,88101 0,40021 2,20379 0,97750 0,87490 0,31945 2,17185 0,67385 0,62985 0,18634 1,49003 5,866 65,17
6 CTA comet tail algorithm (joo) 0,95346 0,86319 0,27770 2,09435 0,99794 0,85740 0,33949 2,19484 0,88769 0,56431 0,10512 1,55712 5,846 64,96
7 TETA time evolution travel algorithm (joo) 0,91362 0,82349 0,31990 2,05701 0,97096 0,89532 0,29324 2,15952 0,73462 0,68569 0,16021 1,58052 5,797 64,41
8 SDSm stochastic diffusion search M 0,93066 0,85445 0,39476 2,17988 0,99983 0,89244 0,19619 2,08846 0,72333 0,61100 0,10670 1,44103 5,709 63,44
9 BOAm billiards optimization algorithm M 0,95757 0,82599 0,25235 2,03590 1,00000 0,90036 0,30502 2,20538 0,73538 0,52523 0,09563 1,35625 5,598 62,19
10 AAm archery algorithm M 0,91744 0,70876 0,42160 2,04780 0,92527 0,75802 0,35328 2,03657 0,67385 0,55200 0,23738 1,46323 5,598 61,64
11 ESG evolution of social groups (joo) 0,99906 0,79654 0,35056 2,14616 1,00000 0,82863 0,13102 1,95965 0,82333 0,55300 0,04725 1,42358 5,529 61,44
12 SIA simulated isotropic annealing (joo) 0,95784 0,84264 0,41465 2,21513 0,98239 0,79586 0,20507 1,98332 0,68667 0,49300 0,09053 1,27020 5,469 60,76
13 EOm 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
14 BBO 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
15 ACS artificial cooperative search 0,75547 0,74744 0,30407 1,80698 1,00000 0,88861 0,22413 2,11274 0,69077 0,48185 0,13322 1,30583 5,226 58,06
16 DA dialectical algorithm 0,86183 0,70033 0,33724 1,89940 0,98163 0,72772 0,28718 1,99653 0,70308 0,45292 0,16367 1,31967 5,216 57,95
17 BHAm black hole algorithm M 0,75236 0,76675 0,34583 1,86493 0,93593 0,80152 0,27177 2,00923 0,65077 0,51646 0,15472 1,32195 5,196 57,73
18 ASO anarchy society optimization 0,84872 0,74646 0,31465 1,90983 0,96148 0,79150 0,23803 1,99101 0,57077 0,54062 0,16614 1,27752 5,178 57,54
19 RFO royal flush optimization (joo) 0,83361 0,73742 0,34629 1,91733 0,89424 0,73824 0,24098 1,87346 0,63154 0,50292 0,16421 1,29867 5,089 56,55
20 AOSm atomic orbital search M 0,80232 0,70449 0,31021 1,81702 0,85660 0,69451 0,21996 1,77107 0,74615 0,52862 0,14358 1,41835 5,006 55,63
21 TSEA algoritmo de evolução do casco de tartaruga (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
22 BSA 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
23 DE differential evolution 0,95044 0,61674 0,30308 1,87026 0,95317 0,78896 0,16652 1,90865 0,78667 0,36033 0,02953 1,17653 4,955 55,06
24 SRA successful restaurateur algorithm (joo) 0,96883 0,63455 0,29217 1,89555 0,94637 0,55506 0,19124 1,69267 0,74923 0,44031 0,12526 1,31480 4,903 54,48
25 CRO chemical reaction optimisation 0,94629 0,66112 0,29853 1,90593 0,87906 0,58422 0,21146 1,67473 0,75846 0,42646 0,12686 1,31178 4,892 54,36
26 BIO blood inheritance optimization (joo) 0,81568 0,65336 0,30877 1,77781 0,89937 0,65319 0,21760 1,77016 0,67846 0,47631 0,13902 1,29378 4,842 53,80
27 DOA 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
28 BSA bird swarm algorithm 0,89306 0,64900 0,26250 1,80455 0,92420 0,71121 0,24939 1,88479 0,69385 0,32615 0,10012 1,12012 4,809 53,44
29 DEA 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
30 HS harmony search 0,86509 0,68782 0,32527 1,87818 0,99999 0,68002 0,09590 1,77592 0,62000 0,42267 0,05458 1,09725 4,751 52,79
31 SSG semeadura e crescimento de mudas 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
32 BCOm bacterial chemotaxis optimization M 0,75953 0,62268 0,31483 1,69704 0,89378 0,61339 0,22542 1,73259 0,65385 0,42092 0,14435 1,21912 4,649 51,65
33 ABO african buffalo optimization 0,83337 0,62247 0,29964 1,75548 0,92170 0,58618 0,19723 1,70511 0,61000 0,43154 0,13225 1,17378 4,634 51,49
34 (PO)ES (PO) evolution strategies 0,79025 0,62647 0,42935 1,84606 0,87616 0,60943 0,19591 1,68151 0,59000 0,37933 0,11322 1,08255 4,610 51,22
35 FBA 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
36 TSm tabu search M 0,87795 0,61431 0,29104 1,78330 0,92885 0,51844 0,19054 1,63783 0,61077 0,38215 0,12157 1,11449 4,536 50,40
37 BSO brain storm optimization 0,93736 0,57616 0,29688 1,81041 0,93131 0,55866 0,23537 1,72534 0,55231 0,29077 0,11914 0,96222 4,498 49,98
38 WOAm wale optimization algorithm M 0,84521 0,56298 0,26263 1,67081 0,93100 0,52278 0,16365 1,61743 0,66308 0,41138 0,11357 1,18803 4,476 49,74
39 AEFA artificial electric field algorithm 0,87700 0,61753 0,25235 1,74688 0,92729 0,72698 0,18064 1,83490 0,66615 0,11631 0,09508 0,87754 4,459 49,55
40 AEO artificial ecosystem-based optimization algorithm 0,91380 0,46713 0,26470 1,64563 0,90223 0,43705 0,21400 1,55327 0,66154 0,30800 0,28563 1,25517 4,454 49,49
41 CAm 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
42 ACOm ant colony optimization M 0,88190 0,66127 0,30377 1,84693 0,85873 0,58680 0,15051 1,59604 0,59667 0,37333 0,02472 0,99472 4,438 49,31
43 CMAES 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
44 DA_duelist 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
45 BFO-GA bacterial foraging optimization - ga 0,89150 0,55111 0,31529 1,75790 0,96982 0,39612 0,06305 1,42899 0,72667 0,27500 0,03525 1,03692 4,224 46,93
CryStAlm crystal_structure_algorithm_M 0,80656 0,56147 0,29097 1,65900 0,75228 0,48674 0,21014 1,44916 0,48461 0,27015 0,11111 0,86587 3,974 44,16
RW random walk 0,48754 0,32159 0,25781 1,06694 0,37554 0,21944 0,15877 0,75375 0,27969 0,14917 0,09847 0,52734 2,348 26,09


Conclusões

Uma ideia elegante arruinada pela implementação. O Crystal Structure Algorithm surgiu em 2021 com uma proposta atraente: aplicar os princípios de formação das estruturas cristalinas à otimização global. Os autores propuseram um algoritmo populacional em que os "cristais" se deslocam sob a influência da posição média da população, da melhor solução encontrada e de vizinhos selecionados aleatoriamente. Quatro estratégias de movimentação e um modelo matemático elegante. No papel, tudo parecia convincente.

O problema é que entre a ideia conceitual e uma implementação funcional existe uma etapa crítica: traduzir a metáfora física em operações matemáticas concretas. E é justamente nesse ponto que o CryStAl original falha. Fórmulas que parecem razoáveis no artigo acabam, na prática, transformando uma busca direcionada em um movimento errático. Não se trata apenas de uma história de "encontramos um bug e o corrigimos", mas de uma lição fundamental: no desenvolvimento de meta-heurísticas, o conceito e sua formulação matemática precisam estar em sintonia. Caso contrário, o algoritmo se comportará de maneira diferente da pretendida ou simplesmente não funcionará.

O descompasso entre a ideia e a execução. Vamos analisar o que os autores QUERIAM implementar no CryStAl original. A ideia é clara: os cristais deveriam ser atraídos por três pontos principais: a posição média de toda a população (centro de massa, informação global), o melhor cristal encontrado (intensificação) e a média dos vizinhos selecionados aleatoriamente (informação local, diversidade). As quatro estratégias combinam essas atrações de maneiras diferentes. Em teoria, isso cria um equilíbrio entre a diversificação para novas regiões do espaço de busca e a intensificação da busca em regiões promissoras. A analogia física é elegante: os átomos interagem com seus vizinhos e tendem a uma configuração de equilíbrio. Os autores QUERIAM implementar uma "atração em direção ao alvo", mas ESCREVERAM uma "adição de um vetor". São operações diferentes!

Erro sistêmico de raciocínio: fórmulas em vez de geometria. A raiz do problema é mais profunda do que uma única fórmula incorreta. Trata-se de um erro conceitual no projeto do algoritmo: concentrar-se nas fórmulas sem compreender o que elas realmente fazem na geometria do espaço de busca. Os autores do CryStAl original escreveram: "o cristal se desloca em direção à posição média, fórmula: x + r × mean". Eles se concentraram na expressão algébrica sem visualizar o que aconteceria com os cristais situados em diferentes regiões do espaço. A abordagem correta exige fazer algumas perguntas:

  • O que deve acontecer com um cristal que já se encontra no ponto-alvo? Resposta: ele deve permanecer ali ou dar um passo muito pequeno. A fórmula original o afasta desse ponto. A fórmula modificada o mantém no lugar (quando x_old = mean, temos x + 0 × (mean - x) = x).
  • O que deve acontecer com um cristal que está além do alvo? Resposta: ele deve retornar em direção ao alvo. A fórmula original o empurra ainda mais na mesma direção. A fórmula modificada inverte o movimento (uma diferença negativa produz um deslocamento de volta).
  • A magnitude do passo deve depender dos valores absolutos das coordenadas? Resposta: não, ela deve depender da distância até o alvo. A fórmula original depende dos valores absolutos. A modificada depende das distâncias.
  • O que acontece quando as variáveis têm escalas diferentes? Na versão original, os deslocamentos nas diferentes coordenadas não são proporcionais às respectivas distâncias relativas até o alvo. Na versão modificada, os deslocamentos são automaticamente escalonados de acordo com essas distâncias relativas.

As respostas a essas perguntas mostram imediatamente que a fórmula x + r × target é geometricamente inadequada para modelar uma atração. Já a fórmula x + r × (target - x) está geometricamente correta: trata-se de uma interpolação, um deslocamento correspondente à fração r do caminho até o alvo, com a propriedade natural de reduzir o passo à medida que o cristal se aproxima.

Muitas fórmulas não corrigem uma base incorreta. Pode parecer que as quatro estratégias do CryStAl compensariam as deficiências umas das outras. A estratégia 0, com um único ponto-alvo, funciona mal, mas a estratégia 3, com três pontos-alvo, não deveria funcionar melhor? Na prática, ocorre o contrário: combinar operações incorretas apenas agrava o problema.

Fórmula da estratégia 3: x_new = x_old + r1 × mean + r2 × best + r3 × fc, em que fc é a média dos cristais selecionados aleatoriamente. Agora estamos somando TRÊS valores elevados, isto é, as coordenadas absolutas de três pontos-alvo. Considere o seguinte exemplo: variáveis de 0 a 1000, cristal em [640, 430, 770], mean = [650, 420, 780], best = [645, 425, 775], fc = [655, 415, 785], com coeficientes r1 = r2 = r3 = 0,4.

Primeira coordenada: 640 + 0,4 × 650 + 0,4 × 645 + 0,4 × 655 = 640 + 260 + 258 + 262 = 1420, ainda mais além do limite permitido!

A estratégia 3 deveria ser a mais sofisticada e eficiente, pois combina informação global, informação local e intensificação. Na prática, porém, ela gera os maiores valores anômalos justamente por somar ainda mais grandezas absolutas. O problema não está na quantidade de componentes, mas no fato de que a operação básica, a soma direta de coordenadas absolutas, é fundamentalmente incorreta.

Da busca aleatória à otimização direcionada. O que o CryStAl original realmente faz na prática? Após algumas iterações, estabelece-se o seguinte padrão de funcionamento: a cada passo, a população recebe deslocamentos enormes, a maioria dos cristais ultrapassa os limites e suas coordenadas são reinicializadas aleatoriamente. Na prática, a cada 2 ou 3 iterações o algoritmo gera uma nova população quase aleatória, preservando apenas uma pequena parcela das coordenadas da geração anterior.

O CryStAlm modificado funciona de maneira fundamentalmente diferente. Os cristais realmente se deslocam em direção aos pontos-alvo, em vez de se moverem de forma caótica. As trajetórias são suaves e controladas. Os cristais raramente ultrapassam os limites. A população converge gradualmente e de forma ordenada para regiões promissoras, preservando ao mesmo tempo a diversidade graças às estratégias aleatórias e ao mecanismo de repulsão. Cada iteração acumula informações sobre a paisagem da função e as utiliza nos passos seguintes, sem apagar o estado anterior por meio de regenerações aleatórias.

Lição metodológica para pesquisadores. A história do CryStAl não é apenas um exemplo de implementação malsucedida. Trata-se de um problema sistêmico no desenvolvimento de algoritmos meta-heurísticos.

Contexto mais amplo: a epidemia de algoritmos fracos. O CryStAl não é um caso isolado. Na literatura sobre meta-heurísticas dos últimos 15 anos, existem centenas de "novos" algoritmos inspirados em diferentes metáforas, e muitos deles sofrem do mesmo problema: uma ideia atraente, uma implementação pouco rigorosa, testes em um conjunto limitado de problemas e uma publicação que reivindica competitividade. Depois, outros pesquisadores adotam esses algoritmos como baseline e fazem comparações com eles, criando uma cadeia de comparações apoiada em bases fundamentalmente frágeis.

A modificação do CryStAl para CryStAlm permite uma análise mais profunda do comportamento do algoritmo. As alterações no código são mínimas: reescrever as fórmulas de movimentação, adicionar PowerDistribution e usar cB em vez de bestCrystalIdx. No entanto, as mudanças no comportamento são fundamentais.

A comparação entre CryStAl e CryStAlm oferece várias lições importantes para a comunidade de pesquisa em otimização meta-heurística:

Lição 1: conceito não é implementação.. Uma boa ideia de algoritmo é apenas o começo. Traduzir essa ideia em operações matemáticas exige uma compreensão profunda da geometria do espaço de busca. As fórmulas precisam fazer aquilo que se espera delas, e não apenas parecer plausíveis no papel. Verifique não só "o que a fórmula calcula", mas também "o que ela faz com os agentes em diferentes situações".

Lição 2: a abstração esconde erros. Quando se escreve que "o cristal é atraído para a posição média", é fácil deixar passar o fato de que a fórmula implementa não uma atração, mas um deslocamento paralelo. O raciocínio abstrato, como "movimento em direção ao alvo", precisa ser acompanhado por uma verificação concreta, como "de que maneira exatamente as coordenadas serão alteradas". Sempre teste as fórmulas com exemplos numéricos específicos.

Lição 3: testes padronizados não são suficientes.. Testes em benchmarks CEC com variáveis normalizadas podem esconder problemas críticos que só aparecem em escalas realistas ou com intervalos distintos entre as variáveis. Inclua testes em condições "desfavoráveis": intervalos muito amplos, escalas distintas entre as variáveis e ótimos estreitos. Visualize o comportamento do algoritmo, e não apenas os resultados finais.

Lição 4: vários mecanismos não compensam uma base incorreta.. Se a operação fundamental, como a fórmula de movimentação, estiver errada, adicionar camadas mais sofisticadas não resolverá o problema. Primeiro, certifique-se de que a base funciona corretamente; depois, acrescente melhorias. Um mecanismo correto vale mais do que dez mecanismos incorretos.

Lição 5: comparações exigem validação do baseline. Se o seu novo algoritmo supera um método existente, faça uma pergunta simples: esse baseline realmente funciona? Uma verificação rápida consiste em compará-lo com uma busca aleatória. Se a diferença for pequena, você está se comparando com uma referência sem capacidade real, e o seu resultado, por si só, não significa muita coisa.

Lição 6: os detalhes da implementação são fundamentais. A diferença entre uma fórmula "quase correta" e uma fórmula "correta" pode ter um impacto enorme no desempenho. x + r × mean e x + r × (mean - x) diferem por apenas alguns caracteres no código, mas podem resultar em uma diferença de aproximadamente duas vezes na qualidade dos resultados. Os detalhes importam. Não os trate como "minúcias técnicas".

O CryStAl demonstra que uma meta-heurística não é apenas uma coleção de metáforas e fórmulas. Trata-se de uma disciplina de engenharia, na qual compreender como as operações afetam a dinâmica da busca no espaço de soluções é tão importante quanto a ideia original. Uma boa ideia implementada sem o devido rigor resulta em um algoritmo que se comporta como uma busca aleatória. A mesma ideia, implementada com compreensão da geometria e da dinâmica da busca, resulta em uma ferramenta que realmente funciona.

Para que essa área avance, não basta criar novas metáforas. É preciso desenvolver algoritmos que realmente façam aquilo que se afirma que fazem: percorrer o espaço de busca de forma direcionada, acumular informações e convergir para soluções ótimas. E isso exige mais do que simplesmente escrever fórmulas; é necessário analisar profundamente se essas fórmulas realmente implementam a ideia pretendida.

tab

Figura 2. Gradação de cores dos algoritmos nos testes correspondentes

chart

Figura 3. Histograma dos resultados dos testes dos algoritmos (em uma escala de 0 a 100, quanto maior, melhor, sendo 100 o resultado teórico máximo possível; o arquivo contém um script para calcular a tabela de classificação)

Vantagens e desvantagens do algoritmo CryStAlm:

Vantagens:

  1. Implementação relativamente simples.
  2. Rápido.
  3. Resultados médios consistentes (cor amarela, sem resultados muito fracos em testes individuais).

Desvantagens:

  1. Dispersão dos valores em funções de baixa e média dimensionalidade.

O artigo inclui um arquivo compactado com as versões atuais dos códigos dos algoritmos. O autor do artigo não se responsabiliza pela exatidão absoluta da descrição dos algoritmos canônicos, pois muitos deles foram modificados para melhorar sua capacidade de busca. As conclusões e avaliações apresentadas nos artigos baseiam-se nos resultados dos experimentos realizados.


Programas utilizados neste artigo

# Nome Tipo Descrição
1 #C_AO.mqh
Arquivo de inclusão
Classe base dos algoritmos de otimização populacional
2 #C_AO_enum.mqh
Arquivo de inclusão
Enumeração dos algoritmos de otimização populacional
3 TestFunctions.mqh
Arquivo de inclusão
Biblioteca de funções de teste
4
TestStandFunctions.mqh
Arquivo de inclusão
Biblioteca de funções do ambiente de testes
5
Utilities.mqh
Arquivo de inclusão
Biblioteca de funções auxiliares
6
CalculationTestResults.mqh
Arquivo de inclusão
Script para calcular os resultados da tabela comparativa
7
Testing AOs.mq5
Script Ambiente de testes unificado para todos os algoritmos de otimização populacional
8
Simple use of population optimization algorithms.mq5
Script
Exemplo simples de uso de algoritmos de otimização populacional sem visualização
9
Test_AO_CryStAlm.mq5
Script Ambiente de testes do CryStAlm

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/19899

Arquivos anexados |
CryStAlm.zip (294.78 KB)
Redes neurais no trading: abordagem semântica baseada em spikes para identificação espaçotemporal (Conclusão) Redes neurais no trading: abordagem semântica baseada em spikes para identificação espaçotemporal (Conclusão)
Em nossa interpretação, a S3CE-Net traduz com eficiência o mercado para a linguagem dos eventos e capta impulsos iniciais que os indicadores tradicionais simplesmente diluem em médias. O STFS mantém a estabilidade do treinamento: o modelo observa os dados sob diferentes perspectivas e não se sobreajusta a anomalias locais. Os blocos SSAM e a implementação em OpenCL proporcionam velocidade e precisão na prática, enquanto a separação entre os modos de treinamento e operação reduz o consumo de recursos em produção.
Análise da influência dos ciclos solares e lunares sobre os preços das moedas Análise da influência dos ciclos solares e lunares sobre os preços das moedas
E se os ciclos lunares e os padrões sazonais influenciarem os mercados cambiais? Este artigo mostra como traduzir conceitos astrológicos para a linguagem da matemática e do machine learning. Criei um sistema em Python com 88 atributos baseados em ciclos astronômicos, treinei o CatBoost com 15 anos de dados do EUR/USD e obtive resultados intrigantes. O código é aberto, os métodos podem ser verificados e as conclusões são inesperadas: a sabedoria antiga encontra o gradient boosting.
Métodos de amostragem MCMC - Algoritmo de Metropolis-Hastings Métodos de amostragem MCMC - Algoritmo de Metropolis-Hastings
O algoritmo de Metropolis-Hastings é um método fundamental de Monte Carlo via cadeias de Markov (MCMC), amplamente utilizado para aproximar distribuições a posteriori na inferência bayesiana. O artigo apresenta os fundamentos teóricos do algoritmo, a implementação da classe MHSampler em MQL5 e exemplos de aplicação com análise das amostras obtidas.
Redes neurais em trading: abordagem semântica baseada em spikes para identificação espaço-temporal (Componentes principais) Redes neurais em trading: abordagem semântica baseada em spikes para identificação espaço-temporal (Componentes principais)
Neste artigo, analisamos em detalhes a integração do módulo SSAM ao bloco SEW-ResNeXt, mostrando como o framework S3CE-Net permite combinar de forma eficiente a atenção baseada em spikes com blocos residuais. Essa arquitetura proporciona um processamento preciso dos fluxos de dados temporais e espaciais, além de alta estabilidade durante o treinamento. A modularidade e a flexibilidade dos componentes facilitam a expansão do modelo e a reutilização de métodos já validados.