Você está na página 1de 10

2º Trabalho de TEI: Meta-heurísticas

Sérgio Silva Mucciaccia

Simulated Annealing Aplicado ao PRVC

29 de novembro de 2019
Sumário

Índice
Sumário............................................................................................................................................2
Introdução........................................................................................................................................3
Representação das soluções.............................................................................................................4
Estrutura de vizinhança....................................................................................................................5
Detalhes de implementação.............................................................................................................6
Resultados computacionais..............................................................................................................7
Notas sobre o tempo de execução....................................................................................................9
Referências.....................................................................................................................................10
Introdução

O recozimento simulado é uma meta-heurística de pesquisa local popular usada para tratar
problemas de otimização discretos e, em menor grau, problemas de otimização contínua. O
principal recurso do recozimento simulado é que ele fornece um meio de escapar dos ótimos locais,
permitindo movimentos de escalada (ou seja, movimentos que pioram o valor da função objetivo)
na esperança de encontrar um ótimo global. Neste trabalho, será apresentada uma implementação
em C++ do recozimento simulado, sua definição e descrição e os resultados obtidos da execução da
heurística em 3 diferentes datasets disponíveis na literatura. Algumas decisões tomadas para a
implementação de recozimento simulado em termos de cronogramas de resfriamento, funções de
vizinhança e solução inicial serão discutidas.

Pseudocódigo do algoritmo implementado neste trabalho:

s_atual = criarSoluçãoInicial();
s_melhor = s_atual;
PARA i DE 1 ATÉ i_max FAÇA
s_i = criarVizinho(s_atual);
temp = calcularTemperatura(i, tmax);
SE custo(s_i) < custo(s_atual) ENTÃO
s_atual = s_i;
SENÃO
SE exp( (custo(s_atual) – custo(s_i)) / temp ) > Rand() ENTÃO
s_atual = s_i;
FIM SE;
FIM SE;
SE custo(s_i) < custo(s_melhor) ENTÃO
s_melhor = s_i;
FIM SE;
FIM PARA;
RETORNA s_melhor;

Representação das soluções


As soluções são representadas como um vetor contendo a sequência de cidades visitadas. Por
exemplo, o arquivo de solução a seguir:

Route #1: 1
Route #2: 8 5 3
Route #3: 9 12 10 6
Route #4: 11 4 7 2
Cost 247

É representado internamente pelo algoritmo desta forma:

0 1 0 8 5 3 0 9 12 10 6 0 11 4 7 2 0

As seguintes regras devem ser obedecidas para que um vetor de solução seja válido:

• Todos os números devem aparecer no vetor de solução.


• Exceto o zero, nenhum número pode aparecer duas ou mais vezes no vetor de solução.
• O vetor de solução deve começar com um zero e terminar com um zero.
• A soma das demandas dos números entre dois zeros deve ser menor que a capacidade.

Zeros em sequência são desconsiderados pelos métodos no vetor de solução. Isso permite que rotas
sejam adicionadas ou retiradas apenas com permutações do vetor. Por exemplo, o seguinte vetor de
solução é válido e tratado da mesma maneira que o vetor anterior:

0 1 0 8 5 3 0 0 9 12 10 6 0 11 4 7 2 0 0

Essa representação tem a vantagem de facilitar a implementação das vizinhanças 2-opt, 3-opt e 4-
opt, além de facilitar o cálculo do custo e da validade da solução que podem ser feitos com
complexidade O(n).

Estrutura de vizinhança
O algoritmo pode ser utilizado com as vizinhanças 2-opt, 3-opt e 4-opt. A vizinhança 2-opt gerou os
melhores resultados, pois, o aumento na velocidade de execução compensa o número menor de
vizinhos. Devido a este fato, a vizinhança 2-opt foi escolhida para realizar os benchmarks do
algoritmo nos sets A, B e F. O movimento 2-opt entre as arestas {12, 10} e {4, 7} pode ser visto nos
vetores de solução a seguir:

Vetor original:
0 1 0 8 5 3 0 9 12 10 6 0 11 4 7 2 0

Vetor depois do 2-opt:


0 1 0 8 5 3 0 9 12 4 11 0 6 10 7 2 0

Para verificar se o vizinho obtido efetuando este movimento 2-opt tem um custo menor que a
solução atual, não é necessário gerar a solução vizinha. Basta apenas somar o comprimento das
novas arestas {12, 4} e {10, 7} e subtrair do comprimento das arestas antigas {12, 10} e {4 ,7}.
Para saber se o novo vizinho é uma solução válida, também não é necessário gerar a solução
vizinha. Basta verificar se a demanda total das rotas alteradas permanece menor que a capacidade.
Portanto, apenas 2 rotas precisam ser verificadas, como ilustrado a seguir.

Rotas que precisam ser verificadas após o movimento 2-opt:


0 1 0 8 5 3 0 9 12 4 11 0 6 10 7 2 0

Com isso, não é necessário fazer uma cópia do vetor de solução na memória para verificar as
condições de aceitação do movimento no SA. Basta escolher um movimento 2-opt e verificar se
este movimento gerará uma solução válida, se a solução for válida é possível prever o custo da
solução gerada pelo movimento e utilizar o valor obtido na previsão para decidir se o SA aceitará ou
não o movimento. Apenas se o movimento for aceito ele será executado na solução. Portanto, para
efetuar o SA só são necessários dois vetores de solução na memória. Um que guardará a melhor
solução até o momento e outro em que serão aplicados os movimentos. Assim, o algoritmo
consegue rodar na maior instância da CVRPLIB, a Flanders2.vrp, que possui 30 mil cidades,
gastando menos de 1 MB de memória.

A previsão de custo da solução gerada por um movimento 2-opt não depende do número de cidades
da solução, pois independente do tamanho apenas 4 arestas serão avaliadas. Do mesmo modo, a
previsão de validade depende apenas do tamanho das duas rotas avaliadas e não do total de cidades
na solução. Como uma iteração do SA não envolve analisar toda a vizinhança, mas somente gerar
um movimento aleatório, esta implementação se mostra bem interessante para uso em problemas
muito grandes, pois sua complexidade cresce linearmente com o número de cidades do problema.

Detalhes de implementação

A linguagem C++ foi escolhida pela sua performance. A seguir estão os comparativos de execução
do mesmo algoritmo em 2 benchmarks feitos pelo grupo benchmarksgame-team.

Benchmark k-nucleotide.
Linguagem Tempo (segundos) Memória (bytes)
C++ (g++) 3,81 156348
C (gcc) 6,00 130132
Java (OpenJ9) 8,43 345684
Python 3 72,24 199856

Benchmark n-body
Linguagem Tempo (segundos) Memória (bytes)
C (gcc) 7,30 8
C++ (g++) 7,92 1116
Java (OpenJ9) 21,95 35604
Python 3 840,10 8176

O código foi feito em c++11 e o compilador utilizado foi o g++ 9.2.1. O código foi compilado com
as flags “-std=c++11”, “-pthread” e “-O3”. O algoritmo foi executado em uma máquina com um
processador Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz, com 6144 KB de cache e 4 cores. A
memória ram total é de 8 GB.

Resultados computacionais
O algoritmo foi executado 5 vezes por 500 milhões de iterações em cada instância, sendo que
nenhuma das execuções demorou mais de 300 segundos.

Set A (Augerat, 1995):

Custo t(s) Custo t(s) Custo t(s) Custo t(s) Custo t(s) Média Opt Diff
A-n32-k5.vrp 784 98 784 110 784 101 784 90 784 102 784 784 0
A-n33-k5.vrp 661 100 661 101 661 102 661 98 661 84 661 661 0
A-n33-k6.vrp 742 107 742 102 742 94 742 104 742 118 742 742 0
A-n34-k5.vrp 778 107 778 121 778 116 778 120 778 117 778 778 0
A-n36-k5.vrp 799 125 799 118 799 124 799 126 799 129 799 799 0
A-n37-k5.vrp 669 113 669 119 669 114 669 119 669 125 669 669 0
A-n37-k6.vrp 949 125 949 121 949 123 949 126 949 134 949 949 0
A-n38-k5.vrp 730 128 730 122 730 132 730 117 730 117 730 730 0
A-n39-k5.vrp 822 142 822 143 822 144 822 144 822 142 822 822 0
A-n39-k6.vrp 831 138 831 134 831 137 831 135 833 132 831,4 831 0
A-n44-k7.vrp 937 141 937 140 937 149 937 150 937 143 937 937 0
A-n45-k6.vrp 944 157 944 153 944 149 945 152 948 141 945 944 0
A-n45-k7.vrp 1146 162 1146 166 1146 161 1146 169 1146 156 1146 1146 0
A-n46-k7.vrp 914 159 914 157 914 155 914 150 914 151 914 914 0
A-n48-k7.vrp 1073 165 1073 169 1073 164 1073 166 1073 163 1073 1073 0
A-n53-k7.vrp 1010 175 1011 166 1014 169 1017 175 1017 193 1013,8 1010 0
A-n54-k7.vrp 1167 196 1170 185 1167 197 1167 199 1167 197 1167,6 1167 0
A-n55-k9.vrp 1073 196 1073 198 1073 194 1073 204 1073 201 1073 1073 0
A-n60-k9.vrp 1354 219 1358 223 1355 224 1358 234 1361 229 1357,2 1354 0
A-n61-k9.vrp 1034 213 1035 220 1035 208 1035 209 1035 206 1034,8 1034 0
A-n62-k8.vrp 1308 219 1308 219 1308 221 1308 219 1308 218 1308 1288 20
A-n63-k10.vrp 1316 219 1320 224 1317 229 1319 231 1321 229 1318,6 1314 2
A-n63-k9.vrp 1628 218 1629 234 1629 236 1629 237 1629 238 1628,8 1616 12
A-n64-k9.vrp 1401 230 1402 230 1402 222 1405 212 1411 211 1404,2 1401 0
A-n65-k9.vrp 1174 229 1178 226 1178 230 1178 231 1179 227 1177,4 1174 0
A-n69-k9.vrp 1163 227 1167 244 1167 225 1164 242 1169 224 1166 1159 4
A-n80-k10.vrp 1763 262 1764 272 1769 252 1770 260 1779 262 1769 1763 0

Set B (Augerat, 1995):


Custo t(s) Custo t(s) Custo t(s) Custo t(s) Custo t(s) Média Opt Diff
B-n31-k5.vrp 672 107 672 113 672 101 672 99 672 112 672 672 0
B-n34-k5.vrp 788 139 788 141 788 142 788 134 788 127 788 788 0
B-n35-k5.vrp 955 139 955 138 955 136 955 135 955 134 955 955 0
B-n38-k6.vrp 805 144 805 145 805 142 805 146 805 140 805 805 0
B-n39-k5.vrp 549 145 549 150 549 150 549 142 549 150 549 549 0
B-n41-k6.vrp 829 176 829 147 829 180 829 152 829 146 829 829 0
B-n43-k6.vrp 742 150 742 151 742 153 742 148 742 151 742 742 0
B-n44-k7.vrp 909 161 909 164 909 166 909 162 909 168 909 909 0
B-n45-k5.vrp 751 145 751 145 751 144 751 145 751 146 751 751 0
B-n45-k6.vrp 678 149 678 153 678 160 678 146 678 153 678 678 0
B-n50-k7.vrp 741 165 741 155 741 166 741 165 741 165 741 741 0
B-n50-k8.vrp 1312 180 1312 178 1312 181 1312 195 1312 179 1312 1312 0
B-n51-k7.vrp 1032 171 1032 172 1032 171 1032 169 1032 172 1032 1032 0
B-n52-k7.vrp 747 161 747 170 747 166 747 168 747 168 747 747 0
B-n56-k7.vrp 707 167 707 171 707 163 707 160 707 167 707 707 0
B-n57-k7.vrp 1153 180 1153 181 1153 177 1153 181 1153 179 1153 1153 0
B-n57-k9.vrp 1598 187 1598 185 1598 187 1598 190 1598 187 1598 1598 0
B-n63-k10.vrp 1496 196 1497 214 1497 198 1497 216 1500 189 1497,4 1496 0
B-n64-k9.vrp 861 188 861 194 861 191 861 182 861 193 861 861 0
B-n66-k9.vrp 1316 204 1316 202 1316 203 1318 200 1318 201 1316,8 1316 0
B-n67-k10.vrp 1032 185 1032 195 1032 198 1033 183 1033 207 1032,4 1032 0
B-n68-k9.vrp 1272 214 1272 213 1272 212 1273 196 1274 195 1272,6 1272 0
B-n78-k10.vrp 1221 199 1221 229 1221 198 1221 198 1221 200 1221 1221 0

Set F (Fisher, 1994)


Custo t(s) Custo t(s) Custo t(s) Custo t(s) Custo t(s) Média Opt Diff
F-n45-k4.vrp 724 81 724 85 724 87 724 89 724 74 724 724 0
F-n72-k4.vrp 237 66 238 61 239 69 240 83 241 57 239 237 0
F-n135-k7.vrp 1164 135 1164 135 1165 136 1168 136 1169 136 1166 1162 2

Notas sobre o tempo de execução


Uma propriedade interessante do Simulated Annealing é o seu comportamento com o crescimento
do problema. Para realizar uma análise desta propriedade, o set AGS (2017) da CVRPLIB é mais
adequado, por ter problemas com um número maior de vértices que os outros sets. O quadro a
seguir mostra o tempo de execução do S.A. com 1 milhão de iterações para os problemas do set
AGS.
Vértices (n) Capacidade Upper Bound SA Tempo (ms)
Leuven1 3000 25 193.343 408.151 8.040
Leuven2 4000 150 112.751 200.766 4.049
Antwerp1 6000 30 479.021 1.437.011 41.569
Antwerp2 7000 100 294.319 637.238 24.175
Ghent1 10000 35 471.084 1.792.823 94.670
Ghent2 11000 170 261.676 917.331 10.849
Brussels1 15000 50 504.392 3.278.590 182.947
Brussels2 16000 150 353.285 1.779.566 14.695
Flanders1 20000 50 7.273.695 45.583.484 331.736
Flanders2 30000 200 4.480.972 39.661.208 21.154

Com os testes realizados, foi percebido que, no pior caso, o tempo de execução do Simulated
Annealing cresce linearmente com o número de vértices para um número fixo de iterações. Como
mostra o quadro, os tempos de execução não dependem apenas do número de vértices, mas também
da capacidade dos veículos e da posição dos vértices. Ou seja, podem existir problemas com poucos
vértices que são mais difíceis de se obter boas soluções que problemas com muitos vértices.

Um fato interessante é que, para a instância Flanders2, uma única busca local na vizinhança 2-opt,
utilizando-se da estratégia Hill Climbing, demorou cerca de 8 horas pra terminar a execução. Isso
praticamente inviabiliza a utilização do Iterated Local Search 2-opt em um tempo razoável. Para
problemas muito grandes, é bem mais interessante utilizar algoritmos em que a complexidade de
resolução não cresce muito rapidamente com o tamanho do problema.

As heurísticas Ant Colony Optimization e Genetic Algorithm tem uma complexidade que aumenta
muito rápido com o tamanho do problema. A primeira, porque a matriz de feromônios possui uma
complexidade que aumenta com o quadrado do número de cidades e a segunda, porque precisa fazer
muitas cópias das soluções para realizar as operações de Cross Over. Das 12 heurísticas estudadas é
provável que o SA será uma das poucas heurísticas que consegue obter bons resultados na instância
Flanders2 em um tempo razoável.

Em uma iteração do SA 2-opt, o movimento aleatório gerado pode ser rejeitado ou aceito. Caso
rejeitado, o tempo de execução da iteração é muito pequeno, pois devido à previsão de validade e à
previsão de custo, o tempo de execução é bastante reduzido, tornando a complexidade desta etapa
próxima a O(1). Já se o movimento for aceito, será necessário executar um 2-opt na solução e essa
operação cresce linearmente com o tamanho da solução, apresentando complexidade aproximada a
O(n/2). Se o movimento gerar uma solução melhor do que a melhor solução atual, a solução atual
deve ser copiada e armazenada como a melhor solução, o que demanda visitar todos os vértices, ou
seja, uma complexidade O(n).

Portanto, uma desvantagem do algoritmo deste trabalho é que é muito difícil prever seu tempo de
execução. Se o algoritmo for feito de modo a parar depois de X segundos, o cronograma de
temperatura pode estar no meio, o que vai afetar bastante a qualidade da solução obtida. O melhor
que pode ser feito nesta situação é calcular o pior caso do algoritmo e escolher o número de
iterações para que o pior caso fique a baixo dos X segundos. Mas dessa forma é provável que o
algoritmo termine muito antes e não aproveite de maneira adequado o tempo disponível para
execução.

Referências
[1] KIRKPATRICK, S; GELATT, C. D; VECCHI, M. P. Optimization by Simulated Annealing.
Science, v. 220. n. 4598. pp. 671-680. Maio, 1983.

[2] BROWNLEE, Jason. Clever Algorithms: Nature-Inspired Programming Recipes. Melbourne,


Australia: LuLu, 2011. 423p.

[3] GLOVER, Fred; KOCHENBERGER, Gary A. Handbook of Metaheuristics. New York, USA:
Kluwer Academic Publishers. 2003. 556p.

[4] TALBI, El-Ghazali. Metaheuristics: From Design to implementation. Hoboken, New Jersey:
John Wiley & Sons, Inc. 2009. 593p.

[5] MITRA, D; ROMEO, F; SANGIOVANNI-VINCENTELLI, A. Convergence and finite-time


behavior of simulated annealing. Advanced Applied Probabilistics, v. 18. n. 1. pp. 747-771. 1986.

[6] CARVALHO, A. S; MARIANO G. P; KAMPKE, E. H; MAURI, G. R. Simulated Annealing


aplicado ao problema de programação de horários do CCA-UFES. Universidade Federal do Espírito
Santo. 2014.

Você também pode gostar